]> git.cworth.org Git - obsolete/notmuch-web/blob - node_modules/express/node_modules/connect/lib/utils.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / node_modules / connect / lib / utils.js
1
2 /*!
3  * Connect - utils
4  * Copyright(c) 2010 Sencha Inc.
5  * Copyright(c) 2011 TJ Holowaychuk
6  * MIT Licensed
7  */
8
9 /**
10  * Module dependencies.
11  */
12
13 var crypto = require('crypto')
14   , Path = require('path')
15   , fs = require('fs');
16
17 /**
18  * Flatten the given `arr`.
19  *
20  * @param {Array} arr
21  * @return {Array}
22  * @api private
23  */
24
25 exports.flatten = function(arr, ret){
26   var ret = ret || []
27     , len = arr.length;
28   for (var i = 0; i < len; ++i) {
29     if (Array.isArray(arr[i])) {
30       exports.flatten(arr[i], ret);
31     } else {
32       ret.push(arr[i]);
33     }
34   }
35   return ret;
36 };
37
38 /**
39  * Return md5 hash of the given string and optional encoding,
40  * defaulting to hex.
41  *
42  *     utils.md5('wahoo');
43  *     // => "e493298061761236c96b02ea6aa8a2ad"
44  *
45  * @param {String} str
46  * @param {String} encoding
47  * @return {String}
48  * @api public
49  */
50
51 exports.md5 = function(str, encoding){
52   return crypto
53     .createHash('md5')
54     .update(str)
55     .digest(encoding || 'hex');
56 };
57
58 /**
59  * Merge object b with object a.
60  *
61  *     var a = { foo: 'bar' }
62  *       , b = { bar: 'baz' };
63  *     
64  *     utils.merge(a, b);
65  *     // => { foo: 'bar', bar: 'baz' }
66  *
67  * @param {Object} a
68  * @param {Object} b
69  * @return {Object}
70  * @api public
71  */
72
73 exports.merge = function(a, b){
74   if (a && b) {
75     for (var key in b) {
76       a[key] = b[key];
77     }
78   }
79   return a;
80 };
81
82 /**
83  * Escape the given string of `html`.
84  *
85  * @param {String} html
86  * @return {String}
87  * @api public
88  */
89
90 exports.escape = function(html){
91   return String(html)
92     .replace(/&(?!\w+;)/g, '&amp;')
93     .replace(/</g, '&lt;')
94     .replace(/>/g, '&gt;')
95     .replace(/"/g, '&quot;');
96 };
97
98
99 /**
100  * Return a unique identifier with the given `len`.
101  *
102  *     utils.uid(10);
103  *     // => "FDaS435D2z"
104  *
105  * @param {Number} len
106  * @return {String}
107  * @api public
108  */
109
110 exports.uid = function(len) {
111   var buf = []
112     , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
113     , charlen = chars.length;
114
115   for (var i = 0; i < len; ++i) {
116     buf.push(chars[getRandomInt(0, charlen - 1)]);
117   }
118
119   return buf.join('');
120 };
121
122 /**
123  * Parse the given cookie string into an object.
124  *
125  * @param {String} str
126  * @return {Object}
127  * @api public
128  */
129
130 exports.parseCookie = function(str){
131   var obj = {}
132     , pairs = str.split(/[;,] */);
133   for (var i = 0, len = pairs.length; i < len; ++i) {
134     var pair = pairs[i]
135       , eqlIndex = pair.indexOf('=')
136       , key = pair.substr(0, eqlIndex).trim().toLowerCase()
137       , val = pair.substr(++eqlIndex, pair.length).trim();
138
139     // Quoted values
140     if (val[0] === '"') {
141       val = val.slice(1, -1);
142     }
143
144     // Only assign once
145     if (obj[key] === undefined) {
146       obj[key] = decodeURIComponent(val.replace(/\+/g, ' '));
147     }
148   }
149   return obj;
150 };
151
152 /**
153  * Serialize the given object into a cookie string.
154  *
155  *      utils.serializeCookie('name', 'tj', { httpOnly: true })
156  *      // => "name=tj; httpOnly"
157  *
158  * @param {String} name
159  * @param {String} val
160  * @param {Object} obj
161  * @return {String}
162  * @api public
163  */
164
165 exports.serializeCookie = function(name, val, obj){
166   var pairs = [name + '=' + encodeURIComponent(val)]
167     , obj = obj || {};
168
169   if (obj.domain) pairs.push('domain=' + obj.domain);
170   if (obj.path) pairs.push('path=' + obj.path);
171   if (obj.expires) pairs.push('expires=' + obj.expires.toUTCString());
172   if (obj.httpOnly) pairs.push('httpOnly');
173   if (obj.secure) pairs.push('secure');
174
175   return pairs.join('; ');
176 };
177
178 /**
179  * Pause `data` and `end` events on the given `obj`.
180  * Middleware performing async tasks _should_ utilize
181  * this utility (or similar), to re-emit data once
182  * the async operation has completed, otherwise these
183  * events may be lost.
184  *
185  *      var pause = utils.pause(req);
186  *      fs.readFile(path, function(){
187  *         next();
188  *         pause.resume();
189  *      });
190  *
191  * @param {Object} obj
192  * @return {Object}
193  * @api public
194  */
195
196 exports.pause = function(obj){
197   var onData
198     , onEnd
199     , events = [];
200
201   // buffer data
202   obj.on('data', onData = function(data, encoding){
203     events.push(['data', data, encoding]);
204   });
205
206   // buffer end
207   obj.on('end', onEnd = function(data, encoding){
208     events.push(['end', data, encoding]);
209   });
210
211   return {
212     end: function(){
213       obj.removeListener('data', onData);
214       obj.removeListener('end', onEnd);
215     },
216     resume: function(){
217       this.end();
218       for (var i = 0, len = events.length; i < len; ++i) {
219         obj.emit.apply(obj, events[i]);
220       }
221     }
222   };
223 };
224
225 /**
226  * Check `req` and `res` to see if it has been modified.
227  *
228  * @param {IncomingMessage} req
229  * @param {ServerResponse} res
230  * @return {Boolean}
231  * @api public
232  */
233
234 exports.modified = function(req, res, headers) {
235   var headers = headers || res._headers || {}
236     , modifiedSince = req.headers['if-modified-since']
237     , lastModified = headers['last-modified']
238     , noneMatch = req.headers['if-none-match']
239     , etag = headers['etag'];
240
241   if (noneMatch) noneMatch = noneMatch.split(/ *, */);
242
243   // check If-None-Match
244   if (noneMatch && etag && ~noneMatch.indexOf(etag)) {
245     return false;
246   }
247
248   // check If-Modified-Since
249   if (modifiedSince && lastModified) {
250     modifiedSince = new Date(modifiedSince);
251     lastModified = new Date(lastModified);
252     // Ignore invalid dates
253     if (!isNaN(modifiedSince.getTime())) {
254       if (lastModified <= modifiedSince) return false;
255     }
256   }
257   
258   return true;
259 };
260
261 /**
262  * Strip `Content-*` headers from `res`.
263  *
264  * @param {ServerResponse} res
265  * @api public
266  */
267
268 exports.removeContentHeaders = function(res){
269   Object.keys(res._headers).forEach(function(field){
270     if (0 == field.indexOf('content')) {
271       res.removeHeader(field);
272     }
273   });
274 };
275
276 /**
277  * Check if `req` is a conditional GET request.
278  *
279  * @param {IncomingMessage} req
280  * @return {Boolean}
281  * @api public
282  */
283
284 exports.conditionalGET = function(req) {
285   return req.headers['if-modified-since']
286     || req.headers['if-none-match'];
287 };
288
289 /**
290  * Respond with 412 "Unauthorized".
291  *
292  * @param {ServerResponse} res
293  * @param {String} realm
294  * @api public
295  */
296
297 exports.unauthorized = function(res, realm) {
298   res.statusCode = 401;
299   res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
300   res.end('Unauthorized');
301 };
302
303 /**
304  * Respond with 400 "Bad Request".
305  *
306  * @param {ServerResponse} res
307  * @api public
308  */
309
310 exports.badRequest = function(res) {
311   res.statusCode = 400;
312   res.end('Bad Request');
313 };
314
315 /**
316  * Respond with 304 "Not Modified".
317  *
318  * @param {ServerResponse} res
319  * @param {Object} headers
320  * @api public
321  */
322
323 exports.notModified = function(res) {
324   exports.removeContentHeaders(res);
325   res.statusCode = 304;
326   res.end();
327 };
328
329 /**
330  * Return an ETag in the form of `"<size>-<mtime>"`
331  * from the given `stat`.
332  *
333  * @param {Object} stat
334  * @return {String}
335  * @api public
336  */
337
338 exports.etag = function(stat) {
339   return '"' + stat.size + '-' + Number(stat.mtime) + '"';
340 };
341
342 /**
343  * Parse "Range" header `str` relative to the given file `size`.
344  *
345  * @param {Number} size
346  * @param {String} str
347  * @return {Array}
348  * @api public
349  */
350
351 exports.parseRange = function(size, str){
352   var valid = true;
353   var arr = str.substr(6).split(',').map(function(range){
354     var range = range.split('-')
355       , start = parseInt(range[0], 10)
356       , end = parseInt(range[1], 10);
357
358     // -500
359     if (isNaN(start)) {
360       start = size - end;
361       end = size - 1;
362     // 500-
363     } else if (isNaN(end)) {
364       end = size - 1;
365     }
366
367     // Invalid
368     if (isNaN(start) || isNaN(end) || start > end) valid = false;
369
370     return { start: start, end: end };
371   });
372   return valid ? arr : undefined;
373 };
374
375 /**
376  * Convert array-like object to an `Array`.
377  *
378  * node-bench measured "16.5 times faster than Array.prototype.slice.call()"
379  *
380  * @param {Object} obj
381  * @return {Array}
382  * @api public
383  */
384
385 var toArray = exports.toArray = function(obj){
386   var len = obj.length
387     , arr = new Array(len);
388   for (var i = 0; i < len; ++i) {
389     arr[i] = obj[i];
390   }
391   return arr;
392 };
393
394 /**
395  * Retrun a random int, used by `utils.uid()`
396  *
397  * @param {Number} min
398  * @param {Number} max
399  * @return {Number}
400  * @api private
401  */
402
403 function getRandomInt(min, max) {
404   return Math.floor(Math.random() * (max - min + 1)) + min;
405 }