From 4ce0da148e45ca8bd8af4bc3d7a557afb093fab0 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 8 Feb 2013 09:12:12 -0800 Subject: [PATCH 01/21] Switch to use Boom directly --- lib/auth/basic.js | 16 ++++++++-------- lib/auth/cookie.js | 6 +++--- lib/auth/hawk.js | 2 +- lib/auth/index.js | 14 +++++++------- lib/auth/oz.js | 2 +- lib/batch.js | 6 +++--- lib/error.js | 11 ----------- lib/files.js | 4 ++-- lib/index.js | 2 +- lib/notFound.js | 4 ++-- lib/payload.js | 22 +++++++++++----------- lib/proxy.js | 6 +++--- lib/request.js | 4 ++-- lib/response/directory.js | 12 ++++++------ lib/response/file.js | 6 +++--- lib/response/generic.js | 2 +- lib/response/index.js | 10 +++++----- lib/server.js | 2 +- lib/state.js | 26 +++++++++++++------------- lib/validation.js | 12 ++++++------ lib/views.js | 14 +++++++------- 21 files changed, 86 insertions(+), 97 deletions(-) delete mode 100755 lib/error.js diff --git a/lib/auth/basic.js b/lib/auth/basic.js index ea6b0fa88..9681d8568 100755 --- a/lib/auth/basic.js +++ b/lib/auth/basic.js @@ -2,7 +2,7 @@ var Cryptiles = require('cryptiles'); var Utils = require('../utils'); -var Err = require('../error'); +var Boom = require('boom'); // Declare internals @@ -33,7 +33,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) { var req = request.raw.req; var authorization = req.headers.authorization; if (!authorization) { - return callback(Err.unauthorized(null, 'Basic')); + return callback(Boom.unauthorized(null, 'Basic')); } var parts = authorization.split(/\s+/); @@ -41,16 +41,16 @@ internals.Scheme.prototype.authenticate = function (request, callback) { if (parts[0] && parts[0].toLowerCase() !== 'basic') { - return callback(Err.unauthorized(null, 'Basic')); + return callback(Boom.unauthorized(null, 'Basic')); } if (parts.length !== 2) { - return callback(Err.badRequest('Bad HTTP authentication header format', 'Basic')); + return callback(Boom.badRequest('Bad HTTP authentication header format', 'Basic')); } var credentialsParts = new Buffer(parts[1], 'base64').toString().split(':'); if (credentialsParts.length !== 2) { - return callback(Err.badRequest('Bad header internal syntax', 'Basic')); + return callback(Boom.badRequest('Bad header internal syntax', 'Basic')); } var credentials = { @@ -67,7 +67,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) { if (!user) { request.log(['auth', 'error', 'user', 'unknown']); - return callback(Err.unauthorized('Bad username or password', 'Basic'), null, true); + return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, true); } if (!user.hasOwnProperty('password') || @@ -75,7 +75,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) { user.id !== credentials.username) { request.log(['auth', 'error', 'user', 'invalid']); - return callback(Err.internal('Bad user object received for Basic auth validation'), null, true); + return callback(Boom.internal('Bad user object received for Basic auth validation'), null, true); } if (typeof self.settings.hashPasswordFunc === 'function') { @@ -86,7 +86,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) { if (!Cryptiles.fixedTimeComparison(user.password, credentials.password)) { request.log(['auth', 'error', 'user', 'password']); - return callback(Err.unauthorized('Bad username or password', 'Basic'), null, true); + return callback(Boom.unauthorized('Bad username or password', 'Basic'), null, true); } // Authenticated diff --git a/lib/auth/cookie.js b/lib/auth/cookie.js index a4b798e29..c22870e63 100755 --- a/lib/auth/cookie.js +++ b/lib/auth/cookie.js @@ -1,7 +1,7 @@ // Load modules var Utils = require('../utils'); -var Err = require('../error'); +var Boom = require('boom'); var Redirection = require('../response/redirection'); @@ -67,7 +67,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) { var session = request.state[self.settings.cookie]; if (!session) { - return unauthenticated(Err.unauthorized()); + return unauthenticated(Boom.unauthorized()); } self.settings.validateFunc(session, function (err, override) { @@ -78,7 +78,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) { } request.log(['auth', 'validate'], err); - return unauthenticated(Err.unauthorized('Invalid cookie'), session, true); + return unauthenticated(Boom.unauthorized('Invalid cookie'), session, true); } if (override) { diff --git a/lib/auth/hawk.js b/lib/auth/hawk.js index 0036eec02..8a5b42968 100755 --- a/lib/auth/hawk.js +++ b/lib/auth/hawk.js @@ -2,7 +2,7 @@ var Hawk = require('hawk'); var Utils = require('../utils'); -var Err = require('../error'); +var Boom = require('boom'); // Declare internals diff --git a/lib/auth/index.js b/lib/auth/index.js index 619fc259c..49f25ed36 100755 --- a/lib/auth/index.js +++ b/lib/auth/index.js @@ -5,7 +5,7 @@ var Hawk = require('./hawk'); var Basic = require('./basic'); var Cookie = require('./cookie'); var Utils = require('../utils'); -var Err = require('../error'); +var Boom = require('boom'); var Log = require('hapi-log'); @@ -97,7 +97,7 @@ internals.Auth.prototype.authenticate = function (request, next) { return next(); } - return next(Err.unauthorized('Missing authentication', authErrors)); + return next(Boom.unauthorized('Missing authentication', authErrors)); } var strategy = self.strategies[config.strategies[strategyPos++]]; // Increments counter after fetching current strategy @@ -109,7 +109,7 @@ internals.Auth.prototype.authenticate = function (request, next) { // Unauthenticated if (!err && !session) { - return next(Err.internal('Authentication response missing both error and session')); + return next(Boom.internal('Authentication response missing both error and session')); } if (err) { @@ -153,7 +153,7 @@ internals.Auth.prototype.authenticate = function (request, next) { (!session.scope || session.scope.indexOf(config.scope) === -1)) { request.log(['auth', 'error', 'scope'], { got: session.scope, need: config.scope }); - return next(Err.forbidden('Insufficient scope (\'' + config.scope + '\' expected)')); + return next(Boom.forbidden('Insufficient scope (\'' + config.scope + '\' expected)')); } // Check TOS @@ -163,7 +163,7 @@ internals.Auth.prototype.authenticate = function (request, next) { (!session.ext || !session.ext.tos || session.ext.tos < tos)) { request.log(['auth', 'error', 'tos'], { min: tos, received: session.ext && session.ext.tos }); - return next(Err.forbidden('Insufficient TOS accepted')); + return next(Boom.forbidden('Insufficient TOS accepted')); } // Check entity @@ -183,7 +183,7 @@ internals.Auth.prototype.authenticate = function (request, next) { if (entity === 'user') { if (!session.user) { request.log(['auth', 'error'], 'User session required'); - return next(Err.forbidden('Application session cannot be used on a user endpoint')); + return next(Boom.forbidden('Application session cannot be used on a user endpoint')); } request.log(['auth']); @@ -195,7 +195,7 @@ internals.Auth.prototype.authenticate = function (request, next) { if (session.user) { request.log(['auth', 'error'], 'App session required'); - return next(Err.forbidden('User session cannot be used on an application endpoint')); + return next(Boom.forbidden('User session cannot be used on an application endpoint')); } request.log(['auth']); diff --git a/lib/auth/oz.js b/lib/auth/oz.js index 1e187edfc..62dd90ece 100755 --- a/lib/auth/oz.js +++ b/lib/auth/oz.js @@ -2,7 +2,7 @@ var Oz = require('oz'); var Utils = require('../utils'); -var Err = require('../error'); +var Boom = require('boom'); // Declare internals diff --git a/lib/batch.js b/lib/batch.js index 50ba7ad6b..09a988f88 100755 --- a/lib/batch.js +++ b/lib/batch.js @@ -1,7 +1,7 @@ // Load modules var Async = require('async'); -var Err = require('./error'); +var Boom = require('boom'); // Declare internals @@ -42,7 +42,7 @@ exports.config = { }; if (!request.payload.requests) { - return request.reply(Err.badRequest('Request missing requests array')); + return request.reply(Boom.badRequest('Request missing requests array')); } for (var i = 0, il = request.payload.requests.length; i < il; ++i) { @@ -67,7 +67,7 @@ exports.config = { internals.process(request, requests, resultsData, request.reply); } else { - request.reply(Err.badRequest(errorMessage)); + request.reply(Boom.badRequest(errorMessage)); } } }; diff --git a/lib/error.js b/lib/error.js deleted file mode 100755 index 1cf9df74e..000000000 --- a/lib/error.js +++ /dev/null @@ -1,11 +0,0 @@ -// Load modules - -var Boom = require('boom'); - - -// Declare internals - -var internals = {}; - - -exports = module.exports = Boom; diff --git a/lib/files.js b/lib/files.js index ac41fffa8..000ff7597 100755 --- a/lib/files.js +++ b/lib/files.js @@ -1,7 +1,7 @@ // Load modules var Response = require('./response'); -var Err = require('./error'); +var Boom = require('boom'); var Utils = require('./utils'); // Declare internals @@ -100,7 +100,7 @@ exports.directoryHandler = function (route, options) { if (request._paramsArray[0]) { if (request._paramsArray[0].indexOf('..') !== -1) { - return request.reply(Err.forbidden()); + return request.reply(Boom.forbidden()); } path += request._paramsArray[0]; diff --git a/lib/index.js b/lib/index.js index 4fad6b5fd..da0065828 100755 --- a/lib/index.js +++ b/lib/index.js @@ -2,7 +2,7 @@ var internals = { modules: { - error: require('./error'), + error: require('boom'), log: require('hapi-log').log, server: require('./server'), response: require('./response'), diff --git a/lib/notFound.js b/lib/notFound.js index 58fd00985..fe92d95fd 100755 --- a/lib/notFound.js +++ b/lib/notFound.js @@ -1,6 +1,6 @@ // Load modules -var Err = require('./error'); +var Boom = require('boom'); // Declare internals @@ -11,6 +11,6 @@ exports.handler = function (route) { return function (request) { - return request.reply(Err.notFound('Not found')); + return request.reply(Boom.notFound('Not found')); }; }; \ No newline at end of file diff --git a/lib/payload.js b/lib/payload.js index b94cb4857..1c99ddf7a 100755 --- a/lib/payload.js +++ b/lib/payload.js @@ -3,7 +3,7 @@ var Zlib = require('zlib'); var Querystring = require('querystring'); var Formidable = require('formidable'); -var Err = require('./error'); +var Boom = require('boom'); // Declare internals @@ -43,7 +43,7 @@ exports.read = function (request, next) { if (contentLength && parseInt(contentLength, 10) > request.server.settings.payload.maxBytes) { - return next(Err.badRequest('Payload content length greater than maximum allowed: ' + request.server.settings.payload.maxBytes)); + return next(Boom.badRequest('Payload content length greater than maximum allowed: ' + request.server.settings.payload.maxBytes)); } var clientTimeout = request.server.settings.timeout.client; @@ -53,7 +53,7 @@ exports.read = function (request, next) { clientTimeout -= Date.now() - request._timestamp; clientTimeoutId = setTimeout(function () { - finish(Err.clientTimeout('Client is taking too long to send request')); + finish(Boom.clientTimeout('Client is taking too long to send request')); }, clientTimeout); } @@ -77,19 +77,19 @@ exports.read = function (request, next) { req.on('close', function () { - return finish(Err.internal('Request closed before finished reading')); + return finish(Boom.internal('Request closed before finished reading')); }); req.on('error', function (err) { - return finish(Err.internal('Request error before finished reading: ' + err)); + return finish(Boom.internal('Request error before finished reading: ' + err)); }); var payload = ''; req.on('data', function (chunk) { if (payload.length + chunk.length > request.server.settings.payload.maxBytes) { - return finish(Err.badRequest('Payload size greater than maximum allowed: ' + request.server.settings.payload.maxBytes)); + return finish(Boom.badRequest('Payload size greater than maximum allowed: ' + request.server.settings.payload.maxBytes)); } payload += chunk.toString(encoding); @@ -106,7 +106,7 @@ exports.read = function (request, next) { Zlib.unzip(new Buffer(payload, encoding), function (err, buffer) { if (err) { - return finish(Err.badRequest('Invalid gzip: ' + err)); + return finish(Boom.badRequest('Invalid gzip: ' + err)); } var unzipped = buffer.toString(); @@ -130,7 +130,7 @@ exports.read = function (request, next) { parserFunc(result, function (err, payload) { if (err) { - return finish(Err.badRequest('Invalid request payload format')); + return finish(Boom.badRequest('Invalid request payload format')); } request.payload = payload; @@ -167,7 +167,7 @@ internals.setParser = function (headers) { obj = JSON.parse(result); } catch (exp) { - return callback(Err.badRequest('Invalid request payload format')); + return callback(Boom.badRequest('Invalid request payload format')); } return callback(null, obj); @@ -212,7 +212,7 @@ internals.setParser = function (headers) { form.once('error', function () { form.removeAllListeners('end'); - return callback(Err.badRequest('Invalid request multipart payload format')); + return callback(Boom.badRequest('Invalid request multipart payload format')); }); form.once('end', function () { @@ -230,5 +230,5 @@ internals.setParser = function (headers) { // Other - return Err.badRequest('Unsupported content-type: ' + mime); + return Boom.badRequest('Unsupported content-type: ' + mime); }; \ No newline at end of file diff --git a/lib/proxy.js b/lib/proxy.js index 958fb93b6..82122d9f9 100755 --- a/lib/proxy.js +++ b/lib/proxy.js @@ -2,7 +2,7 @@ var Request = require('request'); var Utils = require('./utils'); -var Err = require('./error'); +var Boom = require('boom'); // Declare internals @@ -79,7 +79,7 @@ internals.Proxy.prototype.handler = function () { // Request handles all redirect responses (3xx) and will return an err if redirection fails if (err) { - return request.reply(Err.internal('Proxy error', err)); + return request.reply(Boom.internal('Proxy error', err)); } return self.settings.postResponse(request, self.settings, response, payload); @@ -134,7 +134,7 @@ internals.postResponse = function (request, settings, response, payload) { var statusCode = response.statusCode; if (statusCode >= 400) { - return request.reply(Err.passThrough(statusCode, payload, contentType)); + return request.reply(Boom.passThrough(statusCode, payload, contentType)); } var response = request.reply.payload(payload); diff --git a/lib/request.js b/lib/request.js index b171b4c50..d8b89bd30 100755 --- a/lib/request.js +++ b/lib/request.js @@ -7,7 +7,7 @@ var Stream = require('stream'); var Url = require('url'); var Async = require('async'); var Utils = require('./utils'); -var Err = require('./error'); +var Boom = require('boom'); var Payload = require('./payload'); var State = require('./state'); var Auth = require('./auth'); @@ -277,7 +277,7 @@ internals.Request.prototype._execute = function (route) { serverTimeout -= (Date.now() - self._timestamp); // Calculate the timeout from when the request was constructed var timeoutReply = function () { - self._reply(Err.serverTimeout()); + self._reply(Boom.serverTimeout()); }; if (serverTimeout <= 0) { diff --git a/lib/response/directory.js b/lib/response/directory.js index d553c9208..969650983 100755 --- a/lib/response/directory.js +++ b/lib/response/directory.js @@ -4,7 +4,7 @@ var Fs = require('fs'); var Path = require('path'); var NodeUtil = require('util'); var Cacheable = require('./cacheable'); -var Err = require('../error'); +var Boom = require('boom'); var File = require('./file'); var Utils = require('../utils'); @@ -52,7 +52,7 @@ internals.Directory.prototype._prepare = function (request, callback) { }; if (this._hideFile(this._path)) { // Don't serve hidden files when showHidden is disabled - return finalize(Err.notFound()); + return finalize(Boom.notFound()); } // Lookup file @@ -76,7 +76,7 @@ internals.Directory.prototype._prepare = function (request, callback) { if (!self._index && !self._listing) { - return finalize(Err.forbidden()); + return finalize(Boom.forbidden()); } if (!self._index) { @@ -95,13 +95,13 @@ internals.Directory.prototype._prepare = function (request, callback) { // Directory if (indexResponse.code !== 404) { - return finalize(Err.internal('index.html is a directory')); + return finalize(Boom.internal('index.html is a directory')); } // Not found if (!self._listing) { - return finalize(Err.forbidden()); + return finalize(Boom.forbidden()); } return self._generateListing(finalize); @@ -117,7 +117,7 @@ internals.Directory.prototype._generateListing = function (callback) { Fs.readdir(this._path, function (err, files) { if (err) { - return callback(Err.internal('Error accessing directory')); + return callback(Boom.internal('Error accessing directory')); } var separator = ''; diff --git a/lib/response/file.js b/lib/response/file.js index 13d0f791f..784b01a17 100755 --- a/lib/response/file.js +++ b/lib/response/file.js @@ -4,7 +4,7 @@ var Fs = require('fs'); var Path = require('path'); var NodeUtil = require('util'); var Mime = require('mime'); -var Err = require('../error'); +var Boom = require('boom'); var Utils = require('../utils'); var Stream = require('./stream'); @@ -38,11 +38,11 @@ internals.File.prototype._prepare = function (request, callback) { Fs.stat(self._filePath, function (err, stat) { if (err) { - return callback(Err.notFound()); + return callback(Boom.notFound()); } if (stat.isDirectory()) { - return callback(Err.forbidden()); + return callback(Boom.forbidden()); } var fileName = Path.basename(self._filePath); diff --git a/lib/response/generic.js b/lib/response/generic.js index 5dd598518..5fff846c2 100755 --- a/lib/response/generic.js +++ b/lib/response/generic.js @@ -5,7 +5,7 @@ var Zlib = require('zlib'); var Base = require('./base'); var Headers = require('./headers'); var Utils = require('../utils'); -var Err = require('../error'); +var Boom = require('boom'); // Declare internals diff --git a/lib/response/index.js b/lib/response/index.js index c87e3a659..bab11ee56 100755 --- a/lib/response/index.js +++ b/lib/response/index.js @@ -3,7 +3,7 @@ var Stream = require('stream'); var Shot = require('shot'); var Utils = require('../utils'); -var Err = require('../error'); +var Boom = require('boom'); // Declare internals @@ -71,7 +71,7 @@ exports.generate = function (result, onSend) { response = result; } else if (result instanceof Error) { - response = new Err(result); + response = new Boom(result); } else if (result instanceof Stream) { response = new internals.Stream(result); @@ -103,9 +103,9 @@ exports._respond = function (item, request, callback) { var prepare = function (response) { if (!response || - (!response.variety && response instanceof Err === false)) { + (!response.variety && response instanceof Boom === false)) { - response = Err.internal('Unexpected response item', response); + response = Boom.internal('Unexpected response item', response); } if (response._prepare && @@ -121,7 +121,7 @@ exports._respond = function (item, request, callback) { // Error object - if (response instanceof Err) { + if (response instanceof Boom) { request.log(['http', 'response'], response); response = new internals.Error(response.toResponse()); diff --git a/lib/server.js b/lib/server.js index 4c9877b85..55f447bca 100755 --- a/lib/server.js +++ b/lib/server.js @@ -11,7 +11,7 @@ var Auth = require('./auth'); var Batch = require('./batch'); var Catbox = require('catbox'); var Defaults = require('./defaults'); -var Err = require('./error'); +var Boom = require('boom'); var Request = require('./request'); var Route = require('./route'); var Views = require('./views'); diff --git a/lib/state.js b/lib/state.js index b0d4a988a..68a03f14e 100755 --- a/lib/state.js +++ b/lib/state.js @@ -4,7 +4,7 @@ var Querystring = require('querystring'); var Iron = require('iron'); var Async = require('async'); var Cryptiles = require('cryptiles'); -var Err = require('./error'); +var Boom = require('boom'); var Utils = require('./utils'); @@ -74,7 +74,7 @@ exports.parseCookies = function (request, next) { // failAction: 'error', 'log', 'ignore' if (request.server.settings.state.cookies.failAction === 'error') { - next(Err.badRequest('Bad cookie ' + (name ? 'value: ' + name : 'header'))); + next(Boom.badRequest('Bad cookie ' + (name ? 'value: ' + name : 'header'))); return true; } @@ -185,19 +185,19 @@ exports.parseCookies = function (request, next) { var pos = value.lastIndexOf('.'); if (pos === -1) { - return callback(Err.internal('Missing signature separator')); + return callback(Boom.internal('Missing signature separator')); } var unsigned = value.slice(0, pos); var sig = value.slice(pos + 1); if (!sig) { - return callback(Err.internal('Missing signature')); + return callback(Boom.internal('Missing signature')); } sigParts = sig.split('*'); if (sigParts.length !== 2) { - return callback(Err.internal('Bad signature format')); + return callback(Boom.internal('Bad signature format')); } var hmacSalt = sigParts[0]; @@ -212,7 +212,7 @@ exports.parseCookies = function (request, next) { } if (!Cryptiles.fixedTimeComparison(mac.digest, hmac)) { - return callback(Err.internal('Bad hmac value')); + return callback(Boom.internal('Bad hmac value')); } return callback(null, unsigned); @@ -303,7 +303,7 @@ exports.generateSetCookieHeader = function (cookies, definitions, callback) { // Validate name if (!cookie.name.match(internals.nameRegx)) { - return callback(Err.internal('Invalid cookie name: ' + cookie.name)); + return callback(Boom.internal('Invalid cookie name: ' + cookie.name)); } // Encode value @@ -311,7 +311,7 @@ exports.generateSetCookieHeader = function (cookies, definitions, callback) { encode(cookie.value, options, function (err, value) { if (err) { - return callback(Err.internal('Failed to encode cookie (' + cookie.name + ') value' + (err.message ? ': ' + err.message : ''))); + return callback(Boom.internal('Failed to encode cookie (' + cookie.name + ') value' + (err.message ? ': ' + err.message : ''))); } // Validate value @@ -319,7 +319,7 @@ exports.generateSetCookieHeader = function (cookies, definitions, callback) { if (value && (typeof value !== 'string' || !value.match(internals.valueRegx))) { - return callback(Err.internal('Invalid cookie value: ' + cookie.value)); + return callback(Boom.internal('Invalid cookie value: ' + cookie.value)); } // Sign cookie @@ -327,7 +327,7 @@ exports.generateSetCookieHeader = function (cookies, definitions, callback) { sign(cookie.name, value, options.sign, function (err, signed) { if (err) { - return callback(Err.internal('Failed to sign cookie (' + cookie.name + ') value' + (err.message ? ': ' + err.message : ''))); + return callback(Boom.internal('Failed to sign cookie (' + cookie.name + ') value' + (err.message ? ': ' + err.message : ''))); } // Construct cookie @@ -352,11 +352,11 @@ exports.generateSetCookieHeader = function (cookies, definitions, callback) { if (options.domain) { var domain = options.domain.toLowerCase(); if (!domain.match(internals.domainLabelLenRegx)) { - return callback(Err.internal('Cookie domain too long: ' + options.domain)); + return callback(Boom.internal('Cookie domain too long: ' + options.domain)); } if (!domain.match(internals.domainRegx)) { - return callback(Err.internal('Invalid cookie domain: ' + options.domain)); + return callback(Boom.internal('Invalid cookie domain: ' + options.domain)); } segment += '; Domain=' + domain; @@ -364,7 +364,7 @@ exports.generateSetCookieHeader = function (cookies, definitions, callback) { if (options.path) { if (!options.path.match(internals.pathRegx)) { - return callback(Err.internal('Invalid cookie path: ' + options.path)); + return callback(Boom.internal('Invalid cookie path: ' + options.path)); } segment += '; Path=' + options.path; diff --git a/lib/validation.js b/lib/validation.js index 8be8b3df9..2d549ca2a 100755 --- a/lib/validation.js +++ b/lib/validation.js @@ -1,7 +1,7 @@ // Load modules var Joi = require('joi'); -var Err = require('./error'); +var Boom = require('boom'); var Response = require('./response'); @@ -27,7 +27,7 @@ exports.query = function (request, next) { Joi.validate(request.query, request.route.validate.query, function (err) { - next(err ? Err.badRequest(err.message) : null); + next(err ? Boom.badRequest(err.message) : null); }); }; @@ -49,7 +49,7 @@ exports.payload = function (request, next) { Joi.validate(request.payload, request.route.validate.schema, function (err) { - next(err ? Err.badRequest(err.message) : null); + next(err ? Boom.badRequest(err.message) : null); }); }; @@ -71,7 +71,7 @@ exports.path = function (request, next) { Joi.validate(request.params, request.route.validate.path, function (err) { - next(err ? Err.badRequest(err.message) : null); + next(err ? Boom.badRequest(err.message) : null); }); }; @@ -104,7 +104,7 @@ exports.response = function (request, next) { } if (!request.response.varieties.obj) { - return next(Err.internal('Cannot validate non-object response')); + return next(Boom.internal('Cannot validate non-object response')); } Joi.validate(request.response.raw, request.route.response.schema, function (err) { @@ -121,7 +121,7 @@ exports.response = function (request, next) { return next(); } - next(Err.internal(err.message)); + next(Boom.internal(err.message)); }); }; diff --git a/lib/views.js b/lib/views.js index 74903f2e2..d7c25226d 100755 --- a/lib/views.js +++ b/lib/views.js @@ -4,7 +4,7 @@ var Fs = require('fs'); var Path = require('path'); var Defaults = require('./defaults'); var Utils = require('./utils'); -var Err = require('./error'); +var Boom = require('boom'); // Additional engine modules required in constructor @@ -177,7 +177,7 @@ internals.Manager.prototype.render = function (filename, context, options) { return this.execute(engine, template, context, options)(context, options); } catch (err) { - return Err.internal(err.message, err); + return Boom.internal(err.message, err); } } @@ -186,7 +186,7 @@ internals.Manager.prototype.render = function (filename, context, options) { if (context && context.hasOwnProperty(this.settings.layoutKeyword)) { - return Err.internal('settings.layoutKeyword conflict', { context: context, keyword: this.settings.layoutKeyword }); + return Boom.internal('settings.layoutKeyword conflict', { context: context, keyword: this.settings.layoutKeyword }); } var layout = this._get('layout', options); @@ -201,7 +201,7 @@ internals.Manager.prototype.render = function (filename, context, options) { return this.execute(engine, layout, layoutContext, options)(layoutContext); } catch (err) { - return Err.internal(err.message, err); + return Boom.internal(err.message, err); } }; @@ -257,13 +257,13 @@ internals.Manager.prototype._get = function (filename, options) { if (!options.allowAbsolutePaths && isAbsolutePath) { - return Err.internal('Absolute paths are not allowed in views'); + return Boom.internal('Absolute paths are not allowed in views'); } if (!options.allowInsecureAccess && isInsecurePath) { - return Err.internal('View paths cannot lookup templates outside root path (path includes one or more \'../\')'); + return Boom.internal('View paths cannot lookup templates outside root path (path includes one or more \'../\')'); } // Resolve Path and extension @@ -295,7 +295,7 @@ internals.Manager.prototype._get = function (filename, options) { var source = Fs.readFileSync(fullPath).toString(this.settings.encoding); } catch (e) { - return Err.internal('View file not found: ' + fullPath); + return Boom.internal('View file not found: ' + fullPath); } var engine = this.engines.get(fullPath); From 6a523c5ec8af0f5e7ae87ee855b147bd9cd21c5f Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 8 Feb 2013 09:16:05 -0800 Subject: [PATCH 02/21] lowercase Hapi.Error --- test/integration/notFound.js | 2 +- test/integration/proxy.js | 4 ++-- test/unit/validation.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/notFound.js b/test/integration/notFound.js index 48df494cd..254788cc7 100755 --- a/test/integration/notFound.js +++ b/test/integration/notFound.js @@ -61,7 +61,7 @@ describe('NotFound', function () { server.route({ method: 'GET', path: '/exists/{p*}', handler: function (request) { request.reply('OK'); } }); server.route({ method: '*', path: '/{p*}', handler: function (request) { - request.reply(Hapi.Error.notFound('These these aren\'t the pages you\'re looking for.')); + request.reply(Hapi.error.notFound('These these aren\'t the pages you\'re looking for.')); }}); it('returns custom response when requesting a route that doesn\'t exist', function (done) { diff --git a/test/integration/proxy.js b/test/integration/proxy.js index f5bed1ffb..8d1d5bac1 100755 --- a/test/integration/proxy.js +++ b/test/integration/proxy.js @@ -115,12 +115,12 @@ describe('Proxy', function () { function unauthorized (request) { - request.reply(Hapi.Error.unauthorized('Not authorized')); + request.reply(Hapi.error.unauthorized('Not authorized')); } function postResponseWithError (request) { - request.reply(Hapi.Error.forbidden('Forbidden')); + request.reply(Hapi.error.forbidden('Forbidden')); } function postResponse (request, settings, response, payload) { diff --git a/test/unit/validation.js b/test/unit/validation.js index 5ed5b484d..99b765af9 100755 --- a/test/unit/validation.js +++ b/test/unit/validation.js @@ -92,7 +92,7 @@ describe('Validation', function () { var query = { username: 'walmart' }; var request = createRequestObject(query, route); - request.response = Hapi.Response.generate(Hapi.Error.unauthorized('You are not authorized')); + request.response = Hapi.Response.generate(Hapi.error.unauthorized('You are not authorized')); Validation.response(request, function (err) { From 40ae671f522c6fcc4c4a0e1aef90c10dd0fc10a7 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 8 Feb 2013 09:21:34 -0800 Subject: [PATCH 03/21] Replace walmart ref in code --- examples/validation.js | 8 ++++---- examples/views/cms/views/partials/footer.html | 2 +- test/unit/auth/index.js | 6 +++--- test/unit/validation.js | 16 ++++++++-------- 4 files changed, 16 insertions(+), 16 deletions(-) mode change 100644 => 100755 examples/views/cms/views/partials/footer.html diff --git a/examples/validation.js b/examples/validation.js index 59024540d..db1c667d5 100755 --- a/examples/validation.js +++ b/examples/validation.js @@ -59,10 +59,10 @@ internals.main(); Try various URLs like: http://localhost:8080/ // success http://localhost:8080/?username=test // success - http://localhost:8080/admin?username=walmart&password=worldofwalmartlabs // success - http://localhost:8080/admin?username=walmart // fail - http://localhost:8080/users?email=vnguyen@walmart.com // success - http://localhost:8080/users?email=@walmart.com // fail + http://localhost:8080/admin?username=steve&password=shhhhhh // success + http://localhost:8080/admin?username=steve // fail + http://localhost:8080/users?email=steve@example.com // success + http://localhost:8080/users?email=@example.com // fail http://localhost:8080/config?choices=1&choices=2 // success http://localhost:8080/config?choices=1 // success http://localhost:8080/config // fail diff --git a/examples/views/cms/views/partials/footer.html b/examples/views/cms/views/partials/footer.html old mode 100644 new mode 100755 index a5f072b50..5f4351815 --- a/examples/views/cms/views/partials/footer.html +++ b/examples/views/cms/views/partials/footer.html @@ -1 +1 @@ -

