utils.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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. case 'weak':
  134. fn = exports.wetag;
  135. break;
  136. case false:
  137. break;
  138. case 'strong':
  139. fn = exports.etag;
  140. break;
  141. default:
  142. throw new TypeError('unknown value for etag function: ' + val);
  143. }
  144. return fn;
  145. }
  146. /**
  147. * Compile "query parser" value to function.
  148. *
  149. * @param {String|Function} val
  150. * @return {Function}
  151. * @api private
  152. */
  153. exports.compileQueryParser = function compileQueryParser(val) {
  154. var fn;
  155. if (typeof val === 'function') {
  156. return val;
  157. }
  158. switch (val) {
  159. case true:
  160. case 'simple':
  161. fn = querystring.parse;
  162. break;
  163. case false:
  164. fn = newObject;
  165. break;
  166. case 'extended':
  167. fn = parseExtendedQueryString;
  168. break;
  169. default:
  170. throw new TypeError('unknown value for query parser function: ' + val);
  171. }
  172. return fn;
  173. }
  174. /**
  175. * Compile "proxy trust" value to function.
  176. *
  177. * @param {Boolean|String|Number|Array|Function} val
  178. * @return {Function}
  179. * @api private
  180. */
  181. exports.compileTrust = function(val) {
  182. if (typeof val === 'function') return val;
  183. if (val === true) {
  184. // Support plain true/false
  185. return function(){ return true };
  186. }
  187. if (typeof val === 'number') {
  188. // Support trusting hop count
  189. return function(a, i){ return i < val };
  190. }
  191. if (typeof val === 'string') {
  192. // Support comma-separated values
  193. val = val.split(/ *, */);
  194. }
  195. return proxyaddr.compile(val || []);
  196. }
  197. /**
  198. * Set the charset in a given Content-Type string.
  199. *
  200. * @param {String} type
  201. * @param {String} charset
  202. * @return {String}
  203. * @api private
  204. */
  205. exports.setCharset = function setCharset(type, charset) {
  206. if (!type || !charset) {
  207. return type;
  208. }
  209. // parse type
  210. var parsed = contentType.parse(type);
  211. // set charset
  212. parsed.parameters.charset = charset;
  213. // format type
  214. return contentType.format(parsed);
  215. };
  216. /**
  217. * Create an ETag generator function, generating ETags with
  218. * the given options.
  219. *
  220. * @param {object} options
  221. * @return {function}
  222. * @private
  223. */
  224. function createETagGenerator (options) {
  225. return function generateETag (body, encoding) {
  226. var buf = !Buffer.isBuffer(body)
  227. ? Buffer.from(body, encoding)
  228. : body
  229. return etag(buf, options)
  230. }
  231. }
  232. /**
  233. * Parse an extended query string with qs.
  234. *
  235. * @return {Object}
  236. * @private
  237. */
  238. function parseExtendedQueryString(str) {
  239. return qs.parse(str, {
  240. allowPrototypes: true
  241. });
  242. }
  243. /**
  244. * Return new empty object.
  245. *
  246. * @return {Object}
  247. * @api private
  248. */
  249. function newObject() {
  250. return {};
  251. }