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