© @WalmartLabs 2013

\ No newline at end of file +

© @Someone 2013

\ No newline at end of file diff --git a/test/unit/auth/index.js b/test/unit/auth/index.js index 80cdc1fb4..180a2f086 100755 --- a/test/unit/auth/index.js +++ b/test/unit/auth/index.js @@ -137,7 +137,7 @@ describe('Auth', function () { req: { headers: { host: 'localhost', - authorization: 'basic d2FsbWFydDp3YWxtYXJ0' + authorization: 'basic ' + (new Buffer('steve:password').toString('base64')) }, url: 'http://localhost/test' } @@ -155,7 +155,7 @@ describe('Auth', function () { scheme: 'basic', loadUserFunc: function (username, callback) { - return callback(null, { id: 'walmart', password: 'walmart' }); + return callback(null, { id: 'steve', password: 'password' }); } } }; @@ -188,7 +188,7 @@ describe('Auth', function () { scheme: 'basic', loadUserFunc: function (username, callback) { - return callback(null, { id: 'walmart', password: 'walmart' }); + return callback(null, { id: 'steve', password: 'password' }); } } } diff --git a/test/unit/validation.js b/test/unit/validation.js index 99b765af9..3d197179f 100755 --- a/test/unit/validation.js +++ b/test/unit/validation.js @@ -75,7 +75,7 @@ describe('Validation', function () { it('should not raise an error when responding with valid param', function (done) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.response = Hapi.Response.generate({ username: 'test' }); @@ -89,7 +89,7 @@ describe('Validation', function () { it('an error response should skip response validation and not return an error', function (done) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.response = Hapi.Response.generate(Hapi.error.unauthorized('You are not authorized')); @@ -103,7 +103,7 @@ describe('Validation', function () { it('should raise an error when responding with invalid param', function (done) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.response = Hapi.Response.generate({ wrongParam: 'test' }); @@ -116,7 +116,7 @@ describe('Validation', function () { it('should not raise an error when responding with invalid param and sample is 0', function (done) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.route.response.sample = 0; request.response = Hapi.Response.generate({ wrongParam: 'test' }); @@ -130,7 +130,7 @@ describe('Validation', function () { it('should raise an error when responding with invalid param and sample is 100', function (done) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.route.response.sample = 100; request.response = Hapi.Response.generate({ wrongParam: 'test' }); @@ -144,7 +144,7 @@ describe('Validation', function () { internals.calculateFailAverage = function (size, sample) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.route.response.failAction = 'log'; request.route.response.sample = sample; @@ -186,7 +186,7 @@ describe('Validation', function () { it('should report an error when responding with invalid response param and failAction is report', function (done) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.route.response.failAction = 'log'; request.response = Hapi.Response.generate({ wrongParam: 'test' }); @@ -205,7 +205,7 @@ describe('Validation', function () { it('should raise an error when validating a non-object response', function (done) { - var query = { username: 'walmart' }; + var query = { username: 'steve' }; var request = createRequestObject(query, route); request.response = Hapi.Response.generate('test'); From 6f18523bbabab03e070862339c30768ba9196911 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 8 Feb 2013 15:16:55 -0800 Subject: [PATCH 04/21] boom 0.3.0 --- lib/auth/index.js | 7 +++---- lib/request.js | 14 ++++++++------ lib/response/directory.js | 8 +++++--- lib/response/index.js | 15 +++++++++------ lib/validation.js | 2 +- test/unit/response/directory.js | 2 +- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/auth/index.js b/lib/auth/index.js index 49f25ed36..72db2e9d5 100755 --- a/lib/auth/index.js +++ b/lib/auth/index.js @@ -122,7 +122,7 @@ internals.Auth.prototype.authenticate = function (request, next) { } if (!err.isMissing || - err.code !== 401) { // An actual error (not just missing authentication) + err.response.code !== 401) { // An actual error (not just missing authentication) if (config.mode === 'try') { request.session = session; @@ -135,9 +135,8 @@ internals.Auth.prototype.authenticate = function (request, next) { // Try next strategy - var response = err.toResponse(); - if (response.headers['WWW-Authenticate']) { - authErrors.push(response.headers['WWW-Authenticate']); + if (err.response.headers['WWW-Authenticate']) { + authErrors.push(err.response.headers['WWW-Authenticate']); } return authenticate(); diff --git a/lib/request.js b/lib/request.js index d8b89bd30..d5385f520 100755 --- a/lib/request.js +++ b/lib/request.js @@ -336,7 +336,7 @@ internals.Request.prototype._reply = function (err, callback) { if (err) { if (self.response && - self.response instanceof Error === false) { + !self.response.varieties.error) { // Got error after valid result was already set @@ -351,7 +351,7 @@ internals.Request.prototype._reply = function (err, callback) { var sendResponse = function () { - if (!self.response) { // Can only happen when request.reply.close() is called + if (!self.response) { // Can only happen when request.reply.close() is called self.raw.res.end(); // End the response in case it wasn't already closed return finalize(); } @@ -591,13 +591,15 @@ internals.handler = function (request, next) { // Check for Error result - if (response instanceof Error) { + if (response && + response.varieties.error) { + request.log(['handler', 'result', 'error'], { msec: timer.elapsed() }); return callback(response); } if (request._route.cache.rule.strict) { - Utils.assert(response.varieties.cacheable, 'Attempted to cache non-cacheable item'); + Utils.assert(response.varieties.cacheable && !response.varieties.error, 'Attempted to cache non-cacheable item'); } request.log(['handler', 'result'], { msec: timer.elapsed() }); @@ -612,8 +614,8 @@ internals.handler = function (request, next) { var store = function (response) { - request.response = response; - return next(); // Must not include an argument + request.response = Response.generate(response); // Ensure any errors are handled + return next(); // Must not include an argument }; check(); diff --git a/lib/response/directory.js b/lib/response/directory.js index 969650983..3267aae91 100755 --- a/lib/response/directory.js +++ b/lib/response/directory.js @@ -67,8 +67,9 @@ internals.Directory.prototype._prepare = function (request, callback) { // Not found - if (response.code !== 403) { - return finalize(response); + var error = response; + if (error.response.code !== 403) { + return finalize(error); } // Directory @@ -94,7 +95,8 @@ internals.Directory.prototype._prepare = function (request, callback) { // Directory - if (indexResponse.code !== 404) { + var error = indexResponse; + if (error.response.code !== 404) { return finalize(Boom.internal('index.html is a directory')); } diff --git a/lib/response/index.js b/lib/response/index.js index bab11ee56..a663b14ce 100755 --- a/lib/response/index.js +++ b/lib/response/index.js @@ -1,9 +1,9 @@ // Load modules var Stream = require('stream'); +var Boom = require('boom'); var Shot = require('shot'); var Utils = require('../utils'); -var Boom = require('boom'); // Declare internals @@ -70,8 +70,11 @@ exports.generate = function (result, onSend) { if (result.variety) { response = result; } + else if (result instanceof Boom) { + response = new internals.Error(result.response); + } else if (result instanceof Error) { - response = new Boom(result); + response = new internals.Error(new Boom(result).response); } else if (result instanceof Stream) { response = new internals.Stream(result); @@ -82,7 +85,7 @@ exports.generate = function (result, onSend) { response = new internals.Obj(result); } - Utils.assert(response && (response.variety || response instanceof Error), 'Response must be an instance of Error or Generic'); // Safety + Utils.assert(response && response.variety, 'Invalid response object'); // Safety if (onSend) { response.send = function () { @@ -103,9 +106,9 @@ exports._respond = function (item, request, callback) { var prepare = function (response) { if (!response || - (!response.variety && response instanceof Boom === false)) { + !response.variety) { - response = Boom.internal('Unexpected response item', response); + response = new internals.Error(Boom.internal('Unexpected response item', response).response); } if (response._prepare && @@ -123,7 +126,7 @@ exports._respond = function (item, request, callback) { if (response instanceof Boom) { request.log(['http', 'response'], response); - response = new internals.Error(response.toResponse()); + response = new internals.Error(response.response); if (!errorPrepared) { // Prevents a loop if _prepare returns an error errorPrepared = true; diff --git a/lib/validation.js b/lib/validation.js index 2d549ca2a..0a0888d11 100755 --- a/lib/validation.js +++ b/lib/validation.js @@ -99,7 +99,7 @@ exports.response = function (request, next) { } } - if (request.response instanceof Error) { + if (request.response.varieties.error) { return next(); } diff --git a/test/unit/response/directory.js b/test/unit/response/directory.js index b0ce0b448..c37e74817 100755 --- a/test/unit/response/directory.js +++ b/test/unit/response/directory.js @@ -25,7 +25,7 @@ describe('Response', function () { var dir = new Hapi.response.Directory('no_such_path', {}); dir._generateListing(function (response) { - expect(response.code).to.equal(500); + expect(response.response.code).to.equal(500); done(); }); }); From ac154511dbcea993307bec87c7835e7e7e39bc5d Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 8 Feb 2013 16:53:06 -0800 Subject: [PATCH 05/21] fix test --- test/integration/response.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/response.js b/test/integration/response.js index df3a9dca1..f3d117b0c 100755 --- a/test/integration/response.js +++ b/test/integration/response.js @@ -35,7 +35,7 @@ describe('Response', function () { server.inject({ method: 'GET', url: '/null' }, function (res) { - expect(res.readPayload()).to.equal('0\r\n\r\n'); + expect(res.readPayload()).to.equal(''); expect(function () { From fc4cb04004979552c206154027cd892f5f2ff492 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 8 Feb 2013 17:01:26 -0800 Subject: [PATCH 06/21] versions --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 38f9caf3e..b30730894 100755 --- a/package.json +++ b/package.json @@ -22,20 +22,20 @@ }, "dependencies": { "hoek": "0.4.x", - "boom": "0.2.x", + "boom": "0.3.x", "joi": "0.0.x", "hapi-helmet": "0.0.x", "hapi-log": "0.1.x", - "hawk": "0.6.x", + "hawk": "0.7.x", "shot": "0.0.x", - "oz": "0.0.x", + "oz": "0.1.x", "async": "0.1.x", "request": "2.11.x", "formidable": "1.0.x", "mime": "1.2.x", "catbox": "0.1.x", - "cryptiles": "0.0.x", - "iron": "0.1.x", + "cryptiles": "0.1.x", + "iron": "0.2.x", "semver": "1.1.0" }, "devDependencies": { From 47899f39e3c7762b446044f3a955c7f9d723c2bd Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 8 Feb 2013 17:10:59 -0800 Subject: [PATCH 07/21] Fix close() bug --- lib/request.js | 4 ++-- test/integration/response.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/request.js b/lib/request.js index d5385f520..4bbb1f8d4 100755 --- a/lib/request.js +++ b/lib/request.js @@ -614,8 +614,8 @@ internals.handler = function (request, next) { var store = function (response) { - request.response = Response.generate(response); // Ensure any errors are handled - return next(); // Must not include an argument + request.response = (response ? Response.generate(response) : null); // Ensure any errors are handled + return next(); // Must not include an argument }; check(); diff --git a/test/integration/response.js b/test/integration/response.js index f3d117b0c..a85037488 100755 --- a/test/integration/response.js +++ b/test/integration/response.js @@ -26,6 +26,7 @@ describe('Response', function () { var handler = function () { + this.raw.res.end(); this.reply.close(); }; @@ -35,7 +36,7 @@ describe('Response', function () { server.inject({ method: 'GET', url: '/null' }, function (res) { - expect(res.readPayload()).to.equal(''); + expect(res.readPayload()).to.equal('0\r\n\r\n'); expect(function () { From ca8fc39505e56f48252bb4a36666ff785e1a0361 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 10:24:42 -0800 Subject: [PATCH 08/21] misc --- lib/request.js | 9 +++++---- lib/response/index.js | 15 +++++++-------- lib/validation.js | 4 +++- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/request.js b/lib/request.js index 4bbb1f8d4..fed0063dc 100755 --- a/lib/request.js +++ b/lib/request.js @@ -336,6 +336,7 @@ internals.Request.prototype._reply = function (err, callback) { if (err) { if (self.response && + self.response instanceof Boom === false && !self.response.varieties.error) { // Got error after valid result was already set @@ -592,14 +593,14 @@ internals.handler = function (request, next) { // Check for Error result if (response && - response.varieties.error) { + (response instanceof Boom || response.varieties.error)) { request.log(['handler', 'result', 'error'], { msec: timer.elapsed() }); return callback(response); } if (request._route.cache.rule.strict) { - Utils.assert(response.varieties.cacheable && !response.varieties.error, 'Attempted to cache non-cacheable item'); + Utils.assert(response.varieties.cacheable, 'Attempted to cache non-cacheable item'); } request.log(['handler', 'result'], { msec: timer.elapsed() }); @@ -614,8 +615,8 @@ internals.handler = function (request, next) { var store = function (response) { - request.response = (response ? Response.generate(response) : null); // Ensure any errors are handled - return next(); // Must not include an argument + request.response = response; // Ensure any errors are handled + return next(); // Must not include an argument }; check(); diff --git a/lib/response/index.js b/lib/response/index.js index a663b14ce..21ef54ffd 100755 --- a/lib/response/index.js +++ b/lib/response/index.js @@ -67,14 +67,13 @@ exports.generate = function (result, onSend) { response = new internals.Text(result); } else if (typeof result === 'object') { - if (result.variety) { + if (result.variety || + result instanceof Boom) { + response = result; } - else if (result instanceof Boom) { - response = new internals.Error(result.response); - } else if (result instanceof Error) { - response = new internals.Error(new Boom(result).response); + response = new Boom(result); } else if (result instanceof Stream) { response = new internals.Stream(result); @@ -85,7 +84,7 @@ exports.generate = function (result, onSend) { response = new internals.Obj(result); } - Utils.assert(response && response.variety, 'Invalid response object'); // Safety + Utils.assert(response && (response.variety || response instanceof Boom), 'Invalid response object'); // Safety if (onSend) { response.send = function () { @@ -106,9 +105,9 @@ exports._respond = function (item, request, callback) { var prepare = function (response) { if (!response || - !response.variety) { + (!response.variety && response instanceof Boom === false)) { - response = new internals.Error(Boom.internal('Unexpected response item', response).response); + response = Boom.internal('Unexpected response item', response); } if (response._prepare && diff --git a/lib/validation.js b/lib/validation.js index 0a0888d11..44ecd825d 100755 --- a/lib/validation.js +++ b/lib/validation.js @@ -99,7 +99,9 @@ exports.response = function (request, next) { } } - if (request.response.varieties.error) { + if (request.response instanceof Boom || + request.response.varieties.error) { + return next(); } From b6422b1f75894999189ac3061b45d164f1dc32fb Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 10:27:25 -0800 Subject: [PATCH 09/21] comment --- lib/request.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/request.js b/lib/request.js index fed0063dc..97d97caca 100755 --- a/lib/request.js +++ b/lib/request.js @@ -615,8 +615,8 @@ internals.handler = function (request, next) { var store = function (response) { - request.response = response; // Ensure any errors are handled - return next(); // Must not include an argument + request.response = response; + return next(); // Must not include an argument }; check(); From 8375f11fc57c9a942ffe40809b4310150eb0c8d6 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 12:00:01 -0800 Subject: [PATCH 10/21] server.app and request.app for application state --- lib/request.js | 1 + lib/server.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/request.js b/lib/request.js index 97d97caca..81237b44f 100755 --- a/lib/request.js +++ b/lib/request.js @@ -45,6 +45,7 @@ exports = module.exports = internals.Request = function (server, req, res, optio this._setMethod(req.method); // Sets: method this.id = now + '-' + process.pid + '-' + Math.floor(Math.random() * 0x10000); + this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins this.plugins = {}; // Place for plugins to store state without conflicts with hapi, should be namespaced using plugin name this.route = {}; diff --git a/lib/server.js b/lib/server.js index 55f447bca..c3b2ee9e7 100755 --- a/lib/server.js +++ b/lib/server.js @@ -122,6 +122,7 @@ module.exports = internals.Server = function (/* host, port, options */) { this._pack = null; this.plugins = {}; // Registered plugin APIs by plugin name this.plugin.list = {}; // Loaded plugins by plugin name + this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins // Initialize Views From 8441c195da47ffce2678923321f6ce05f8e05813 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 12:06:23 -0800 Subject: [PATCH 11/21] route.config.app for application state --- lib/auth/index.js | 6 +++--- lib/route.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/auth/index.js b/lib/auth/index.js index 72db2e9d5..f484ed15c 100755 --- a/lib/auth/index.js +++ b/lib/auth/index.js @@ -169,7 +169,7 @@ internals.Auth.prototype.authenticate = function (request, next) { var entity = config.entity || 'any'; - // Entity: any + // Entity: 'any' if (entity === 'any') { request.log(['auth']); @@ -177,7 +177,7 @@ internals.Auth.prototype.authenticate = function (request, next) { return next(); } - // Entity: user + // Entity: user' if (entity === 'user') { if (!session.user) { @@ -190,7 +190,7 @@ internals.Auth.prototype.authenticate = function (request, next) { return next(); } - // Entity: app + // Entity: 'app' if (session.user) { request.log(['auth', 'error'], 'App session required'); diff --git a/lib/route.js b/lib/route.js index b1307b59f..bf3f5fb07 100755 --- a/lib/route.js +++ b/lib/route.js @@ -32,7 +32,9 @@ exports = module.exports = internals.Route = function (options, server) { this.method = settings.method.toLowerCase(); this.path = settings.path; this.config = Utils.applyToDefaults(server.settings.router.routeDefaults, settings.config || {}); - this.config.plugins = this.config.plugins || {}; + + this.config.plugins = this.config.plugins || {}; // Route-specific plugins config, namespaced using plugin name + this.config.app = this.config.app || {}; // Route-specific application config Utils.assert(this.method !== 'head', 'Cannot add a HEAD route'); Utils.assert(!!settings.handler ^ !!this.config.handler, 'Handler must appear once and only once'); // XOR From 374bac8434c7df3972630ca4e49c8ca55e0583a6 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 12:23:07 -0800 Subject: [PATCH 12/21] Expose method in route config --- lib/route.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/route.js b/lib/route.js index bf3f5fb07..2bd7f5224 100755 --- a/lib/route.js +++ b/lib/route.js @@ -32,6 +32,7 @@ exports = module.exports = internals.Route = function (options, server) { this.method = settings.method.toLowerCase(); this.path = settings.path; this.config = Utils.applyToDefaults(server.settings.router.routeDefaults, settings.config || {}); + this.config.method = this.method; // Expose method in config this.config.plugins = this.config.plugins || {}; // Route-specific plugins config, namespaced using plugin name this.config.app = this.config.app || {}; // Route-specific application config From 1f3181c7cd17033c81f7b5b44053756ff781f1e6 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 12:29:29 -0800 Subject: [PATCH 13/21] streamline notFound --- lib/notFound.js | 16 ---------------- lib/request.js | 4 ++-- lib/route.js | 13 +++++++++++-- lib/server.js | 6 +++--- 4 files changed, 16 insertions(+), 23 deletions(-) delete mode 100755 lib/notFound.js diff --git a/lib/notFound.js b/lib/notFound.js deleted file mode 100755 index fe92d95fd..000000000 --- a/lib/notFound.js +++ /dev/null @@ -1,16 +0,0 @@ -// Load modules - -var Boom = require('boom'); - -// Declare internals - -var internals = {}; - - -exports.handler = function (route) { - - return function (request) { - - return request.reply(Boom.notFound('Not found')); - }; -}; \ No newline at end of file diff --git a/lib/request.js b/lib/request.js index 81237b44f..6857a555f 100755 --- a/lib/request.js +++ b/lib/request.js @@ -256,8 +256,8 @@ internals.Request.prototype._onRequestExt = function (callback) { // Send error response - self._route = self.server._router.notFound; // Only settings are used, not the handler - self.route = self.server._router.notFound.config; + self._route = self.server._router.notfound; // Only settings are used, not the handler + self.route = self.server._router.notfound.config; self._reply(err, function () { return callback(err); diff --git a/lib/route.js b/lib/route.js index 2bd7f5224..5ec58a2f7 100755 --- a/lib/route.js +++ b/lib/route.js @@ -1,9 +1,9 @@ // Load modules +var Boom = require('boom'); var Catbox = require('catbox'); var Auth = require('./auth'); var Files = require('./files'); -var NotFound = require('./notFound'); var Proxy = require('./proxy'); var Utils = require('./utils'); var Views = require('./views'); @@ -169,7 +169,7 @@ exports = module.exports = internals.Route = function (options, server) { } } else if (this.config.handler === 'notFound') { - this.config.handler = NotFound.handler(this); + this.config.handler = internals.notFound(); } return this; @@ -390,3 +390,12 @@ exports.sort = function (a, b) { return (al > bl ? bFirst : aFirst); }; + +internals.notFound = function () { + + return function (request) { + + return request.reply(Boom.notFound()); + }; +}; + diff --git a/lib/server.js b/lib/server.js index c3b2ee9e7..0f2e3ec46 100755 --- a/lib/server.js +++ b/lib/server.js @@ -108,8 +108,8 @@ module.exports = internals.Server = function (/* host, port, options */) { table: {} // Array per HTTP method, including * for catch-all }; - this._router.notFound = new Route({ - method: 'notFound', + this._router.notfound = new Route({ + method: 'notfound', path: '/{p*}', config: { auth: { mode: 'none' }, // In case defaults are set otherwise @@ -271,7 +271,7 @@ internals.Server.prototype._dispatch = function (options) { // Not found - return request._execute(self._router.notFound); + return request._execute(self._router.notfound); }); }; }; From 1b62dfb93f74927986abd6276b7aaf54f583d6e7 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 14:27:41 -0800 Subject: [PATCH 14/21] Option to automatically clear invalid cookies --- lib/defaults.js | 3 ++- lib/index.js | 1 + lib/state.js | 6 ++++++ test/unit/state.js | 13 +++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/defaults.js b/lib/defaults.js index 2e8ecb129..534b2fe61 100755 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -33,7 +33,8 @@ exports.server = { state: { cookies: { parse: true, // Parse content of req.headers.cookie - failAction: 'error' // Action on bad cookie - 'error': return 400, 'log': log and continue, 'ignore': continue + failAction: 'error', // Action on bad cookie - 'error': return 400, 'log': log and continue, 'ignore': continue + clearInvalid: false // Automatically instruct the client to remove the invalid cookie } }, diff --git a/lib/index.js b/lib/index.js index da0065828..b243a4049 100755 --- a/lib/index.js +++ b/lib/index.js @@ -3,6 +3,7 @@ var internals = { modules: { error: require('boom'), + boom: require('boom'), log: require('hapi-log').log, server: require('./server'), response: require('./response'), diff --git a/lib/state.js b/lib/state.js index 68a03f14e..6e17634e9 100755 --- a/lib/state.js +++ b/lib/state.js @@ -71,6 +71,10 @@ exports.parseCookies = function (request, next) { var shouldStop = function (error, name) { + if (request.server.settings.state.cookies.clearInvalid) { + request.clearState(name); + } + // failAction: 'error', 'log', 'ignore' if (request.server.settings.state.cookies.failAction === 'error') { @@ -169,6 +173,8 @@ exports.parseCookies = function (request, next) { }, function (err) { + // All cookies parsed + return next(); }); }; diff --git a/test/unit/state.js b/test/unit/state.js index 5dd626fc6..170ce9413 100755 --- a/test/unit/state.js +++ b/test/unit/state.js @@ -107,6 +107,7 @@ describe('State', function () { it('fails parsing cookie header: ' + header, function (done) { var ignore = false; + var cleared = ''; var request = { raw: { @@ -124,6 +125,10 @@ describe('State', function () { }, log: function (tags, data) { ignore = true; + }, + clearState: function (name) { + + cleared = name; } }; @@ -136,6 +141,10 @@ describe('State', function () { expect(err).to.exist; } + if (request.server.settings.state.cookies.clearInvalid) { + expect(cleared).to.equal('sid'); + } + done(); }); }); @@ -162,6 +171,10 @@ describe('State', function () { fail('key=XeyJ0ZXN0aW5nIjoianNvbiJ9; key=XeyJ0ZXN0aW5dnIjoianNvbiJ9', setLog, { key: { encoding: 'base64json' } }); fail('sid=a=1&b=2&c=3%20x', setLog, { sid: { encoding: 'form', sign: { password: 'password' } } }); fail('sid=a=1&b=2&c=3%20x; sid=a=1&b=2&c=3%20x', setLog, { sid: { encoding: 'form', sign: { password: 'password' } } }); + + var clearInvalid = Hapi.utils.clone(Defaults.server.state); + clearInvalid.cookies.clearInvalid = true; + fail('sid=a=1&b=2&c=3%20x', clearInvalid, { sid: { encoding: 'form', sign: { password: 'password' } } }); }); }); From f69dd85437143042a12d82e05315386f4d8eb6c7 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 20:16:14 -0800 Subject: [PATCH 15/21] Provide auth schemes a way to extend requests outside of auth func --- lib/auth/cookie.js | 28 ++++++++++++++++------------ lib/auth/index.js | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/lib/auth/cookie.js b/lib/auth/cookie.js index c22870e63..0f944ae16 100755 --- a/lib/auth/cookie.js +++ b/lib/auth/cookie.js @@ -49,18 +49,6 @@ internals.Scheme.prototype.authenticate = function (request, callback) { var self = this; - // Decorate request - - request.setSession = function (session) { - - request.setState(self.settings.cookie, session); - }; - - request.clearSession = function () { - - request.clearState(self.settings.cookie); - }; - var validate = function () { // Check cookie @@ -114,3 +102,19 @@ internals.Scheme.prototype.authenticate = function (request, callback) { }; +internals.Scheme.prototype.extend = function (request) { + + var self = this; + + // Decorate request + + request.setSession = function (session) { + + request.setState(self.settings.cookie, session); + }; + + request.clearSession = function () { + + request.clearState(self.settings.cookie); + }; +}; diff --git a/lib/auth/index.js b/lib/auth/index.js index f484ed15c..a116a6da1 100755 --- a/lib/auth/index.js +++ b/lib/auth/index.js @@ -30,6 +30,7 @@ exports = module.exports = internals.Auth = function (server, options) { // Load strategies this.strategies = {}; + this.extensions = []; for (var name in settings) { if (settings.hasOwnProperty(name)) { var strategy = settings[name]; @@ -46,6 +47,12 @@ exports = module.exports = internals.Auth = function (server, options) { case 'cookie': this.strategies[name] = new Cookie(this.server, strategy); break; default: this.strategies[name] = strategy.implementation; break; } + + if (this.strategies[name].extend && + typeof this.strategies[name].extend === 'function') { + + this.extensions.push(this.strategies[name]); + } } } @@ -55,6 +62,14 @@ exports = module.exports = internals.Auth = function (server, options) { internals.Auth.authenticate = function (request, next) { + // Extend requests with loaded strategies + + if (request.server.auth) { + for (var i = 0, il = request.server.auth.extensions.length; i < il; ++i) { + request.server.auth.extensions[i].extend(request); + } + } + // Modes: required, optional, try, none var config = request.route.auth; From 10db85137e398a32dd29043b89222747cef3a80d Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 9 Feb 2013 23:23:16 -0800 Subject: [PATCH 16/21] Set default session cookie path to / --- lib/auth/cookie.js | 3 ++- test/integration/auth.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/auth/cookie.js b/lib/auth/cookie.js index 0f944ae16..29e2b6ed9 100755 --- a/lib/auth/cookie.js +++ b/lib/auth/cookie.js @@ -26,7 +26,8 @@ exports = module.exports = internals.Scheme = function (server, options) { var cookieOptions = { encoding: 'iron', password: this.settings.password, - isSecure: !this.settings.allowInsecure + isSecure: !this.settings.allowInsecure, + path: '/' }; if (this.settings.ttl) { diff --git a/test/integration/auth.js b/test/integration/auth.js index 40f7ec5f1..1d619e829 100755 --- a/test/integration/auth.js +++ b/test/integration/auth.js @@ -937,7 +937,7 @@ describe('Auth', function () { expect(res.statusCode).to.equal(200); expect(res.result).to.equal('logged-out'); - expect(res.headers['Set-Cookie'][0]).to.equal('special=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure'); + expect(res.headers['Set-Cookie'][0]).to.equal('special=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; Path=/'); done(); }); }); @@ -955,7 +955,7 @@ describe('Auth', function () { server.inject({ method: 'GET', url: '/resource', headers: { cookie: 'special=' + cookie[1] } }, function (res) { - expect(res.headers['Set-Cookie'][0]).to.equal('special=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure'); + expect(res.headers['Set-Cookie'][0]).to.equal('special=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; Path=/'); expect(res.statusCode).to.equal(401); done(); }); From 620c650d49025c27beed07c28c5782ef4dc8f9ff Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sun, 10 Feb 2013 10:48:00 -0800 Subject: [PATCH 17/21] isBoom --- lib/auth/cookie.js | 6 +++--- lib/auth/index.js | 11 ++++------- lib/request.js | 25 ++++++++++++++----------- lib/response/index.js | 8 ++++---- lib/validation.js | 2 +- test/integration/auth.js | 31 +++++++++++++++++++++++++++++++ 6 files changed, 57 insertions(+), 26 deletions(-) diff --git a/lib/auth/cookie.js b/lib/auth/cookie.js index 29e2b6ed9..d5397189a 100755 --- a/lib/auth/cookie.js +++ b/lib/auth/cookie.js @@ -78,10 +78,10 @@ internals.Scheme.prototype.authenticate = function (request, callback) { }); }; - var unauthenticated = function (err) { + var unauthenticated = function (err, session, wasLogged) { if (!self.settings.redirectTo) { - return callback(err); + return callback(err, session, wasLogged); } var uri = self.settings.redirectTo; @@ -96,7 +96,7 @@ internals.Scheme.prototype.authenticate = function (request, callback) { uri += self.settings.appendNext + '=' + encodeURIComponent(request.url.path); } - return callback(new Redirection(uri)); + return callback(new Redirection(uri), session, wasLogged); }; validate(); diff --git a/lib/auth/index.js b/lib/auth/index.js index a116a6da1..eacc075f7 100755 --- a/lib/auth/index.js +++ b/lib/auth/index.js @@ -132,16 +132,13 @@ internals.Auth.prototype.authenticate = function (request, next) { request.log(['auth', 'unauthenticated'], err); } - if (err instanceof Error === false) { // Not an actual error (e.g. redirect, custom response) - return next(err); - } - - if (!err.isMissing || - err.response.code !== 401) { // An actual error (not just missing authentication) + if (err instanceof Error === false || // Not an actual error (e.g. redirect, custom response) + !err.isMissing || // Missing authentication (did not fail) + err.response.code !== 401) { // An actual error (not just missing authentication) if (config.mode === 'try') { request.session = session; - request.log(['auth', 'unauthenticated', 'try']); + request.log(['auth', 'unauthenticated', 'try'], err); return next(); } diff --git a/lib/request.js b/lib/request.js index 6857a555f..50fb78f3c 100755 --- a/lib/request.js +++ b/lib/request.js @@ -30,6 +30,8 @@ exports = module.exports = internals.Request = function (server, req, res, optio Utils.assert(req, 'req must be provided'); Utils.assert(res, 'res must be provided'); + options = options || {}; + // Pause request req.pause(); // Must be done before switching event execution context @@ -52,13 +54,22 @@ exports = module.exports = internals.Request = function (server, req, res, optio this.response = null; this.isReplied = false; + this.session = null; // Does not mean authenticated { id, [app], [scope], [user], [ext.tos] } + this.isAuthenticated = false; + + // Apply options + + if (options.session) { + this.session = options.session; + } + // Defined elsewhere: // query // params // rawBody // payload - // session: { id, [app], [scope], [user], [ext.tos] } + // state // setUrl() @@ -104,14 +115,6 @@ exports = module.exports = internals.Request = function (server, req, res, optio this._debug = this.query[this.server.settings.debug.queryKey]; } - // Apply options - - if (options && - options.session) { - - this.session = options.session; - } - // Log request var about = { @@ -337,7 +340,7 @@ internals.Request.prototype._reply = function (err, callback) { if (err) { if (self.response && - self.response instanceof Boom === false && + !self.response.isBoom && !self.response.varieties.error) { // Got error after valid result was already set @@ -594,7 +597,7 @@ internals.handler = function (request, next) { // Check for Error result if (response && - (response instanceof Boom || response.varieties.error)) { + (response.isBoom || response.varieties.error)) { request.log(['handler', 'result', 'error'], { msec: timer.elapsed() }); return callback(response); diff --git a/lib/response/index.js b/lib/response/index.js index 21ef54ffd..c2fac8294 100755 --- a/lib/response/index.js +++ b/lib/response/index.js @@ -68,7 +68,7 @@ exports.generate = function (result, onSend) { } else if (typeof result === 'object') { if (result.variety || - result instanceof Boom) { + result.isBoom) { response = result; } @@ -84,7 +84,7 @@ exports.generate = function (result, onSend) { response = new internals.Obj(result); } - Utils.assert(response && (response.variety || response instanceof Boom), 'Invalid response object'); // Safety + Utils.assert(response && (response.variety || response.isBoom), 'Invalid response object'); // Safety if (onSend) { response.send = function () { @@ -105,7 +105,7 @@ exports._respond = function (item, request, callback) { var prepare = function (response) { if (!response || - (!response.variety && response instanceof Boom === false)) { + (!response.variety && !response.isBoom)) { response = Boom.internal('Unexpected response item', response); } @@ -123,7 +123,7 @@ exports._respond = function (item, request, callback) { // Error object - if (response instanceof Boom) { + if (response.isBoom) { request.log(['http', 'response'], response); response = new internals.Error(response.response); diff --git a/lib/validation.js b/lib/validation.js index 44ecd825d..5e0b24709 100755 --- a/lib/validation.js +++ b/lib/validation.js @@ -99,7 +99,7 @@ exports.response = function (request, next) { } } - if (request.response instanceof Boom || + if (request.response.isBoom || request.response.varieties.error) { return next(); diff --git a/test/integration/auth.js b/test/integration/auth.js index 1d619e829..702a8e5b0 100755 --- a/test/integration/auth.js +++ b/test/integration/auth.js @@ -1027,6 +1027,37 @@ describe('Auth', function () { done(); }); }); + + it('does not redirect on try', function (done) { + + var config = { + scheme: 'cookie', + password: 'password', + ttl: 60 * 1000, + redirectTo: 'http://example.com/login', + appendNext: true, + validateFunc: function (session, callback) { + + return callback(); + } + }; + + var server = new Hapi.Server({ auth: config }); + + server.route({ + method: 'GET', path: '/', config: { auth: { mode: 'try' } }, handler: function () { + + return this.reply('try'); + } + }); + + server.inject({ method: 'GET', url: '/' }, function (res) { + + expect(res.result).to.equal('try'); + expect(res.statusCode).to.equal(200); + done(); + }); + }); }); }); }); \ No newline at end of file From 83cf8fd96a9a32efc18021d2eaeda36d6a6e39dc Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sun, 10 Feb 2013 15:19:49 -0800 Subject: [PATCH 18/21] Refactor response prepare to cover every flow --- lib/response/base.js | 8 +++++ lib/response/directory.js | 2 ++ lib/response/file.js | 35 ++++++++++++------ lib/response/generic.js | 2 ++ lib/response/headers.js | 1 - lib/response/index.js | 76 +++++++++++++++++++++++++++------------ lib/response/obj.js | 2 +- lib/response/view.js | 2 ++ 8 files changed, 92 insertions(+), 36 deletions(-) diff --git a/lib/response/base.js b/lib/response/base.js index cba803e9a..5002cf23b 100755 --- a/lib/response/base.js +++ b/lib/response/base.js @@ -18,6 +18,7 @@ exports = module.exports = internals.Base = function () { this._flags = {}; // Cached this._states = {}; // Not cached + this._wasPrepared = false; return this; }; @@ -66,6 +67,13 @@ internals.Base.prototype.unstate = function (name) { }; +internals.Base.prototype._prepare = function (request, callback) { + + this._wasPrepared = true; + return callback(this); +}; + + // Required interface /* diff --git a/lib/response/directory.js b/lib/response/directory.js index 3267aae91..17064a704 100755 --- a/lib/response/directory.js +++ b/lib/response/directory.js @@ -42,6 +42,8 @@ internals.Directory.prototype._prepare = function (request, callback) { var self = this; + this._wasPrepared = true; + var finalize = function (response) { if (response instanceof Error) { diff --git a/lib/response/file.js b/lib/response/file.js index b0ca0e10c..c0c96f8a5 100755 --- a/lib/response/file.js +++ b/lib/response/file.js @@ -7,14 +7,16 @@ var Mime = require('mime'); var Boom = require('boom'); var Utils = require('../utils'); var Stream = require('./stream'); -var LRU = require('lru-cache'); +var LruCache = require('lru-cache'); var Crypto = require('crypto'); // Declare internals -var internals = {}; -internals.fileEtags = LRU(); +var internals = { + fileEtags: LruCache() // Files etags cache +}; + // File response (Base -> Generic -> Stream -> File) @@ -37,6 +39,9 @@ NodeUtil.inherits(internals.File, Stream); internals.File.prototype._prepare = function (request, callback) { var self = this; + + this._wasPrepared = true; + Fs.stat(self._filePath, function (err, stat) { if (err) { @@ -49,26 +54,34 @@ internals.File.prototype._prepare = function (request, callback) { var fileName = Path.basename(self._filePath); var stream = Fs.createReadStream(self._filePath); + Stream.call(self, stream); + self._headers['Content-Type'] = Mime.lookup(self._filePath) || 'application/octet-stream'; self._headers['Content-Length'] = stat.size; - + // Use stat info for an LRU cache key. + var cachekey = [self._filePath, stat.ino, stat.size, Date.parse(stat.mtime)].join('-'); - - // The etag must hash the file contents in order to be consistent between nodes. + + // The etag must hash the file contents in order to be consistent across distributed deployments + if (internals.fileEtags.has(cachekey)) { self._headers.etag = JSON.stringify(internals.fileEtags.get(cachekey)); - } else { + } + else { var hash = Crypto.createHash('md5'); stream.on('data', function (chunk) { + hash.update(chunk); - }) + }); + stream.on('end', function () { - internals.fileEtags.set(cachekey, hash.digest("hex")); - }) + + internals.fileEtags.set(cachekey, hash.digest('hex')); + }); } - + self._headers['Content-Disposition'] = 'inline; filename=' + encodeURIComponent(fileName); return Stream.prototype._prepare.call(self, request, callback); diff --git a/lib/response/generic.js b/lib/response/generic.js index 5fff846c2..5c54a8e62 100755 --- a/lib/response/generic.js +++ b/lib/response/generic.js @@ -37,6 +37,8 @@ internals.Generic.prototype._prepare = function (request, callback) { var self = this; + this._wasPrepared = true; + Headers.state(this, request, function (err) { if (err) { diff --git a/lib/response/headers.js b/lib/response/headers.js index bc340232e..a5e528770 100755 --- a/lib/response/headers.js +++ b/lib/response/headers.js @@ -88,4 +88,3 @@ exports.state = function (response, request, callback) { return callback(); }); }; - diff --git a/lib/response/index.js b/lib/response/index.js index c2fac8294..d7c4d9a3c 100755 --- a/lib/response/index.js +++ b/lib/response/index.js @@ -100,7 +100,7 @@ exports.generate = function (result, onSend) { exports._respond = function (item, request, callback) { - var errorPrepared = false; + var prepareCount = 0; var prepare = function (response) { @@ -110,45 +110,73 @@ exports._respond = function (item, request, callback) { response = Boom.internal('Unexpected response item', response); } - if (response._prepare && - typeof response._prepare === 'function') { + if (response.isBoom) { + response = new internals.Error(response.response); + } - return response._prepare(request, send); + if (!response._prepare) { + return etag(response); } - return send(response); + response._prepare(request, function (result) { + + if (!result._wasPrepared) { + if (++prepareCount > 2) { // Prevent loops + return send(result); + } + + return prepare(result); + } + + return etag(result); + }); }; - var send = function (response) { + var etag = function (response) { - // Error object + if (request.method !== 'get' && + request.method !== 'head') { - if (response.isBoom) { - request.log(['http', 'response'], response); - response = new internals.Error(response.response); + return send(response); + } - if (!errorPrepared) { // Prevents a loop if _prepare returns an error - errorPrepared = true; - return response._prepare(request, send); - } + // Process ETag + + var etag = response._headers && response._headers.etag; + if (etag && + request.raw.req.headers['if-none-match'] === etag) { + + var unchanged = new internals.Empty(); + unchanged._code = 304; + return prepare(unchanged); } - if (request.method === 'get' || request.method === 'head') { + // Process If-Modified-Since headers + + var ifModifiedSinceHeader = request.raw.req.headers['if-modified-since']; + var lastModifiedHeader = response._headers && response._headers['Last-Modified']; - // Process ETag and If-Modified-Since headers + if (ifModifiedSinceHeader && + lastModifiedHeader) { - var ifModifiedSince = request.raw.req.headers['if-modified-since'] ? Date.parse(request.raw.req.headers['if-modified-since']) : null; - var lastModified = response._headers && response._headers['Last-Modified'] ? Date.parse(response._headers['Last-Modified']) : null; - var etag = response._headers ? response._headers.etag : null; + var ifModifiedSince = Date.parse(ifModifiedSinceHeader); + var lastModified = Date.parse(lastModifiedHeader); - if ((etag && request.raw.req.headers['if-none-match'] === etag) || - (ifModifiedSince && lastModified && ifModifiedSince >= lastModified)) { + if (ifModifiedSince && + lastModified && + ifModifiedSince >= lastModified) { - response = new internals.Empty(); - response._code = 304; + var unchanged = new intenals.Empty(); + unchanged._code = 304; + return prepare(unchanged); } } + return send(response); + }; + + var send = function (response) { + // Injection if (response._payload !== undefined) { // Value can be falsey @@ -166,3 +194,5 @@ exports._respond = function (item, request, callback) { prepare(item); }; + + diff --git a/lib/response/obj.js b/lib/response/obj.js index b1f7383d1..e9d1dcc71 100755 --- a/lib/response/obj.js +++ b/lib/response/obj.js @@ -18,7 +18,7 @@ exports = module.exports = internals.Obj = function (object, type) { this.varieties.obj = true; this.raw = object; // Can change if reference is modified - this.update(type); // Convert immediately to snapshot content + this.update(type); // Convert immediately to snapshot content return this; }; diff --git a/lib/response/view.js b/lib/response/view.js index a1bf5b386..e17236466 100755 --- a/lib/response/view.js +++ b/lib/response/view.js @@ -32,6 +32,8 @@ NodeUtil.inherits(internals.View, Cacheable); internals.View.prototype._prepare = function (request, callback) { + this._wasPrepared = true; + this._payload = this.view.manager.render(this.view.template, this.view.context, this.view.options); if (this._payload instanceof Error) { return callback(this._payload); From a2ac432c987da39198caad8ed2dc6b0b0b174fa9 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sun, 10 Feb 2013 23:35:10 -0800 Subject: [PATCH 19/21] Prepare loops --- lib/response/error.js | 6 +- lib/response/index.js | 20 ++++- test/integration/response.js | 140 ++++++++++++++++++++++++++++------- 3 files changed, 134 insertions(+), 32 deletions(-) diff --git a/lib/response/error.js b/lib/response/error.js index c886ab1f7..54c88e9dd 100755 --- a/lib/response/error.js +++ b/lib/response/error.js @@ -14,7 +14,11 @@ var internals = {}; exports = module.exports = internals.Error = function (options) { - // { code, payload, type, headers } + // { code, payload, type, headers } or Boom + + if (options.isBoom) { + options = options.response; + } Obj.call(this, options.payload); this.variety = 'error'; diff --git a/lib/response/index.js b/lib/response/index.js index d7c4d9a3c..e883e991a 100755 --- a/lib/response/index.js +++ b/lib/response/index.js @@ -8,7 +8,9 @@ var Utils = require('../utils'); // Declare internals -var internals = {}; +var internals = { + maxNestedPreparations: 5 +}; /* @@ -100,6 +102,7 @@ exports.generate = function (result, onSend) { exports._respond = function (item, request, callback) { + var errorPrepared = false; var prepareCount = 0; var prepare = function (response) { @@ -111,7 +114,7 @@ exports._respond = function (item, request, callback) { } if (response.isBoom) { - response = new internals.Error(response.response); + response = new internals.Error(response); } if (!response._prepare) { @@ -121,10 +124,21 @@ exports._respond = function (item, request, callback) { response._prepare(request, function (result) { if (!result._wasPrepared) { - if (++prepareCount > 2) { // Prevent loops + ++prepareCount; + if (prepareCount > internals.maxNestedPreparations) { // Prevent prepare loops + result = new internals.Error(Boom.internal('Response prepare count exceeded maximum allowed', item)); return send(result); } + if (result.isBoom) { + if (errorPrepared) { + result = new internals.Error(result); + return send(result); + } + + errorPrepared = true; // Prevent error loops + } + return prepare(result); } diff --git a/test/integration/response.js b/test/integration/response.js index 19ab87398..32cbe15a8 100755 --- a/test/integration/response.js +++ b/test/integration/response.js @@ -20,34 +20,6 @@ var expect = Chai.expect; describe('Response', function () { - describe('External', function () { - - it('returns a reply', function (done) { - - var handler = function () { - - this.raw.res.end(); - this.reply.close(); - }; - - var server = new Hapi.Server({ cache: { engine: 'memory' } }); - server.route({ method: 'GET', path: '/throw', config: { handler: handler, cache: { mode: 'server', expiresIn: 9999 } } }); - server.route({ method: 'GET', path: '/null', config: { handler: handler } }); - - server.inject({ method: 'GET', url: '/null' }, function (res) { - - expect(res.readPayload()).to.equal('0\r\n\r\n'); - - expect(function () { - - server.inject({ method: 'GET', url: '/throw' }, function (res) { }); - }).to.throw(); - - done(); - }); - }); - }); - describe('Text', function () { it('returns a text reply', function (done) { @@ -1614,6 +1586,118 @@ describe('Response', function () { }); }); + describe('External', function () { + + it('returns a reply', function (done) { + + var handler = function () { + + this.raw.res.end(); + this.reply.close(); + }; + + var server = new Hapi.Server({ cache: { engine: 'memory' } }); + server.route({ method: 'GET', path: '/throw', config: { handler: handler, cache: { mode: 'server', expiresIn: 9999 } } }); + server.route({ method: 'GET', path: '/null', config: { handler: handler } }); + + server.inject({ method: 'GET', url: '/null' }, function (res) { + + expect(res.readPayload()).to.equal('0\r\n\r\n'); + + expect(function () { + + server.inject({ method: 'GET', url: '/throw' }, function (res) { }); + }).to.throw(); + + done(); + }); + }); + }); + + describe('Extension', function () { + + it('returns a reply using custom response without _prepare', function (done) { + + var handler = function () { + + var custom = { + variety: 'x-custom', + varieties: { 'x-custom': true }, + _transmit: function (request, callback) { + + request.raw.res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': 11 }); + request.raw.res.end('Hello World'); + } + }; + + this.reply(custom); + }; + + var server = new Hapi.Server(); + server.route({ method: 'GET', path: '/', config: { handler: handler } }); + + server.inject({ method: 'GET', url: '/' }, function (res) { + + expect(res.readPayload()).to.equal('Hello World'); + done(); + }); + }); + + it('returns an internal error on error response loop', function (done) { + + var handler = function () { + + var custom = { + variety: 'x-custom', + varieties: { 'x-custom': true }, + _prepare: function (request, callback) { + + callback(Hapi.error.badRequest()); + }, + _transmit: function () { } + }; + + this.setState('bad', {}); + this.reply(custom); + }; + + var server = new Hapi.Server(); + server.route({ method: 'GET', path: '/', config: { handler: handler } }); + + server.inject({ method: 'GET', url: '/' }, function (res) { + + expect(res.result.code).to.equal(500); + done(); + }); + }); + + it('returns an error on infinite _prepare loop', function (done) { + + var handler = function () { + + var custom = { + variety: 'x-custom', + varieties: { 'x-custom': true }, + _prepare: function (request, callback) { + + callback(custom); + } + }; + + this.reply(custom); + }; + + var server = new Hapi.Server(); + server.route({ method: 'GET', path: '/', config: { handler: handler } }); + + server.inject({ method: 'GET', url: '/' }, function (res) { + + expect(res.result.code).to.equal(500); + done(); + }); + }); + }); + describe('#_respond', function () { it('returns an error reply on invalid Response._respond', function (done) { From 13addca0617542d09606c1136bc76a6cad2ee611 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Mon, 11 Feb 2013 00:21:21 -0800 Subject: [PATCH 20/21] typo --- lib/auth/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/auth/index.js b/lib/auth/index.js index eacc075f7..3fe3fa510 100755 --- a/lib/auth/index.js +++ b/lib/auth/index.js @@ -189,7 +189,7 @@ internals.Auth.prototype.authenticate = function (request, next) { return next(); } - // Entity: user' + // Entity: 'user' if (entity === 'user') { if (!session.user) { From f7743aca2aff613359f8c04e18a02e8bf8989e8d Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Mon, 11 Feb 2013 08:27:16 -0800 Subject: [PATCH 21/21] deps --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 5e10a19ff..4f2def4bb 100755 --- a/package.json +++ b/package.json @@ -34,6 +34,9 @@ "formidable": "1.0.x", "mime": "1.2.x", "catbox": "0.1.x", + "cryptiles": "0.1.x", + "iron": "0.2.x", + "semver": "1.1.0", "lru-cache": "2.2.x" }, "devDependencies": {