4 * Copyright(c) 2010 Sencha Inc.
5 * Copyright(c) 2011 TJ Holowaychuk
10 * Module dependencies.
13 var crypto = require('crypto')
14 , Path = require('path')
18 * Flatten the given `arr`.
25 exports.flatten = function(arr, ret){
28 for (var i = 0; i < len; ++i) {
29 if (Array.isArray(arr[i])) {
30 exports.flatten(arr[i], ret);
39 * Return md5 hash of the given string and optional encoding,
43 * // => "e493298061761236c96b02ea6aa8a2ad"
46 * @param {String} encoding
51 exports.md5 = function(str, encoding){
55 .digest(encoding || 'hex');
59 * Merge object b with object a.
61 * var a = { foo: 'bar' }
62 * , b = { bar: 'baz' };
65 * // => { foo: 'bar', bar: 'baz' }
73 exports.merge = function(a, b){
83 * Escape the given string of `html`.
85 * @param {String} html
90 exports.escape = function(html){
92 .replace(/&(?!\w+;)/g, '&')
93 .replace(/</g, '<')
94 .replace(/>/g, '>')
95 .replace(/"/g, '"');
100 * Return a unique identifier with the given `len`.
105 * @param {Number} len
110 exports.uid = function(len) {
112 , chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
113 , charlen = chars.length;
115 for (var i = 0; i < len; ++i) {
116 buf.push(chars[getRandomInt(0, charlen - 1)]);
123 * Parse the given cookie string into an object.
125 * @param {String} str
130 exports.parseCookie = function(str){
132 , pairs = str.split(/[;,] */);
133 for (var i = 0, len = pairs.length; i < len; ++i) {
135 , eqlIndex = pair.indexOf('=')
136 , key = pair.substr(0, eqlIndex).trim().toLowerCase()
137 , val = pair.substr(++eqlIndex, pair.length).trim();
140 if (val[0] === '"') {
141 val = val.slice(1, -1);
145 if (obj[key] === undefined) {
146 obj[key] = decodeURIComponent(val.replace(/\+/g, ' '));
153 * Serialize the given object into a cookie string.
155 * utils.serializeCookie('name', 'tj', { httpOnly: true })
156 * // => "name=tj; httpOnly"
158 * @param {String} name
159 * @param {String} val
160 * @param {Object} obj
165 exports.serializeCookie = function(name, val, obj){
166 var pairs = [name + '=' + encodeURIComponent(val)]
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');
175 return pairs.join('; ');
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.
185 * var pause = utils.pause(req);
186 * fs.readFile(path, function(){
191 * @param {Object} obj
196 exports.pause = function(obj){
202 obj.on('data', onData = function(data, encoding){
203 events.push(['data', data, encoding]);
207 obj.on('end', onEnd = function(data, encoding){
208 events.push(['end', data, encoding]);
213 obj.removeListener('data', onData);
214 obj.removeListener('end', onEnd);
218 for (var i = 0, len = events.length; i < len; ++i) {
219 obj.emit.apply(obj, events[i]);
226 * Check `req` and `res` to see if it has been modified.
228 * @param {IncomingMessage} req
229 * @param {ServerResponse} res
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'];
241 if (noneMatch) noneMatch = noneMatch.split(/ *, */);
243 // check If-None-Match
244 if (noneMatch && etag && ~noneMatch.indexOf(etag)) {
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;
262 * Strip `Content-*` headers from `res`.
264 * @param {ServerResponse} res
268 exports.removeContentHeaders = function(res){
269 Object.keys(res._headers).forEach(function(field){
270 if (0 == field.indexOf('content')) {
271 res.removeHeader(field);
277 * Check if `req` is a conditional GET request.
279 * @param {IncomingMessage} req
284 exports.conditionalGET = function(req) {
285 return req.headers['if-modified-since']
286 || req.headers['if-none-match'];
290 * Respond with 412 "Unauthorized".
292 * @param {ServerResponse} res
293 * @param {String} realm
297 exports.unauthorized = function(res, realm) {
298 res.statusCode = 401;
299 res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
300 res.end('Unauthorized');
304 * Respond with 400 "Bad Request".
306 * @param {ServerResponse} res
310 exports.badRequest = function(res) {
311 res.statusCode = 400;
312 res.end('Bad Request');
316 * Respond with 304 "Not Modified".
318 * @param {ServerResponse} res
319 * @param {Object} headers
323 exports.notModified = function(res) {
324 exports.removeContentHeaders(res);
325 res.statusCode = 304;
330 * Return an ETag in the form of `"<size>-<mtime>"`
331 * from the given `stat`.
333 * @param {Object} stat
338 exports.etag = function(stat) {
339 return '"' + stat.size + '-' + Number(stat.mtime) + '"';
343 * Parse "Range" header `str` relative to the given file `size`.
345 * @param {Number} size
346 * @param {String} str
351 exports.parseRange = function(size, str){
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);
363 } else if (isNaN(end)) {
368 if (isNaN(start) || isNaN(end) || start > end) valid = false;
370 return { start: start, end: end };
372 return valid ? arr : undefined;
376 * Convert array-like object to an `Array`.
378 * node-bench measured "16.5 times faster than Array.prototype.slice.call()"
380 * @param {Object} obj
385 var toArray = exports.toArray = function(obj){
387 , arr = new Array(len);
388 for (var i = 0; i < len; ++i) {
395 * Retrun a random int, used by `utils.uid()`
397 * @param {Number} min
398 * @param {Number} max
403 function getRandomInt(min, max) {
404 return Math.floor(Math.random() * (max - min + 1)) + min;