second
[josuexyz/.git] / node_modules / express / lib / utils.js
1 /*!
2  * express
3  * Copyright(c) 2009-2013 TJ Holowaychuk
4  * Copyright(c) 2014-2015 Douglas Christopher Wilson
5  * MIT Licensed
6  */
7
8 'use strict';
9
10 /**
11  * Module dependencies.
12  * @api private
13  */
14
15 var Buffer = require('safe-buffer').Buffer
16 var contentDisposition = require('content-disposition');
17 var contentType = require('content-type');
18 var deprecate = require('depd')('express');
19 var flatten = require('array-flatten');
20 var mime = require('send').mime;
21 var etag = require('etag');
22 var proxyaddr = require('proxy-addr');
23 var qs = require('qs');
24 var querystring = require('querystring');
25
26 /**
27  * Return strong ETag for `body`.
28  *
29  * @param {String|Buffer} body
30  * @param {String} [encoding]
31  * @return {String}
32  * @api private
33  */
34
35 exports.etag = createETagGenerator({ weak: false })
36
37 /**
38  * Return weak ETag for `body`.
39  *
40  * @param {String|Buffer} body
41  * @param {String} [encoding]
42  * @return {String}
43  * @api private
44  */
45
46 exports.wetag = createETagGenerator({ weak: true })
47
48 /**
49  * Check if `path` looks absolute.
50  *
51  * @param {String} path
52  * @return {Boolean}
53  * @api private
54  */
55
56 exports.isAbsolute = function(path){
57   if ('/' === path[0]) return true;
58   if (':' === path[1] && ('\\' === path[2] || '/' === path[2])) return true; // Windows device path
59   if ('\\\\' === path.substring(0, 2)) return true; // Microsoft Azure absolute path
60 };
61
62 /**
63  * Flatten the given `arr`.
64  *
65  * @param {Array} arr
66  * @return {Array}
67  * @api private
68  */
69
70 exports.flatten = deprecate.function(flatten,
71   'utils.flatten: use array-flatten npm module instead');
72
73 /**
74  * Normalize the given `type`, for example "html" becomes "text/html".
75  *
76  * @param {String} type
77  * @return {Object}
78  * @api private
79  */
80
81 exports.normalizeType = function(type){
82   return ~type.indexOf('/')
83     ? acceptParams(type)
84     : { value: mime.lookup(type), params: {} };
85 };
86
87 /**
88  * Normalize `types`, for example "html" becomes "text/html".
89  *
90  * @param {Array} types
91  * @return {Array}
92  * @api private
93  */
94
95 exports.normalizeTypes = function(types){
96   var ret = [];
97
98   for (var i = 0; i < types.length; ++i) {
99     ret.push(exports.normalizeType(types[i]));
100   }
101
102   return ret;
103 };
104
105 /**
106  * Generate Content-Disposition header appropriate for the filename.
107  * non-ascii filenames are urlencoded and a filename* parameter is added
108  *
109  * @param {String} filename
110  * @return {String}
111  * @api private
112  */
113
114 exports.contentDisposition = deprecate.function(contentDisposition,
115   'utils.contentDisposition: use content-disposition npm module instead');
116
117 /**
118  * Parse accept params `str` returning an
119  * object with `.value`, `.quality` and `.params`.
120  * also includes `.originalIndex` for stable sorting
121  *
122  * @param {String} str
123  * @return {Object}
124  * @api private
125  */
126
127 function acceptParams(str, index) {
128   var parts = str.split(/ *; */);
129   var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index };
130
131   for (var i = 1; i < parts.length; ++i) {
132     var pms = parts[i].split(/ *= */);
133     if ('q' === pms[0]) {
134       ret.quality = parseFloat(pms[1]);
135     } else {
136       ret.params[pms[0]] = pms[1];
137     }
138   }
139
140   return ret;
141 }
142
143 /**
144  * Compile "etag" value to function.
145  *
146  * @param  {Boolean|String|Function} val
147  * @return {Function}
148  * @api private
149  */
150
151 exports.compileETag = function(val) {
152   var fn;
153
154   if (typeof val === 'function') {
155     return val;
156   }
157
158   switch (val) {
159     case true:
160       fn = exports.wetag;
161       break;
162     case false:
163       break;
164     case 'strong':
165       fn = exports.etag;
166       break;
167     case 'weak':
168       fn = exports.wetag;
169       break;
170     default:
171       throw new TypeError('unknown value for etag function: ' + val);
172   }
173
174   return fn;
175 }
176
177 /**
178  * Compile "query parser" value to function.
179  *
180  * @param  {String|Function} val
181  * @return {Function}
182  * @api private
183  */
184
185 exports.compileQueryParser = function compileQueryParser(val) {
186   var fn;
187
188   if (typeof val === 'function') {
189     return val;
190   }
191
192   switch (val) {
193     case true:
194       fn = querystring.parse;
195       break;
196     case false:
197       fn = newObject;
198       break;
199     case 'extended':
200       fn = parseExtendedQueryString;
201       break;
202     case 'simple':
203       fn = querystring.parse;
204       break;
205     default:
206       throw new TypeError('unknown value for query parser function: ' + val);
207   }
208
209   return fn;
210 }
211
212 /**
213  * Compile "proxy trust" value to function.
214  *
215  * @param  {Boolean|String|Number|Array|Function} val
216  * @return {Function}
217  * @api private
218  */
219
220 exports.compileTrust = function(val) {
221   if (typeof val === 'function') return val;
222
223   if (val === true) {
224     // Support plain true/false
225     return function(){ return true };
226   }
227
228   if (typeof val === 'number') {
229     // Support trusting hop count
230     return function(a, i){ return i < val };
231   }
232
233   if (typeof val === 'string') {
234     // Support comma-separated values
235     val = val.split(/ *, */);
236   }
237
238   return proxyaddr.compile(val || []);
239 }
240
241 /**
242  * Set the charset in a given Content-Type string.
243  *
244  * @param {String} type
245  * @param {String} charset
246  * @return {String}
247  * @api private
248  */
249
250 exports.setCharset = function setCharset(type, charset) {
251   if (!type || !charset) {
252     return type;
253   }
254
255   // parse type
256   var parsed = contentType.parse(type);
257
258   // set charset
259   parsed.parameters.charset = charset;
260
261   // format type
262   return contentType.format(parsed);
263 };
264
265 /**
266  * Create an ETag generator function, generating ETags with
267  * the given options.
268  *
269  * @param {object} options
270  * @return {function}
271  * @private
272  */
273
274 function createETagGenerator (options) {
275   return function generateETag (body, encoding) {
276     var buf = !Buffer.isBuffer(body)
277       ? Buffer.from(body, encoding)
278       : body
279
280     return etag(buf, options)
281   }
282 }
283
284 /**
285  * Parse an extended query string with qs.
286  *
287  * @return {Object}
288  * @private
289  */
290
291 function parseExtendedQueryString(str) {
292   return qs.parse(str, {
293     allowPrototypes: true
294   });
295 }
296
297 /**
298  * Return new empty object.
299  *
300  * @return {Object}
301  * @api private
302  */
303
304 function newObject() {
305   return {};
306 }