var util = require('../util'),
_ = util.lodash,
E = '',
SPC = ' ',
CRLF = '\r\n',
HEADER_KV_SEPARATOR = ':',
Property = require('./property').Property,
PropertyList = require('./property-list').PropertyList,
Header;
/**
* @typedef Header.definition
* @property {String} key The Header name (e.g: 'Content-Type')
* @property {String} value The value of the header.
*
* @example <caption>Create a header</caption>
* var Header = require('postman-collection').Header,
* header = new Header({
* key: 'Content-Type',
* value: 'application/xml'
* });
*
* console.log(header.toString()) // prints the string representation of the Header.
*/
_.inherit((
/**
* Represents an HTTP header, for requests or for responses.
*
* @constructor
* @extends {Property}
*
* @param {Header.definition|String} options - Pass the header definition as an object or the value of the header.
* If the value is passed as a string, it should either be in `name:value` format or the second "name" parameter
* should be used to pass the name as string
* @param {String} [name] - optional override the header name or use when the first parameter is the header value as
* string.
*
* @example <caption>Parse a string of headers into an array of Header objects</caption>
* var Header = require('postman-collection').Header,
* headerString = 'Content-Type: application/json\nUser-Agent: MyClientLibrary/2.0\n';
*
* var rawHeaders = Header.parse(headerString);
* console.log(rawHeaders); // [{ 'Content-Type': 'application/json', 'User-Agent': 'MyClientLibrary/2.0' }]
*
* var headers = rawHeaders.map(function (h) {
* return new Header(h);
* });
*
* function assert(condition, message) {
* if (!condition) {
* message = message || "Assertion failed";
* if (typeof Error !== "undefined") {
* throw new Error(message);
* }
* throw message; //fallback
* }
* else {
* console.log("Assertion passed");
* }
* }
*
* assert(headerString.trim() === Header.unparse(headers).trim());
*/
Header = function PostmanHeader (options, name) {
if (_.isString(options)) {
options = _.isString(name) ? { key: name, value: options } : Header.parseSingle(options);
}
// this constructor is intended to inherit and as such the super constructor is required to be executed
Header.super_.apply(this, arguments);
this.update(options);
}), Property);
_.assign(Header.prototype, /** @lends Header.prototype */ {
/**
* Converts the header to a single header string.
*
* @returns {String}
*/
toString () {
return this.key + ': ' + this.value;
},
/**
* Return the value of this header.
*
* @returns {String}
*/
valueOf () {
return this.value;
},
/**
* Assigns the given properties to the Header
*
* @param {Object} options -
* @todo check for allowed characters in header key-value or store encoded.
*/
update (options) {
/**
* The header Key
*
* @type {String}
* @todo avoid headers with falsy key.
*/
this.key = _.get(options, 'key') || E;
/**
* The header value
*
* @type {String}
*/
this.value = _.get(options, 'value', E);
/**
* Indicates whether the header was added by internal SDK operations, such as authorizing a request.
*
* @type {*|boolean}
*/
_.has(options, 'system') && (this.system = options.system);
/**
* Indicates whether the header should be .
*
* @type {*|boolean}
* @todo figure out whether this should be in property.js
*/
_.has(options, 'disabled') && (this.disabled = options.disabled);
}
});
_.assign(Header, /** @lends Header */ {
/**
* Defines the name of this property for internal use.
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyName: 'Header',
/**
* Specify the key to be used while indexing this object
*
* @private
* @readOnly
* @type {String}
*/
_postman_propertyIndexKey: 'key',
/**
* Specifies whether the index lookup of this property, when in a list is case insensitive or not
*
* @private
* @readOnly
* @type {boolean}
*/
_postman_propertyIndexCaseInsensitive: true,
/**
* Since each header may have multiple possible values, this is set to true.
*
* @private
* @readOnly
* @type {Boolean}
*/
_postman_propertyAllowsMultipleValues: true,
/**
* Parses a multi line header string into an array of {@link Header.definition}.
*
* @param {String} headerString -
* @returns {Array}
*/
parse: function (headerString) {
var headers = [],
regexes = {
header: /^(\S+):(.*)$/gm,
fold: /\r\n([ \t])/g,
trim: /^\s*(.*\S)?\s*$/ // eslint-disable-line security/detect-unsafe-regex
},
match = regexes.header.exec(headerString);
headerString = headerString.toString().replace(regexes.fold, '$1');
while (match) {
headers.push({
key: match[1],
value: match[2].replace(regexes.trim, '$1')
});
match = regexes.header.exec(headerString);
}
return headers;
},
/**
* Parses a single Header.
*
* @param {String} header -
* @returns {{key: String, value: String}}
*/
parseSingle: function (header) {
if (!_.isString(header)) { return { key: E, value: E }; }
var index = header.indexOf(HEADER_KV_SEPARATOR),
key,
value;
(index < 0) && (index = header.length);
key = header.substr(0, index);
value = header.substr(index + 1);
return {
key: _.trim(key),
value: _.trim(value)
};
},
/**
* Stringifies an Array or {@link PropertyList} of Headers into a single string.
*
* @note Disabled headers are excluded.
*
* @param {Array|PropertyList<Header>} headers -
* @param {String=} [separator='\r\n'] - Specify a string for separating each header
* @returns {String}
*/
unparse: function (headers, separator = CRLF) {
if (!_.isArray(headers) && !PropertyList.isPropertyList(headers)) {
return E;
}
return headers.reduce(function (acc, header) {
if (header && !header.disabled) {
acc += Header.unparseSingle(header) + separator;
}
return acc;
}, E);
},
/**
* Unparses a single Header.
*
* @param {String} header -
* @returns {String}
*/
unparseSingle: function (header) {
if (!_.isObject(header)) { return E; }
return header.key + HEADER_KV_SEPARATOR + SPC + header.value;
},
/**
* Check whether an object is an instance of PostmanHeader.
*
* @param {*} obj -
* @returns {Boolean}
*/
isHeader: function (obj) {
return Boolean(obj) && ((obj instanceof Header) ||
_.inSuperChain(obj.constructor, '_postman_propertyName', Header._postman_propertyName));
},
/* eslint-disable jsdoc/check-param-names */
/**
* Create a new header instance
*
* @param {Header.definition|String} [value] - Pass the header definition as an object or the value of the header.
* If the value is passed as a string, it should either be in `name:value` format or the second "name" parameter
* should be used to pass the name as string
* @param {String} [name] - optional override the header name or use when the first parameter is the header value as
* string.
* @returns {Header}
*/
create: function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(Header);
return new (Header.bind.apply(Header, args))(); // eslint-disable-line prefer-spread
}
/* eslint-enable jsdoc/check-param-names */
});
module.exports = {
Header
};