query-param.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. "use strict";
  2. var _ = require('../../lodash'),
  3. // @todo discontinue in v4
  4. encodeQueryParam = require('postman-url-encoder/encoder').encodeQueryParam, Property = require('./property').Property, PropertyList = require('./property-list').PropertyList, E = '', AMPERSAND = '&', STRING = 'string', EQUALS = '=', EMPTY = '', HASH = '#', BRACE_START = '{{', BRACE_END = '}}', REGEX_HASH = /#/g, REGEX_EQUALS = /=/g, // eslint-disable-line no-div-regex
  5. REGEX_AMPERSAND = /&/g, REGEX_BRACE_START = /%7B%7B/g, REGEX_BRACE_END = /%7D%7D/g, REGEX_EXTRACT_VARS = /{{[^{}]*[&#=][^{}]*}}/g, QueryParam,
  6. /**
  7. * Percent encode reserved chars (&, = and #) in the given string.
  8. *
  9. * @private
  10. * @param {String} str
  11. * @param {Boolean} encodeEquals
  12. * @returns {String}
  13. */
  14. encodeReservedChars = function (str, encodeEquals) {
  15. if (!str) {
  16. return str;
  17. }
  18. // eslint-disable-next-line lodash/prefer-includes
  19. str.indexOf(AMPERSAND) !== -1 && (str = str.replace(REGEX_AMPERSAND, '%26'));
  20. // eslint-disable-next-line lodash/prefer-includes
  21. str.indexOf(HASH) !== -1 && (str = str.replace(REGEX_HASH, '%23'));
  22. // eslint-disable-next-line lodash/prefer-includes
  23. encodeEquals && str.indexOf(EQUALS) !== -1 && (str = str.replace(REGEX_EQUALS, '%3D'));
  24. return str;
  25. },
  26. /**
  27. * Normalize the given param string by percent-encoding the reserved chars
  28. * such that it won't affect the re-parsing.
  29. *
  30. * @note `&`, `=` and `#` needs to be percent-encoded otherwise re-parsing
  31. * the same URL string will generate different output
  32. *
  33. * @private
  34. * @param {String} str
  35. * @param {Boolean} encodeEquals
  36. * @returns {String}
  37. */
  38. normalizeParam = function (str, encodeEquals) {
  39. // bail out if the given sting is null or empty
  40. if (!(str && typeof str === STRING)) {
  41. return str;
  42. }
  43. // bail out if the given string does not include reserved chars
  44. // eslint-disable-next-line lodash/prefer-includes
  45. if (str.indexOf(AMPERSAND) === -1 && str.indexOf(HASH) === -1) {
  46. // eslint-disable-next-line lodash/prefer-includes
  47. if (!(encodeEquals && str.indexOf(EQUALS) !== -1)) {
  48. return str;
  49. }
  50. }
  51. var normalizedString = '', pointer = 0, variable, match, index;
  52. // find all the instances of {{<variable>}} which includes reserved chars
  53. while ((match = REGEX_EXTRACT_VARS.exec(str)) !== null) {
  54. variable = match[0];
  55. index = match.index;
  56. // [pointer, index) string is normalized + the matched variable
  57. normalizedString += encodeReservedChars(str.slice(pointer, index), encodeEquals) + variable;
  58. // update the pointer
  59. pointer = index + variable.length;
  60. }
  61. // whatever left in the string is normalized as well
  62. if (pointer < str.length) {
  63. normalizedString += encodeReservedChars(str.slice(pointer), encodeEquals);
  64. }
  65. return normalizedString;
  66. };
  67. /**
  68. * @typedef QueryParam.definition
  69. * @property {String} key The name ("key") of the query parameter.
  70. * @property {String} value The value of the parameter.
  71. */
  72. _.inherit((
  73. /**
  74. * Represents a URL query parameter, which can exist in request URL or POST data.
  75. *
  76. * @constructor
  77. * @extends {Property}
  78. * @param {FormParam.definition|String} options Pass the initial definition of the query parameter. In case of
  79. * string, the query parameter is parsed using {@link QueryParam.parseSingle}.
  80. */
  81. QueryParam = function PostmanQueryParam(options) {
  82. // this constructor is intended to inherit and as such the super constructor is required to be executed
  83. QueryParam.super_.apply(this, arguments);
  84. this.update(options);
  85. }), Property);
  86. _.assign(QueryParam.prototype, /** @lends QueryParam.prototype */ {
  87. /**
  88. * Converts the QueryParameter to a single param string.
  89. *
  90. * @returns {String}
  91. */
  92. toString: function () {
  93. return QueryParam.unparseSingle(this);
  94. },
  95. /**
  96. * Updates the key and value of the query parameter
  97. *
  98. * @param {String|Object} param
  99. * @param {String} param.key
  100. * @param {String=} [param.value]
  101. */
  102. update: function (param) {
  103. _.assign(this, /** @lends QueryParam.prototype */ _.isString(param) ? QueryParam.parseSingle(param) : {
  104. key: _.get(param, 'key'),
  105. value: _.get(param, 'value')
  106. });
  107. _.has(param, 'system') && (this.system = param.system);
  108. },
  109. valueOf: function () {
  110. return _.isString(this.value) ? this.value : EMPTY;
  111. }
  112. });
  113. _.assign(QueryParam, /** @lends QueryParam */ {
  114. /**
  115. * Defines the name of this property for internal use.
  116. * @private
  117. * @readOnly
  118. * @type {String}
  119. */
  120. _postman_propertyName: 'QueryParam',
  121. /**
  122. * Declare the list index key, so that property lists of query parameters work correctly
  123. *
  124. * @type {String}
  125. */
  126. _postman_propertyIndexKey: 'key',
  127. /**
  128. * Query params can have multiple values, so set this to true.
  129. *
  130. * @type {Boolean}
  131. */
  132. _postman_propertyAllowsMultipleValues: true,
  133. /**
  134. * Parse a query string into an array of objects, where each object contains a key and a value.
  135. *
  136. * @param {String} query
  137. * @returns {Array}
  138. */
  139. parse: function (query) {
  140. return _.isString(query) ? query.split(AMPERSAND).map(QueryParam.parseSingle) : [];
  141. },
  142. /**
  143. * Parses a single query parameter.
  144. *
  145. * @param {String} param
  146. * @param {Number} idx
  147. * @param {String[]} all - array of all params, in case this is being called while parsing multiple params.
  148. * @returns {{key: string|null, value: string|null}}
  149. */
  150. parseSingle: function (param, idx, all) {
  151. // helps handle weird edge cases such as "/get?a=b&&"
  152. if (param === EMPTY && // if param is empty
  153. _.isNumber(idx) && // this and the next condition ensures that this is part of a map call
  154. _.isArray(all) &&
  155. idx !== (all && (all.length - 1))) { // not last parameter in the array
  156. return { key: null, value: null };
  157. }
  158. var index = (typeof param === STRING) ? param.indexOf(EQUALS) : -1, paramObj = {};
  159. // this means that there was no value for this key (not even blank, so we store this info) and the value is set
  160. // to null
  161. if (index < 0) {
  162. paramObj.key = param.substr(0, param.length);
  163. paramObj.value = null;
  164. }
  165. else {
  166. paramObj.key = param.substr(0, index);
  167. paramObj.value = param.substr(index + 1);
  168. }
  169. return paramObj;
  170. },
  171. /**
  172. * Create a query string from array of parameters (or object of key-values). This function ensures that
  173. * the double braces "{{" and "}}" are not URL-encoded on unparsing, which allows for variable-substitution.
  174. *
  175. * @param {Array|Object} params
  176. * @param {Object=} options
  177. * @param {?Boolean} [options.encode=false] - Enables URL encoding of the parameters
  178. * @param {?Boolean} [options.ignoreDisabled=false] - Removes disabled query parameters when set to true.
  179. * @returns {string}
  180. *
  181. * @deprecated since v3.4.6, drop support for `options.encode`
  182. *
  183. * @todo - remove disabled arg and flatten params (retain back compat)
  184. */
  185. unparse: function (params, options) {
  186. if (!params) {
  187. return EMPTY;
  188. }
  189. var str, firstEnabledParam = true, encode = options && options.encode, ignoreDisabled = options && options.ignoreDisabled;
  190. // Convert hash maps to an array of params
  191. if (!_.isArray(params) && !PropertyList.isPropertyList(params)) {
  192. return _.reduce(params, function (result, value, key) {
  193. result && (result += AMPERSAND);
  194. return result + QueryParam.unparseSingle({ key: key, value: value }, encode);
  195. }, EMPTY);
  196. }
  197. // construct a query parameter string from the list, with considerations for disabled values
  198. str = params.reduce(function (result, param) {
  199. // If disabled parameters are to be ignored, bail out here
  200. if (ignoreDisabled && (param.disabled === true)) {
  201. return result;
  202. }
  203. // don't add '&' for the very first enabled param
  204. if (firstEnabledParam) {
  205. firstEnabledParam = false;
  206. }
  207. // add '&' before concatenating param
  208. else {
  209. result += AMPERSAND;
  210. }
  211. return result + QueryParam.unparseSingle(param, encode);
  212. }, EMPTY);
  213. encode && (str = str.replace(REGEX_BRACE_START, BRACE_START).replace(REGEX_BRACE_END, BRACE_END));
  214. return str;
  215. },
  216. /**
  217. * Takes a query param and converts to string
  218. *
  219. * @param {Object} obj
  220. * @param {Boolean} encode
  221. * @returns {String}
  222. *
  223. * @deprecated since v3.4.6, drop support for `encode`
  224. */
  225. unparseSingle: function (obj, encode) {
  226. if (!obj) {
  227. return EMPTY;
  228. }
  229. var key = obj.key, value = obj.value, result;
  230. if (typeof key === STRING) {
  231. result = encode ? encodeQueryParam(key) : normalizeParam(key, true);
  232. }
  233. else {
  234. result = E;
  235. }
  236. if (typeof value === STRING) {
  237. result += EQUALS + (encode ? encodeQueryParam(value) : normalizeParam(value));
  238. }
  239. return result;
  240. }
  241. });
  242. module.exports = {
  243. QueryParam: QueryParam
  244. };