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.

313 lines
6.3 KiB

  1. /**
  2. * Module dependencies.
  3. */
  4. var mime = require('connect').mime
  5. , crc32 = require('buffer-crc32');
  6. /**
  7. * toString ref.
  8. */
  9. var toString = {}.toString;
  10. /**
  11. * Return ETag for `body`.
  12. *
  13. * @param {String|Buffer} body
  14. * @return {String}
  15. * @api private
  16. */
  17. exports.etag = function(body){
  18. return '"' + crc32.signed(body) + '"';
  19. };
  20. /**
  21. * Make `locals()` bound to the given `obj`.
  22. *
  23. * This is used for `app.locals` and `res.locals`.
  24. *
  25. * @param {Object} obj
  26. * @return {Function}
  27. * @api private
  28. */
  29. exports.locals = function(obj){
  30. function locals(obj){
  31. for (var key in obj) locals[key] = obj[key];
  32. return obj;
  33. };
  34. return locals;
  35. };
  36. /**
  37. * Check if `path` looks absolute.
  38. *
  39. * @param {String} path
  40. * @return {Boolean}
  41. * @api private
  42. */
  43. exports.isAbsolute = function(path){
  44. if ('/' == path[0]) return true;
  45. if (':' == path[1] && '\\' == path[2]) return true;
  46. };
  47. /**
  48. * Flatten the given `arr`.
  49. *
  50. * @param {Array} arr
  51. * @return {Array}
  52. * @api private
  53. */
  54. exports.flatten = function(arr, ret){
  55. var ret = ret || []
  56. , len = arr.length;
  57. for (var i = 0; i < len; ++i) {
  58. if (Array.isArray(arr[i])) {
  59. exports.flatten(arr[i], ret);
  60. } else {
  61. ret.push(arr[i]);
  62. }
  63. }
  64. return ret;
  65. };
  66. /**
  67. * Normalize the given `type`, for example "html" becomes "text/html".
  68. *
  69. * @param {String} type
  70. * @return {Object}
  71. * @api private
  72. */
  73. exports.normalizeType = function(type){
  74. return ~type.indexOf('/')
  75. ? acceptParams(type)
  76. : { value: mime.lookup(type), params: {} };
  77. };
  78. /**
  79. * Normalize `types`, for example "html" becomes "text/html".
  80. *
  81. * @param {Array} types
  82. * @return {Array}
  83. * @api private
  84. */
  85. exports.normalizeTypes = function(types){
  86. var ret = [];
  87. for (var i = 0; i < types.length; ++i) {
  88. ret.push(exports.normalizeType(types[i]));
  89. }
  90. return ret;
  91. };
  92. /**
  93. * Return the acceptable type in `types`, if any.
  94. *
  95. * @param {Array} types
  96. * @param {String} str
  97. * @return {String}
  98. * @api private
  99. */
  100. exports.acceptsArray = function(types, str){
  101. // accept anything when Accept is not present
  102. if (!str) return types[0];
  103. // parse
  104. var accepted = exports.parseAccept(str)
  105. , normalized = exports.normalizeTypes(types)
  106. , len = accepted.length;
  107. for (var i = 0; i < len; ++i) {
  108. for (var j = 0, jlen = types.length; j < jlen; ++j) {
  109. if (exports.accept(normalized[j], accepted[i])) {
  110. return types[j];
  111. }
  112. }
  113. }
  114. };
  115. /**
  116. * Check if `type(s)` are acceptable based on
  117. * the given `str`.
  118. *
  119. * @param {String|Array} type(s)
  120. * @param {String} str
  121. * @return {Boolean|String}
  122. * @api private
  123. */
  124. exports.accepts = function(type, str){
  125. if ('string' == typeof type) type = type.split(/ *, */);
  126. return exports.acceptsArray(type, str);
  127. };
  128. /**
  129. * Check if `type` array is acceptable for `other`.
  130. *
  131. * @param {Object} type
  132. * @param {Object} other
  133. * @return {Boolean}
  134. * @api private
  135. */
  136. exports.accept = function(type, other){
  137. var t = type.value.split('/');
  138. return (t[0] == other.type || '*' == other.type)
  139. && (t[1] == other.subtype || '*' == other.subtype)
  140. && paramsEqual(type.params, other.params);
  141. };
  142. /**
  143. * Check if accept params are equal.
  144. *
  145. * @param {Object} a
  146. * @param {Object} b
  147. * @return {Boolean}
  148. * @api private
  149. */
  150. function paramsEqual(a, b){
  151. return !Object.keys(a).some(function(k) {
  152. return a[k] != b[k];
  153. });
  154. }
  155. /**
  156. * Parse accept `str`, returning
  157. * an array objects containing
  158. * `.type` and `.subtype` along
  159. * with the values provided by
  160. * `parseQuality()`.
  161. *
  162. * @param {Type} name
  163. * @return {Type}
  164. * @api private
  165. */
  166. exports.parseAccept = function(str){
  167. return exports
  168. .parseParams(str)
  169. .map(function(obj){
  170. var parts = obj.value.split('/');
  171. obj.type = parts[0];
  172. obj.subtype = parts[1];
  173. return obj;
  174. });
  175. };
  176. /**
  177. * Parse quality `str`, returning an
  178. * array of objects with `.value`,
  179. * `.quality` and optional `.params`
  180. *
  181. * @param {String} str
  182. * @return {Array}
  183. * @api private
  184. */
  185. exports.parseParams = function(str){
  186. return str
  187. .split(/ *, */)
  188. .map(acceptParams)
  189. .filter(function(obj){
  190. return obj.quality;
  191. })
  192. .sort(function(a, b){
  193. if (a.quality === b.quality) {
  194. return a.originalIndex - b.originalIndex;
  195. } else {
  196. return b.quality - a.quality;
  197. }
  198. });
  199. };
  200. /**
  201. * Parse accept params `str` returning an
  202. * object with `.value`, `.quality` and `.params`.
  203. * also includes `.originalIndex` for stable sorting
  204. *
  205. * @param {String} str
  206. * @return {Object}
  207. * @api private
  208. */
  209. function acceptParams(str, index) {
  210. var parts = str.split(/ *; */);
  211. var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index };
  212. for (var i = 1; i < parts.length; ++i) {
  213. var pms = parts[i].split(/ *= */);
  214. if ('q' == pms[0]) {
  215. ret.quality = parseFloat(pms[1]);
  216. } else {
  217. ret.params[pms[0]] = pms[1];
  218. }
  219. }
  220. return ret;
  221. }
  222. /**
  223. * Escape special characters in the given string of html.
  224. *
  225. * @param {String} html
  226. * @return {String}
  227. * @api private
  228. */
  229. exports.escape = function(html) {
  230. return String(html)
  231. .replace(/&/g, '&amp;')
  232. .replace(/"/g, '&quot;')
  233. .replace(/</g, '&lt;')
  234. .replace(/>/g, '&gt;');
  235. };
  236. /**
  237. * Normalize the given path string,
  238. * returning a regular expression.
  239. *
  240. * An empty array should be passed,
  241. * which will contain the placeholder
  242. * key names. For example "/user/:id" will
  243. * then contain ["id"].
  244. *
  245. * @param {String|RegExp|Array} path
  246. * @param {Array} keys
  247. * @param {Boolean} sensitive
  248. * @param {Boolean} strict
  249. * @return {RegExp}
  250. * @api private
  251. */
  252. exports.pathRegexp = function(path, keys, sensitive, strict) {
  253. if (toString.call(path) == '[object RegExp]') return path;
  254. if (Array.isArray(path)) path = '(' + path.join('|') + ')';
  255. path = path
  256. .concat(strict ? '' : '/?')
  257. .replace(/\/\(/g, '(?:/')
  258. .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
  259. keys.push({ name: key, optional: !! optional });
  260. slash = slash || '';
  261. return ''
  262. + (optional ? '' : slash)
  263. + '(?:'
  264. + (optional ? slash : '')
  265. + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
  266. + (optional || '')
  267. + (star ? '(/*)?' : '');
  268. })
  269. .replace(/([\/.])/g, '\\$1')
  270. .replace(/\*/g, '(.*)');
  271. return new RegExp('^' + path + '$', sensitive ? '' : 'i');
  272. }