utils.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. /**
  2. * Module dependencies.
  3. */
  4. var mime = require('send').mime;
  5. var crc32 = require('buffer-crc32');
  6. var crypto = require('crypto');
  7. var basename = require('path').basename;
  8. var deprecate = require('util').deprecate;
  9. var proxyaddr = require('proxy-addr');
  10. /**
  11. * Simple detection of charset parameter in content-type
  12. */
  13. var charsetRegExp = /;\s*charset\s*=/;
  14. /**
  15. * Deprecate function, like core `util.deprecate`,
  16. * but with NODE_ENV and color support.
  17. *
  18. * @param {Function} fn
  19. * @param {String} msg
  20. * @return {Function}
  21. * @api private
  22. */
  23. exports.deprecate = function(fn, msg){
  24. if (process.env.NODE_ENV === 'test') return fn;
  25. // prepend module name
  26. msg = 'express: ' + msg;
  27. if (process.stderr.isTTY) {
  28. // colorize
  29. msg = '\x1b[31;1m' + msg + '\x1b[0m';
  30. }
  31. return deprecate(fn, msg);
  32. };
  33. /**
  34. * Return strong ETag for `body`.
  35. *
  36. * @param {String|Buffer} body
  37. * @param {String} [encoding]
  38. * @return {String}
  39. * @api private
  40. */
  41. exports.etag = function etag(body, encoding){
  42. if (body.length === 0) {
  43. // fast-path empty body
  44. return '"1B2M2Y8AsgTpgAmY7PhCfg=="'
  45. }
  46. var hash = crypto
  47. .createHash('md5')
  48. .update(body, encoding)
  49. .digest('base64')
  50. return '"' + hash + '"'
  51. };
  52. /**
  53. * Return weak ETag for `body`.
  54. *
  55. * @param {String|Buffer} body
  56. * @param {String} [encoding]
  57. * @return {String}
  58. * @api private
  59. */
  60. exports.wetag = function wetag(body, encoding){
  61. if (body.length === 0) {
  62. // fast-path empty body
  63. return 'W/"0-0"'
  64. }
  65. var buf = Buffer.isBuffer(body)
  66. ? body
  67. : new Buffer(body, encoding)
  68. var len = buf.length
  69. return 'W/"' + len.toString(16) + '-' + crc32.unsigned(buf) + '"'
  70. };
  71. /**
  72. * Check if `path` looks absolute.
  73. *
  74. * @param {String} path
  75. * @return {Boolean}
  76. * @api private
  77. */
  78. exports.isAbsolute = function(path){
  79. if ('/' == path[0]) return true;
  80. if (':' == path[1] && '\\' == path[2]) return true;
  81. if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path
  82. };
  83. /**
  84. * Flatten the given `arr`.
  85. *
  86. * @param {Array} arr
  87. * @return {Array}
  88. * @api private
  89. */
  90. exports.flatten = function(arr, ret){
  91. ret = ret || [];
  92. var len = arr.length;
  93. for (var i = 0; i < len; ++i) {
  94. if (Array.isArray(arr[i])) {
  95. exports.flatten(arr[i], ret);
  96. } else {
  97. ret.push(arr[i]);
  98. }
  99. }
  100. return ret;
  101. };
  102. /**
  103. * Normalize the given `type`, for example "html" becomes "text/html".
  104. *
  105. * @param {String} type
  106. * @return {Object}
  107. * @api private
  108. */
  109. exports.normalizeType = function(type){
  110. return ~type.indexOf('/')
  111. ? acceptParams(type)
  112. : { value: mime.lookup(type), params: {} };
  113. };
  114. /**
  115. * Normalize `types`, for example "html" becomes "text/html".
  116. *
  117. * @param {Array} types
  118. * @return {Array}
  119. * @api private
  120. */
  121. exports.normalizeTypes = function(types){
  122. var ret = [];
  123. for (var i = 0; i < types.length; ++i) {
  124. ret.push(exports.normalizeType(types[i]));
  125. }
  126. return ret;
  127. };
  128. /**
  129. * Generate Content-Disposition header appropriate for the filename.
  130. * non-ascii filenames are urlencoded and a filename* parameter is added
  131. *
  132. * @param {String} filename
  133. * @return {String}
  134. * @api private
  135. */
  136. exports.contentDisposition = function(filename){
  137. var ret = 'attachment';
  138. if (filename) {
  139. filename = basename(filename);
  140. // if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
  141. ret = /[^\040-\176]/.test(filename)
  142. ? 'attachment; filename="' + encodeURI(filename) + '"; filename*=UTF-8\'\'' + encodeURI(filename)
  143. : 'attachment; filename="' + filename + '"';
  144. }
  145. return ret;
  146. };
  147. /**
  148. * Parse accept params `str` returning an
  149. * object with `.value`, `.quality` and `.params`.
  150. * also includes `.originalIndex` for stable sorting
  151. *
  152. * @param {String} str
  153. * @return {Object}
  154. * @api private
  155. */
  156. function acceptParams(str, index) {
  157. var parts = str.split(/ *; */);
  158. var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index };
  159. for (var i = 1; i < parts.length; ++i) {
  160. var pms = parts[i].split(/ *= */);
  161. if ('q' == pms[0]) {
  162. ret.quality = parseFloat(pms[1]);
  163. } else {
  164. ret.params[pms[0]] = pms[1];
  165. }
  166. }
  167. return ret;
  168. }
  169. /**
  170. * Compile "etag" value to function.
  171. *
  172. * @param {Boolean|String|Function} val
  173. * @return {Function}
  174. * @api private
  175. */
  176. exports.compileETag = function(val) {
  177. var fn;
  178. if (typeof val === 'function') {
  179. return val;
  180. }
  181. switch (val) {
  182. case true:
  183. fn = exports.wetag;
  184. break;
  185. case false:
  186. break;
  187. case 'strong':
  188. fn = exports.etag;
  189. break;
  190. case 'weak':
  191. fn = exports.wetag;
  192. break;
  193. default:
  194. throw new TypeError('unknown value for etag function: ' + val);
  195. }
  196. return fn;
  197. }
  198. /**
  199. * Compile "proxy trust" value to function.
  200. *
  201. * @param {Boolean|String|Number|Array|Function} val
  202. * @return {Function}
  203. * @api private
  204. */
  205. exports.compileTrust = function(val) {
  206. if (typeof val === 'function') return val;
  207. if (val === true) {
  208. // Support plain true/false
  209. return function(){ return true };
  210. }
  211. if (typeof val === 'number') {
  212. // Support trusting hop count
  213. return function(a, i){ return i < val };
  214. }
  215. if (typeof val === 'string') {
  216. // Support comma-separated values
  217. val = val.split(/ *, */);
  218. }
  219. return proxyaddr.compile(val || []);
  220. }
  221. /**
  222. * Set the charset in a given Content-Type string.
  223. *
  224. * @param {String} type
  225. * @param {String} charset
  226. * @return {String}
  227. * @api private
  228. */
  229. exports.setCharset = function(type, charset){
  230. if (!type || !charset) return type;
  231. var exists = charsetRegExp.test(type);
  232. // removing existing charset
  233. if (exists) {
  234. var parts = type.split(';');
  235. for (var i = 1; i < parts.length; i++) {
  236. if (charsetRegExp.test(';' + parts[i])) {
  237. parts.splice(i, 1);
  238. break;
  239. }
  240. }
  241. type = parts.join(';');
  242. }
  243. return type + '; charset=' + charset;
  244. };