utils.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*!
  2. * express
  3. * Copyright(c) 2009-2013 TJ Holowaychuk
  4. * Copyright(c) 2014-2015 Douglas Christopher Wilson
  5. * MIT Licensed
  6. */
  7. 'use strict';
  8. /**
  9. * Module dependencies.
  10. * @api private
  11. */
  12. var { METHODS } = require('node:http');
  13. var contentType = require('content-type');
  14. var etag = require('etag');
  15. var mime = require('mime-types')
  16. var proxyaddr = require('proxy-addr');
  17. var qs = require('qs');
  18. var querystring = require('querystring');
  19. /**
  20. * A list of lowercased HTTP methods that are supported by Node.js.
  21. * @api private
  22. */
  23. exports.methods = METHODS.map((method) => method.toLowerCase());
  24. /**
  25. * Return strong ETag for `body`.
  26. *
  27. * @param {String|Buffer} body
  28. * @param {String} [encoding]
  29. * @return {String}
  30. * @api private
  31. */
  32. exports.etag = createETagGenerator({ weak: false })
  33. /**
  34. * Return weak ETag for `body`.
  35. *
  36. * @param {String|Buffer} body
  37. * @param {String} [encoding]
  38. * @return {String}
  39. * @api private
  40. */
  41. exports.wetag = createETagGenerator({ weak: true })
  42. /**
  43. * Normalize the given `type`, for example "html" becomes "text/html".
  44. *
  45. * @param {String} type
  46. * @return {Object}
  47. * @api private
  48. */
  49. exports.normalizeType = function(type){
  50. return ~type.indexOf('/')
  51. ? acceptParams(type)
  52. : { value: (mime.lookup(type) || 'application/octet-stream'), params: {} }
  53. };
  54. /**
  55. * Normalize `types`, for example "html" becomes "text/html".
  56. *
  57. * @param {Array} types
  58. * @return {Array}
  59. * @api private
  60. */
  61. exports.normalizeTypes = function(types) {
  62. return types.map(exports.normalizeType);
  63. };
  64. /**
  65. * Parse accept params `str` returning an
  66. * object with `.value`, `.quality` and `.params`.
  67. *
  68. * @param {String} str
  69. * @return {Object}
  70. * @api private
  71. */
  72. function acceptParams (str) {
  73. var length = str.length;
  74. var colonIndex = str.indexOf(';');
  75. var index = colonIndex === -1 ? length : colonIndex;
  76. var ret = { value: str.slice(0, index).trim(), quality: 1, params: {} };
  77. while (index < length) {
  78. var splitIndex = str.indexOf('=', index);
  79. if (splitIndex === -1) break;
  80. var colonIndex = str.indexOf(';', index);
  81. var endIndex = colonIndex === -1 ? length : colonIndex;
  82. if (splitIndex > endIndex) {
  83. index = str.lastIndexOf(';', splitIndex - 1) + 1;
  84. continue;
  85. }
  86. var key = str.slice(index, splitIndex).trim();
  87. var value = str.slice(splitIndex + 1, endIndex).trim();
  88. if (key === 'q') {
  89. ret.quality = parseFloat(value);
  90. } else {
  91. ret.params[key] = value;
  92. }
  93. index = endIndex + 1;
  94. }
  95. return ret;
  96. }
  97. /**
  98. * Compile "etag" value to function.
  99. *
  100. * @param {Boolean|String|Function} val
  101. * @return {Function}
  102. * @api private
  103. */
  104. exports.compileETag = function(val) {
  105. var fn;
  106. if (typeof val === 'function') {
  107. return val;
  108. }
  109. switch (val) {
  110. case true:
  111. case 'weak':
  112. fn = exports.wetag;
  113. break;
  114. case false:
  115. break;
  116. case 'strong':
  117. fn = exports.etag;
  118. break;
  119. default:
  120. throw new TypeError('unknown value for etag function: ' + val);
  121. }
  122. return fn;
  123. }
  124. /**
  125. * Compile "query parser" value to function.
  126. *
  127. * @param {String|Function} val
  128. * @return {Function}
  129. * @api private
  130. */
  131. exports.compileQueryParser = function compileQueryParser(val) {
  132. var fn;
  133. if (typeof val === 'function') {
  134. return val;
  135. }
  136. switch (val) {
  137. case true:
  138. case 'simple':
  139. fn = querystring.parse;
  140. break;
  141. case false:
  142. break;
  143. case 'extended':
  144. fn = parseExtendedQueryString;
  145. break;
  146. default:
  147. throw new TypeError('unknown value for query parser function: ' + val);
  148. }
  149. return fn;
  150. }
  151. /**
  152. * Compile "proxy trust" value to function.
  153. *
  154. * @param {Boolean|String|Number|Array|Function} val
  155. * @return {Function}
  156. * @api private
  157. */
  158. exports.compileTrust = function(val) {
  159. if (typeof val === 'function') return val;
  160. if (val === true) {
  161. // Support plain true/false
  162. return function(){ return true };
  163. }
  164. if (typeof val === 'number') {
  165. // Support trusting hop count
  166. return function(a, i){ return i < val };
  167. }
  168. if (typeof val === 'string') {
  169. // Support comma-separated values
  170. val = val.split(',')
  171. .map(function (v) { return v.trim() })
  172. }
  173. return proxyaddr.compile(val || []);
  174. }
  175. /**
  176. * Set the charset in a given Content-Type string.
  177. *
  178. * @param {String} type
  179. * @param {String} charset
  180. * @return {String}
  181. * @api private
  182. */
  183. exports.setCharset = function setCharset(type, charset) {
  184. if (!type || !charset) {
  185. return type;
  186. }
  187. // parse type
  188. var parsed = contentType.parse(type);
  189. // set charset
  190. parsed.parameters.charset = charset;
  191. // format type
  192. return contentType.format(parsed);
  193. };
  194. /**
  195. * Create an ETag generator function, generating ETags with
  196. * the given options.
  197. *
  198. * @param {object} options
  199. * @return {function}
  200. * @private
  201. */
  202. function createETagGenerator (options) {
  203. return function generateETag (body, encoding) {
  204. var buf = !Buffer.isBuffer(body)
  205. ? Buffer.from(body, encoding)
  206. : body
  207. return etag(buf, options)
  208. }
  209. }
  210. /**
  211. * Parse an extended query string with qs.
  212. *
  213. * @param {String} str
  214. * @return {Object}
  215. * @private
  216. */
  217. function parseExtendedQueryString(str) {
  218. return qs.parse(str, {
  219. allowPrototypes: true
  220. });
  221. }