utils.js 5.2 KB

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