utils.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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. *
  101. * @param {String} str
  102. * @return {Object}
  103. * @api private
  104. */
  105. function acceptParams (str) {
  106. var parts = str.split(/ *; */);
  107. var ret = { value: parts[0], quality: 1, params: {} }
  108. for (var i = 1; i < parts.length; ++i) {
  109. var pms = parts[i].split(/ *= */);
  110. if ('q' === pms[0]) {
  111. ret.quality = parseFloat(pms[1]);
  112. } else {
  113. ret.params[pms[0]] = pms[1];
  114. }
  115. }
  116. return ret;
  117. }
  118. /**
  119. * Compile "etag" value to function.
  120. *
  121. * @param {Boolean|String|Function} val
  122. * @return {Function}
  123. * @api private
  124. */
  125. exports.compileETag = function(val) {
  126. var fn;
  127. if (typeof val === 'function') {
  128. return val;
  129. }
  130. switch (val) {
  131. case true:
  132. case 'weak':
  133. fn = exports.wetag;
  134. break;
  135. case false:
  136. break;
  137. case 'strong':
  138. fn = exports.etag;
  139. break;
  140. default:
  141. throw new TypeError('unknown value for etag function: ' + val);
  142. }
  143. return fn;
  144. }
  145. /**
  146. * Compile "query parser" value to function.
  147. *
  148. * @param {String|Function} val
  149. * @return {Function}
  150. * @api private
  151. */
  152. exports.compileQueryParser = function compileQueryParser(val) {
  153. var fn;
  154. if (typeof val === 'function') {
  155. return val;
  156. }
  157. switch (val) {
  158. case true:
  159. case 'simple':
  160. fn = querystring.parse;
  161. break;
  162. case false:
  163. fn = newObject;
  164. break;
  165. case 'extended':
  166. fn = parseExtendedQueryString;
  167. break;
  168. default:
  169. throw new TypeError('unknown value for query parser function: ' + val);
  170. }
  171. return fn;
  172. }
  173. /**
  174. * Compile "proxy trust" value to function.
  175. *
  176. * @param {Boolean|String|Number|Array|Function} val
  177. * @return {Function}
  178. * @api private
  179. */
  180. exports.compileTrust = function(val) {
  181. if (typeof val === 'function') return val;
  182. if (val === true) {
  183. // Support plain true/false
  184. return function(){ return true };
  185. }
  186. if (typeof val === 'number') {
  187. // Support trusting hop count
  188. return function(a, i){ return i < val };
  189. }
  190. if (typeof val === 'string') {
  191. // Support comma-separated values
  192. val = val.split(',')
  193. .map(function (v) { return v.trim() })
  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. * @param {String} str
  236. * @return {Object}
  237. * @private
  238. */
  239. function parseExtendedQueryString(str) {
  240. return qs.parse(str, {
  241. allowPrototypes: true
  242. });
  243. }
  244. /**
  245. * Return new empty object.
  246. *
  247. * @return {Object}
  248. * @api private
  249. */
  250. function newObject() {
  251. return {};
  252. }