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
index a5f072b50..5f4351815 100755
--- a/examples/views/cms/views/partials/footer.html
+++ b/examples/views/cms/views/partials/footer.html
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
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..d5397189a 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');
@@ -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) {
@@ -49,25 +50,13 @@ 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
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 +67,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) {
@@ -89,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;
@@ -107,10 +96,26 @@ 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();
};
+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/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..3fe3fa510 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');
@@ -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;
@@ -97,7 +112,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 +124,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) {
@@ -117,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.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();
}
@@ -135,9 +147,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();
@@ -153,7 +164,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,14 +174,14 @@ 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
var entity = config.entity || 'any';
- // Entity: any
+ // Entity: 'any'
if (entity === 'any') {
request.log(['auth']);
@@ -178,12 +189,12 @@ internals.Auth.prototype.authenticate = function (request, next) {
return next();
}
- // Entity: user
+ // Entity: 'user'
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']);
@@ -191,11 +202,11 @@ internals.Auth.prototype.authenticate = function (request, next) {
return next();
}
- // Entity: app
+ // Entity: 'app'
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/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/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..b243a4049 100755
--- a/lib/index.js
+++ b/lib/index.js
@@ -2,7 +2,8 @@
var internals = {
modules: {
- error: require('./error'),
+ error: require('boom'),
+ boom: require('boom'),
log: require('hapi-log').log,
server: require('./server'),
response: require('./response'),
diff --git a/lib/notFound.js b/lib/notFound.js
deleted file mode 100755
index 58fd00985..000000000
--- a/lib/notFound.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// Load modules
-
-var Err = require('./error');
-
-// Declare internals
-
-var internals = {};
-
-
-exports.handler = function (route) {
-
- return function (request) {
-
- return request.reply(Err.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..50fb78f3c 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');
@@ -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
@@ -45,19 +47,29 @@ 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 = {};
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()
@@ -103,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 = {
@@ -255,8 +259,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);
@@ -277,7 +281,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) {
@@ -336,7 +340,8 @@ internals.Request.prototype._reply = function (err, callback) {
if (err) {
if (self.response &&
- self.response instanceof Error === false) {
+ !self.response.isBoom &&
+ !self.response.varieties.error) {
// Got error after valid result was already set
@@ -351,7 +356,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,7 +596,9 @@ internals.handler = function (request, next) {
// Check for Error result
- if (response instanceof Error) {
+ if (response &&
+ (response.isBoom || response.varieties.error)) {
+
request.log(['handler', 'result', 'error'], { msec: timer.elapsed() });
return callback(response);
}
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 d553c9208..17064a704 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');
@@ -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) {
@@ -52,7 +54,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
@@ -67,8 +69,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
@@ -76,7 +79,7 @@ internals.Directory.prototype._prepare = function (request, callback) {
if (!self._index &&
!self._listing) {
- return finalize(Err.forbidden());
+ return finalize(Boom.forbidden());
}
if (!self._index) {
@@ -94,14 +97,15 @@ internals.Directory.prototype._prepare = function (request, callback) {
// Directory
- if (indexResponse.code !== 404) {
- return finalize(Err.internal('index.html is a directory'));
+ var error = indexResponse;
+ if (error.response.code !== 404) {
+ 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 +121,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/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/file.js b/lib/response/file.js
index b2e34cf3b..c0c96f8a5 100755
--- a/lib/response/file.js
+++ b/lib/response/file.js
@@ -4,17 +4,19 @@ 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');
-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,38 +39,49 @@ 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) {
- 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);
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 5dd598518..5c54a8e62 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
@@ -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 c87e3a659..e883e991a 100755
--- a/lib/response/index.js
+++ b/lib/response/index.js
@@ -1,14 +1,16 @@
// Load modules
var Stream = require('stream');
+var Boom = require('boom');
var Shot = require('shot');
var Utils = require('../utils');
-var Err = require('../error');
// Declare internals
-var internals = {};
+var internals = {
+ maxNestedPreparations: 5
+};
/*
@@ -67,11 +69,13 @@ exports.generate = function (result, onSend) {
response = new internals.Text(result);
}
else if (typeof result === 'object') {
- if (result.variety) {
+ if (result.variety ||
+ result.isBoom) {
+
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);
@@ -82,7 +86,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 || response.isBoom), 'Invalid response object'); // Safety
if (onSend) {
response.send = function () {
@@ -99,54 +103,94 @@ exports.generate = function (result, onSend) {
exports._respond = function (item, request, callback) {
var errorPrepared = false;
+ var prepareCount = 0;
var prepare = function (response) {
if (!response ||
- (!response.variety && response instanceof Err === false)) {
+ (!response.variety && !response.isBoom)) {
- response = Err.internal('Unexpected response item', response);
+ response = Boom.internal('Unexpected response item', response);
}
- if (response._prepare &&
- typeof response._prepare === 'function') {
+ if (response.isBoom) {
+ response = new internals.Error(response);
+ }
- return response._prepare(request, send);
+ if (!response._prepare) {
+ return etag(response);
}
- return send(response);
- };
+ response._prepare(request, function (result) {
- var send = function (response) {
+ if (!result._wasPrepared) {
+ ++prepareCount;
+ if (prepareCount > internals.maxNestedPreparations) { // Prevent prepare loops
+ result = new internals.Error(Boom.internal('Response prepare count exceeded maximum allowed', item));
+ return send(result);
+ }
- // Error object
+ if (result.isBoom) {
+ if (errorPrepared) {
+ result = new internals.Error(result);
+ return send(result);
+ }
- if (response instanceof Err) {
- request.log(['http', 'response'], response);
- response = new internals.Error(response.toResponse());
+ errorPrepared = true; // Prevent error loops
+ }
- if (!errorPrepared) { // Prevents a loop if _prepare returns an error
- errorPrepared = true;
- return response._prepare(request, send);
+ return prepare(result);
}
+
+ return etag(result);
+ });
+ };
+
+ var etag = function (response) {
+
+ if (request.method !== 'get' &&
+ request.method !== 'head') {
+
+ return send(response);
}
- if (request.method === 'get' || request.method === 'head') {
+ // Process ETag
- // Process ETag and If-Modified-Since headers
+ var etag = response._headers && response._headers.etag;
+ if (etag &&
+ request.raw.req.headers['if-none-match'] === etag) {
- 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 unchanged = new internals.Empty();
+ unchanged._code = 304;
+ return prepare(unchanged);
+ }
+
+ // Process If-Modified-Since headers
+
+ var ifModifiedSinceHeader = request.raw.req.headers['if-modified-since'];
+ var lastModifiedHeader = response._headers && response._headers['Last-Modified'];
- if ((etag && request.raw.req.headers['if-none-match'] === etag) ||
- (ifModifiedSince && lastModified && ifModifiedSince >= lastModified)) {
+ if (ifModifiedSinceHeader &&
+ lastModifiedHeader) {
- response = new internals.Empty();
- response._code = 304;
+ var ifModifiedSince = Date.parse(ifModifiedSinceHeader);
+ var lastModified = Date.parse(lastModifiedHeader);
+
+ if (ifModifiedSince &&
+ lastModified &&
+ ifModifiedSince >= lastModified) {
+
+ 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
@@ -164,3 +208,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);
diff --git a/lib/route.js b/lib/route.js
index b1307b59f..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');
@@ -32,7 +32,10 @@ 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.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
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
@@ -166,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;
@@ -387,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 4c9877b85..0f2e3ec46 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');
@@ -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
@@ -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
@@ -270,7 +271,7 @@ internals.Server.prototype._dispatch = function (options) {
// Not found
- return request._execute(self._router.notFound);
+ return request._execute(self._router.notfound);
});
};
};
diff --git a/lib/state.js b/lib/state.js
index b0d4a988a..6e17634e9 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');
@@ -71,10 +71,14 @@ 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') {
- next(Err.badRequest('Bad cookie ' + (name ? 'value: ' + name : 'header')));
+ next(Boom.badRequest('Bad cookie ' + (name ? 'value: ' + name : 'header')));
return true;
}
@@ -169,6 +173,8 @@ exports.parseCookies = function (request, next) {
},
function (err) {
+ // All cookies parsed
+
return next();
});
};
@@ -185,19 +191,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 +218,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 +309,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 +317,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 +325,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 +333,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 +358,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 +370,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..5e0b24709 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);
});
};
@@ -99,12 +99,14 @@ exports.response = function (request, next) {
}
}
- if (request.response instanceof Error) {
+ if (request.response.isBoom ||
+ request.response.varieties.error) {
+
return 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 +123,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);
diff --git a/package.json b/package.json
index fcd76c658..4f2def4bb 100755
--- a/package.json
+++ b/package.json
@@ -22,22 +22,22 @@
},
"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",
- "lru-cache": "~2.2.2"
+ "lru-cache": "2.2.x"
},
"devDependencies": {
"mocha": "1.x.x",
diff --git a/test/integration/auth.js b/test/integration/auth.js
index 40f7ec5f1..702a8e5b0 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();
});
@@ -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
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/integration/response.js b/test/integration/response.js
index cb38eee4d..32cbe15a8 100755
--- a/test/integration/response.js
+++ b/test/integration/response.js
@@ -20,33 +20,6 @@ var expect = Chai.expect;
describe('Response', function () {
- describe('External', function () {
-
- it('returns a reply', function (done) {
-
- var handler = function () {
-
- 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) {
@@ -1613,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) {
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/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();
});
});
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' } } });
});
});
diff --git a/test/unit/validation.js b/test/unit/validation.js
index 5ed5b484d..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,10 +89,10 @@ 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'));
+ request.response = Hapi.Response.generate(Hapi.error.unauthorized('You are not authorized'));
Validation.response(request, function (err) {
@@ -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');