You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
5.8 KiB

4 years ago
  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 Buffer = require('safe-buffer').Buffer
  13. var contentDisposition = require('content-disposition');
  14. var contentType = require('content-type');
  15. var deprecate = require('depd')('express');
  16. var flatten = require('array-flatten');
  17. var mime = require('send').mime;
  18. var etag = require('etag');
  19. var proxyaddr = require('proxy-addr');
  20. var qs = require('qs');
  21. var querystring = require('querystring');
  22. /**
  23. * Return strong ETag for `body`.
  24. *
  25. * @param {String|Buffer} body
  26. * @param {String} [encoding]
  27. * @return {String}
  28. * @api private
  29. */
  30. exports.etag = createETagGenerator({ weak: false })
  31. /**
  32. * Return weak ETag for `body`.
  33. *
  34. * @param {String|Buffer} body
  35. * @param {String} [encoding]
  36. * @return {String}
  37. * @api private
  38. */
  39. exports.wetag = createETagGenerator({ weak: true })
  40. /**
  41. * Check if `path` looks absolute.
  42. *
  43. * @param {String} path
  44. * @return {Boolean}
  45. * @api private
  46. */
  47. exports.isAbsolute = function(path){
  48. if ('/' === path[0]) return true;
  49. if (':' === path[1] && ('\\' === path[2] || '/' === path[2])) return true; // Windows device path
  50. if ('\\\\' === path.substring(0, 2)) return true; // Microsoft Azure absolute path
  51. };
  52. /**
  53. * Flatten the given `arr`.
  54. *
  55. * @param {Array} arr
  56. * @return {Array}
  57. * @api private
  58. */
  59. exports.flatten = deprecate.function(flatten,
  60. 'utils.flatten: use array-flatten npm module instead');
  61. /**
  62. * Normalize the given `type`, for example "html" becomes "text/html".
  63. *
  64. * @param {String} type
  65. * @return {Object}
  66. * @api private
  67. */
  68. exports.normalizeType = function(type){
  69. return ~type.indexOf('/')
  70. ? acceptParams(type)
  71. : { value: mime.lookup(type), params: {} };
  72. };
  73. /**
  74. * Normalize `types`, for example "html" becomes "text/html".
  75. *
  76. * @param {Array} types
  77. * @return {Array}
  78. * @api private
  79. */
  80. exports.normalizeTypes = function(types){
  81. var ret = [];
  82. for (var i = 0; i < types.length; ++i) {
  83. ret.push(exports.normalizeType(types[i]));
  84. }
  85. return ret;
  86. };
  87. /**
  88. * Generate Content-Disposition header appropriate for the filename.
  89. * non-ascii filenames are urlencoded and a filename* parameter is added
  90. *
  91. * @param {String} filename
  92. * @return {String}
  93. * @api private
  94. */
  95. exports.contentDisposition = deprecate.function(contentDisposition,
  96. 'utils.contentDisposition: use content-disposition npm module instead');
  97. /**
  98. * Parse accept params `str` returning an
  99. * object with `.value`, `.quality` and `.params`.
  100. * also includes `.originalIndex` for stable sorting
  101. *
  102. * @param {String} str
  103. * @return {Object}
  104. * @api private
  105. */
  106. function acceptParams(str, index) {
  107. var parts = str.split(/ *; */);
  108. var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index };
  109. for (var i = 1; i < parts.length; ++i) {
  110. var pms = parts[i].split(/ *= */);
  111. if ('q' === pms[0]) {
  112. ret.quality = parseFloat(pms[1]);
  113. } else {
  114. ret.params[pms[0]] = pms[1];
  115. }
  116. }
  117. return ret;
  118. }
  119. /**
  120. * Compile "etag" value to function.
  121. *
  122. * @param {Boolean|String|Function} val
  123. * @return {Function}
  124. * @api private
  125. */
  126. exports.compileETag = function(val) {
  127. var fn;
  128. if (typeof val === 'function') {
  129. return val;
  130. }
  131. switch (val) {
  132. case true:
  133. fn = exports.wetag;
  134. break;
  135. case false:
  136. break;
  137. case 'strong':
  138. fn = exports.etag;
  139. break;
  140. case 'weak':
  141. fn = exports.wetag;
  142. break;
  143. default:
  144. throw new TypeError('unknown value for etag function: ' + val);
  145. }
  146. return fn;
  147. }
  148. /**
  149. * Compile "query parser" value to function.
  150. *
  151. * @param {String|Function} val
  152. * @return {Function}
  153. * @api private
  154. */
  155. exports.compileQueryParser = function compileQueryParser(val) {
  156. var fn;
  157. if (typeof val === 'function') {
  158. return val;
  159. }
  160. switch (val) {
  161. case true:
  162. fn = querystring.parse;
  163. break;
  164. case false:
  165. fn = newObject;
  166. break;
  167. case 'extended':
  168. fn = parseExtendedQueryString;
  169. break;
  170. case 'simple':
  171. fn = querystring.parse;
  172. break;
  173. default:
  174. throw new TypeError('unknown value for query parser function: ' + val);
  175. }
  176. return fn;
  177. }
  178. /**
  179. * Compile "proxy trust" value to function.
  180. *
  181. * @param {Boolean|String|Number|Array|Function} val
  182. * @return {Function}
  183. * @api private
  184. */
  185. exports.compileTrust = function(val) {
  186. if (typeof val === 'function') return val;
  187. if (val === true) {
  188. // Support plain true/false
  189. return function(){ return true };
  190. }
  191. if (typeof val === 'number') {
  192. // Support trusting hop count
  193. return function(a, i){ return i < val };
  194. }
  195. if (typeof val === 'string') {
  196. // Support comma-separated values
  197. val = val.split(/ *, */);
  198. }
  199. return proxyaddr.compile(val || []);
  200. }
  201. /**
  202. * Set the charset in a given Content-Type string.
  203. *
  204. * @param {String} type
  205. * @param {String} charset
  206. * @return {String}
  207. * @api private
  208. */
  209. exports.setCharset = function setCharset(type, charset) {
  210. if (!type || !charset) {
  211. return type;
  212. }
  213. // parse type
  214. var parsed = contentType.parse(type);
  215. // set charset
  216. parsed.parameters.charset = charset;
  217. // format type
  218. return contentType.format(parsed);
  219. };
  220. /**
  221. * Create an ETag generator function, generating ETags with
  222. * the given options.
  223. *
  224. * @param {object} options
  225. * @return {function}
  226. * @private
  227. */
  228. function createETagGenerator (options) {
  229. return function generateETag (body, encoding) {
  230. var buf = !Buffer.isBuffer(body)
  231. ? Buffer.from(body, encoding)
  232. : body
  233. return etag(buf, options)
  234. }
  235. }
  236. /**
  237. * Parse an extended query string with qs.
  238. *
  239. * @return {Object}
  240. * @private
  241. */
  242. function parseExtendedQueryString(str) {
  243. return qs.parse(str, {
  244. allowPrototypes: true
  245. });
  246. }
  247. /**
  248. * Return new empty object.
  249. *
  250. * @return {Object}
  251. * @api private
  252. */
  253. function newObject() {
  254. return {};
  255. }