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.

299 lines
5.7 KiB

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