335 lines
10 KiB
JavaScript
335 lines
10 KiB
JavaScript
/* istanbul ignore file */
|
|
// @todo
|
|
// 1. Return with annotations like (overridden headers, auth headers etc.)
|
|
// 2. Utilize requester (core.js) methods for dryRun
|
|
// 3. Add tests
|
|
const _ = require('lodash'),
|
|
async = require('async'),
|
|
mime = require('mime-types'),
|
|
urlEncoder = require('postman-url-encoder'),
|
|
Request = require('postman-collection').Request,
|
|
authorizeRequest = require('../authorizer').authorizeRequest,
|
|
authHandlers = require('../authorizer').AuthLoader.handlers,
|
|
version = require('../../package.json').version,
|
|
|
|
CALCULATED_AT_RUNTIME = '<calculated when request is sent>',
|
|
COOKIE = 'Cookie',
|
|
FUNCTION = 'function',
|
|
CONTENT_TYPE = 'Content-Type',
|
|
DEFAULT_MIME_TYPE = 'application/octet-stream',
|
|
CONTENT_TYPE_URLENCODED = 'application/x-www-form-urlencoded',
|
|
CONTENT_TYPE_FORMDATA = 'multipart/form-data; boundary=' + CALCULATED_AT_RUNTIME,
|
|
|
|
CONTENT_TYPE_LANGUAGE = {
|
|
'html': 'text/html',
|
|
'text': 'text/plain',
|
|
'json': 'application/json',
|
|
'javascript': 'application/javascript',
|
|
'xml': 'application/xml'
|
|
},
|
|
|
|
BODY_MODE = {
|
|
raw: 'raw',
|
|
file: 'file',
|
|
graphql: 'graphql',
|
|
formdata: 'formdata',
|
|
urlencoded: 'urlencoded'
|
|
};
|
|
|
|
/**
|
|
* Check if request body is empty and also handles disabled params for urlencoded
|
|
* and formdata bodies.
|
|
*
|
|
* @todo Update Collection SDK isEmpty method to account for this.
|
|
*
|
|
* @private
|
|
* @param {RequestBody} body
|
|
* @returns {Boolean}
|
|
*/
|
|
function bodyIsEmpty (body) {
|
|
if (!body || body.disabled || body.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
var i,
|
|
param,
|
|
mode = body.mode;
|
|
|
|
if (!(mode === BODY_MODE.formdata || mode === BODY_MODE.urlencoded)) {
|
|
return false;
|
|
}
|
|
|
|
for (i = body[mode].members.length - 1; i >= 0; i--) {
|
|
param = body[mode].members[i];
|
|
// bail out if a single enabled param is present
|
|
if (param && !param.disabled) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Add new System header.
|
|
*
|
|
* @param {object} headers
|
|
* @param {String} key
|
|
* @param {String} value
|
|
*/
|
|
function addSystemHeader (headers, key, value) {
|
|
headers.add({
|
|
key: key,
|
|
value: value,
|
|
system: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Authorize the given request.
|
|
*
|
|
* @private
|
|
* @param {Request} request
|
|
* @param {Function} callback
|
|
*/
|
|
function setAuthorization (request, callback) {
|
|
authorizeRequest(request, function (err, clonedRequest) {
|
|
// @note authorizeRequest returns a cloned request.
|
|
!clonedRequest && (clonedRequest = new Request(request.toJSON()));
|
|
|
|
if (err) {
|
|
return callback(null, clonedRequest);
|
|
}
|
|
|
|
var auth = request.auth,
|
|
authType = auth && auth.type,
|
|
manifest = _.get(authHandlers, [authType, 'manifest']),
|
|
headers = _.get(clonedRequest, 'headers.reference') || {},
|
|
queryParams = _.get(clonedRequest, 'url.query.reference') || {},
|
|
bodyParams = _.get(clonedRequest, 'body.urlencoded.reference') || {},
|
|
propertyList,
|
|
propertyKey,
|
|
property;
|
|
|
|
if (authType === 'apikey' && (auth = auth.apikey)) {
|
|
propertyKey = String(auth.get('key')).toLowerCase();
|
|
propertyList = auth.get('in') === 'query' ? queryParams : headers;
|
|
|
|
if ((property = propertyList[propertyKey])) {
|
|
Array.isArray(property) && (property = property[property.length - 1]);
|
|
property.auth = true;
|
|
}
|
|
|
|
return callback(null, clonedRequest);
|
|
}
|
|
|
|
if (!(manifest && manifest.updates)) {
|
|
return callback(null, clonedRequest);
|
|
}
|
|
|
|
manifest.updates.forEach(function (update) {
|
|
propertyKey = update.property;
|
|
|
|
switch (update.type) {
|
|
case 'header':
|
|
propertyKey = propertyKey.toLowerCase();
|
|
propertyList = headers;
|
|
break;
|
|
case 'url.param':
|
|
propertyList = queryParams;
|
|
break;
|
|
case 'body.urlencoded':
|
|
propertyList = bodyParams;
|
|
break;
|
|
default: return;
|
|
}
|
|
|
|
if ((property = propertyList[propertyKey])) {
|
|
Array.isArray(property) && (property = property[property.length - 1]);
|
|
property.auth = true;
|
|
}
|
|
});
|
|
|
|
callback(null, clonedRequest);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Adds Content-Type header based on selected request body.
|
|
*
|
|
* @private
|
|
* @param {Request} request
|
|
* @param {Function} callback
|
|
*/
|
|
function setContentType (request, callback) {
|
|
// bail out if body is empty
|
|
if (bodyIsEmpty(request.body)) {
|
|
return callback(null, request);
|
|
}
|
|
|
|
var headers = request.headers,
|
|
contentLanguage;
|
|
|
|
switch (request.body.mode) {
|
|
case BODY_MODE.raw:
|
|
contentLanguage = _.get(request, 'body.options.raw.language', 'text');
|
|
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_LANGUAGE[contentLanguage] ||
|
|
CONTENT_TYPE_LANGUAGE.text);
|
|
break;
|
|
case BODY_MODE.urlencoded:
|
|
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_URLENCODED);
|
|
break;
|
|
case BODY_MODE.formdata:
|
|
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_FORMDATA);
|
|
break;
|
|
case BODY_MODE.graphql:
|
|
addSystemHeader(headers, CONTENT_TYPE, CONTENT_TYPE_LANGUAGE.json);
|
|
break;
|
|
case BODY_MODE.file:
|
|
addSystemHeader(headers, CONTENT_TYPE,
|
|
mime.lookup(request.body.file && request.body.file.src) || DEFAULT_MIME_TYPE);
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
addSystemHeader(headers, 'Content-Length', CALCULATED_AT_RUNTIME);
|
|
|
|
callback(null, request);
|
|
}
|
|
|
|
/**
|
|
* Adds Cookie header for the given request url.
|
|
*
|
|
* @private
|
|
* @param {Request} request
|
|
* @param {Object} cookieJar
|
|
* @param {Function} callback
|
|
*/
|
|
function setCookie (request, cookieJar, callback) {
|
|
// bail out if not a valid instance of CookieJar
|
|
if (!(cookieJar && cookieJar.getCookieString)) {
|
|
return callback(null, request);
|
|
}
|
|
|
|
// @note don't pass request.url instance to force re-parsing of the URL
|
|
cookieJar.getCookieString(urlEncoder.toNodeUrl(request.url.toString()), function (err, cookies) {
|
|
if (err) {
|
|
return callback(null, request);
|
|
}
|
|
|
|
if (cookies && cookies.length) {
|
|
addSystemHeader(request.headers, COOKIE, cookies);
|
|
}
|
|
|
|
callback(null, request);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* A helper method to dry run the given request instance.
|
|
* It returns the cloned request instance with the system added properties.
|
|
*
|
|
* @param {Request} request
|
|
* @param {Object} options
|
|
* @param {Object} options.cookieJar
|
|
* @param {Object} options.protocolProfileBehavior
|
|
* @param {Function} done
|
|
*/
|
|
function dryRun (request, options, done) {
|
|
if (!done && typeof options === FUNCTION) {
|
|
done = options;
|
|
options = {};
|
|
}
|
|
|
|
if (!Request.isRequest(request)) {
|
|
return done(new Error('Invalid Request instance'));
|
|
}
|
|
|
|
!options && (options = {});
|
|
|
|
var cookieJar = options.cookieJar,
|
|
implicitCacheControl = options.implicitCacheControl,
|
|
implicitTraceHeader = options.implicitTraceHeader,
|
|
disabledSystemHeaders = _.get(options.protocolProfileBehavior, 'disabledSystemHeaders') || {},
|
|
disableCookies = _.get(options.protocolProfileBehavior, 'disableCookies');
|
|
|
|
async.waterfall([
|
|
function setAuthorizationHeaders (next) {
|
|
setAuthorization(request, next);
|
|
},
|
|
function setContentTypeHeader (request, next) {
|
|
setContentType(request, next);
|
|
},
|
|
function setContentLength (request, next) {
|
|
var headers = request.headers,
|
|
header = headers.one('content-length');
|
|
|
|
// bail out if header added by body helper
|
|
if (header && header.system) {
|
|
return next(null, request);
|
|
}
|
|
|
|
switch (String(request.method).toUpperCase()) {
|
|
case 'GET':
|
|
case 'HEAD':
|
|
case 'TRACE':
|
|
case 'DELETE':
|
|
case 'CONNECT':
|
|
case 'OPTIONS':
|
|
break;
|
|
default:
|
|
addSystemHeader(headers, 'Content-Length', '0');
|
|
break;
|
|
}
|
|
|
|
next(null, request);
|
|
},
|
|
function setCookieHeader (request, next) {
|
|
if (disableCookies || !cookieJar) {
|
|
return next(null, request);
|
|
}
|
|
|
|
setCookie(request, cookieJar, next);
|
|
},
|
|
function setStaticHeaders (request, next) {
|
|
var headers = request.headers;
|
|
|
|
// remove header added by auth helpers
|
|
headers.remove(function (header) {
|
|
return header.system && header.key.toLowerCase() === 'host';
|
|
});
|
|
|
|
addSystemHeader(headers, 'User-Agent', 'PostmanRuntime/' + version);
|
|
addSystemHeader(headers, 'Accept', '*/*');
|
|
addSystemHeader(headers, 'Accept-Encoding', 'gzip, deflate, br');
|
|
addSystemHeader(headers, 'Host', CALCULATED_AT_RUNTIME);
|
|
addSystemHeader(headers, 'Connection', 'keep-alive');
|
|
implicitCacheControl && addSystemHeader(headers, 'Cache-Control', 'no-cache');
|
|
implicitTraceHeader && addSystemHeader(headers, 'Postman-Token', CALCULATED_AT_RUNTIME);
|
|
|
|
next(null, request);
|
|
},
|
|
function disableSystemHeaders (request, next) {
|
|
var headersReference = request.headers.reference,
|
|
header;
|
|
|
|
_.forEach(disabledSystemHeaders, function (disabled, headerKey) {
|
|
if (!disabled) { return; }
|
|
|
|
if ((header = headersReference[headerKey.toLowerCase()])) {
|
|
Array.isArray(header) && (header = header[header.length - 1]);
|
|
header.system && (header.disabled = true);
|
|
}
|
|
});
|
|
|
|
next(null, request);
|
|
}
|
|
], function (err, request) {
|
|
if (err) { return done(err); }
|
|
|
|
done(null, request);
|
|
});
|
|
}
|
|
|
|
module.exports = dryRun;
|