UNPKG

7.5 kBJavaScriptView Raw
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
13var http = require('http')
14 , crypto = require('crypto')
15 , parse = require('url').parse
16 , Path = require('path')
17 , fs = require('fs');
18
19/**
20 * Extract the mime type from the given request's
21 * _Content-Type_ header.
22 *
23 * @param {IncomingMessage} req
24 * @return {String}
25 * @api private
26 */
27
28exports.mime = function(req) {
29 var str = req.headers['content-type'] || '';
30 return str.split(';')[0];
31};
32
33/**
34 * Generate an `Error` from the given status `code`.
35 *
36 * @param {Number} code
37 * @return {Error}
38 * @api private
39 */
40
41exports.error = function(code){
42 var err = new Error(http.STATUS_CODES[code]);
43 err.status = code;
44 return err;
45};
46
47/**
48 * Return md5 hash of the given string and optional encoding,
49 * defaulting to hex.
50 *
51 * utils.md5('wahoo');
52 * // => "e493298061761236c96b02ea6aa8a2ad"
53 *
54 * @param {String} str
55 * @param {String} encoding
56 * @return {String}
57 * @api public
58 */
59
60exports.md5 = function(str, encoding){
61 return crypto
62 .createHash('md5')
63 .update(str)
64 .digest(encoding || 'hex');
65};
66
67/**
68 * Merge object b with object a.
69 *
70 * var a = { foo: 'bar' }
71 * , b = { bar: 'baz' };
72 *
73 * utils.merge(a, b);
74 * // => { foo: 'bar', bar: 'baz' }
75 *
76 * @param {Object} a
77 * @param {Object} b
78 * @return {Object}
79 * @api private
80 */
81
82exports.merge = function(a, b){
83 if (a && b) {
84 for (var key in b) {
85 a[key] = b[key];
86 }
87 }
88 return a;
89};
90
91/**
92 * Escape the given string of `html`.
93 *
94 * @param {String} html
95 * @return {String}
96 * @api private
97 */
98
99exports.escape = function(html){
100 return String(html)
101 .replace(/&(?!\w+;)/g, '&')
102 .replace(/</g, '&lt;')
103 .replace(/>/g, '&gt;')
104 .replace(/"/g, '&quot;');
105};
106
107
108/**
109 * Return a unique identifier with the given `len`.
110 *
111 * utils.uid(10);
112 * // => "FDaS435D2z"
113 *
114 * @param {Number} len
115 * @return {String}
116 * @api private
117 */
118
119exports.uid = function(len) {
120 return crypto.randomBytes(Math.ceil(len * 3 / 4))
121 .toString('base64')
122 .slice(0, len);
123};
124
125/**
126 * Sign the given `val` with `secret`.
127 *
128 * @param {String} val
129 * @param {String} secret
130 * @return {String}
131 * @api private
132 */
133
134exports.sign = function(val, secret){
135 return val + '.' + crypto
136 .createHmac('sha256', secret)
137 .update(val)
138 .digest('base64')
139 .replace(/=+$/, '');
140};
141
142/**
143 * Unsign and decode the given `val` with `secret`,
144 * returning `false` if the signature is invalid.
145 *
146 * @param {String} val
147 * @param {String} secret
148 * @return {String|Boolean}
149 * @api private
150 */
151
152exports.unsign = function(val, secret){
153 var str = val.slice(0,val.lastIndexOf('.'));
154 return exports.sign(str, secret) == val
155 ? str
156 : false;
157};
158
159/**
160 * Parse signed cookies, returning an object
161 * containing the decoded key/value pairs,
162 * while removing the signed key from `obj`.
163 *
164 * @param {Object} obj
165 * @return {Object}
166 * @api private
167 */
168
169exports.parseSignedCookies = function(obj, secret){
170 var ret = {};
171 Object.keys(obj).forEach(function(key){
172 var val = obj[key];
173 if (0 == val.indexOf('s:')) {
174 ret[key] = exports.unsign(val.slice(2), secret);
175 delete obj[key];
176 }
177 });
178 return ret;
179};
180
181/**
182 * Parse a signed cookie string, return the decoded value
183 *
184 * @param {String} str signed cookie string
185 * @param {String} secret
186 * @return {String} decoded value
187 * @api private
188 */
189
190exports.parseSignedCookie = function(str, secret){
191 return 0 == str.indexOf('s:')
192 ? exports.unsign(str.slice(2), secret)
193 : str;
194};
195
196/**
197 * Parse JSON cookies.
198 *
199 * @param {Object} obj
200 * @return {Object}
201 * @api private
202 */
203
204exports.parseJSONCookies = function(obj){
205 Object.keys(obj).forEach(function(key){
206 var val = obj[key];
207 var res = exports.parseJSONCookie(val);
208 if (res) obj[key] = res;
209 });
210 return obj;
211};
212
213/**
214 * Parse JSON cookie string
215 *
216 * @param {String} str
217 * @return {Object} Parsed object or null if not json cookie
218 * @api private
219 */
220
221exports.parseJSONCookie = function(str) {
222 if (0 == str.indexOf('j:')) {
223 try {
224 return JSON.parse(str.slice(2));
225 } catch (err) {
226 // no op
227 }
228 }
229}
230
231/**
232 * Pause `data` and `end` events on the given `obj`.
233 * Middleware performing async tasks _should_ utilize
234 * this utility (or similar), to re-emit data once
235 * the async operation has completed, otherwise these
236 * events may be lost.
237 *
238 * var pause = utils.pause(req);
239 * fs.readFile(path, function(){
240 * next();
241 * pause.resume();
242 * });
243 *
244 * @param {Object} obj
245 * @return {Object}
246 * @api private
247 */
248
249exports.pause = function(obj){
250 var onData
251 , onEnd
252 , events = [];
253
254 // buffer data
255 obj.on('data', onData = function(data, encoding){
256 events.push(['data', data, encoding]);
257 });
258
259 // buffer end
260 obj.on('end', onEnd = function(data, encoding){
261 events.push(['end', data, encoding]);
262 });
263
264 return {
265 end: function(){
266 obj.removeListener('data', onData);
267 obj.removeListener('end', onEnd);
268 },
269 resume: function(){
270 this.end();
271 for (var i = 0, len = events.length; i < len; ++i) {
272 obj.emit.apply(obj, events[i]);
273 }
274 }
275 };
276};
277
278/**
279 * Strip `Content-*` headers from `res`.
280 *
281 * @param {ServerResponse} res
282 * @api private
283 */
284
285exports.removeContentHeaders = function(res){
286 Object.keys(res._headers).forEach(function(field){
287 if (0 == field.indexOf('content')) {
288 res.removeHeader(field);
289 }
290 });
291};
292
293/**
294 * Check if `req` is a conditional GET request.
295 *
296 * @param {IncomingMessage} req
297 * @return {Boolean}
298 * @api private
299 */
300
301exports.conditionalGET = function(req) {
302 return req.headers['if-modified-since']
303 || req.headers['if-none-match'];
304};
305
306/**
307 * Respond with 401 "Unauthorized".
308 *
309 * @param {ServerResponse} res
310 * @param {String} realm
311 * @api private
312 */
313
314exports.unauthorized = function(res, realm) {
315 res.statusCode = 401;
316 res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
317 res.end('Unauthorized');
318};
319
320/**
321 * Respond with 304 "Not Modified".
322 *
323 * @param {ServerResponse} res
324 * @param {Object} headers
325 * @api private
326 */
327
328exports.notModified = function(res) {
329 exports.removeContentHeaders(res);
330 res.statusCode = 304;
331 res.end();
332};
333
334/**
335 * Return an ETag in the form of `"<size>-<mtime>"`
336 * from the given `stat`.
337 *
338 * @param {Object} stat
339 * @return {String}
340 * @api private
341 */
342
343exports.etag = function(stat) {
344 return '"' + stat.size + '-' + Number(stat.mtime) + '"';
345};
346
347/**
348 * Parse the given Cache-Control `str`.
349 *
350 * @param {String} str
351 * @return {Object}
352 * @api private
353 */
354
355exports.parseCacheControl = function(str){
356 var directives = str.split(',')
357 , obj = {};
358
359 for(var i = 0, len = directives.length; i < len; i++) {
360 var parts = directives[i].split('=')
361 , key = parts.shift().trim()
362 , val = parseInt(parts.shift(), 10);
363
364 obj[key] = isNaN(val) ? true : val;
365 }
366
367 return obj;
368};
369
370/**
371 * Parse the `req` url with memoization.
372 *
373 * @param {ServerRequest} req
374 * @return {Object}
375 * @api public
376 */
377
378exports.parseUrl = function(req){
379 var parsed = req._parsedUrl;
380 if (parsed && parsed.href == req.url) {
381 return parsed;
382 } else {
383 return req._parsedUrl = parse(req.url);
384 }
385};
386
387/**
388 * Parse byte `size` string.
389 *
390 * @param {String} size
391 * @return {Number}
392 * @api private
393 */
394
395exports.parseBytes = require('bytes');