Server = function (options) {
Hoek.assert(this instanceof internals.Server, 'Server must be instantiated using new');
options = Schema.apply('server', options || {});
this._settings = Hoek.applyToDefaultsWithShallow(Defaults.server, options, ['connections.routes.bind']);
this._settings.connections = Hoek.applyToDefaultsWithShallow(Defaults.connection, this._settings.connections || {}, ['routes
.bind']);
this._settings.connections.routes.cors = Hoek.applyToDefaults(Defaults.cors, this._settings.connections.routes.cors);
this._settings.connections.routes.security = Hoek.applyToDefaults(Defaults.security, this._settings.connections.routes.security
);
this._caches = {}; // Cache clients
this._handlers = {}; // Registered handlers
this._methods = new Methods(this); // Server methods
this._events = new Podium([{ name: 'log', tags: true }, 'start', 'stop']); // Server-only events
this._dependencies = []; // Plugin dependencies
this._registrations = {}; // Tracks plugins registered before connection
added
this._heavy = new Heavy(this._settings.load);
this._mime = new Mimos(this._settings.mime);
this._replier = new Reply();
this._requestor = new Request();
this._decorations = {};
this._plugins = {}; // Exposed plugin properties by name
this._app = {};
this._registring = false; // true while register() is waiting for plugin
callbacks
this._state = 'stopped'; // 'stopped', 'initializing', 'initialized', '
starting', 'started', 'stopping', 'invalid'
this._extensionsSeq = 0; // Used to keep absolute order of extensions
based on the order added across locations
this._extensions = {
onPreStart: new Ext('onPreStart', this),
onPostStart: new Ext('onPostStart', this),
onPreStop: new Ext('onPreStop', this),
onPostStop: new Ext('onPostStop', this)
};
if (options.cache) {
this._createCache(options.cache);
}
if (!this._caches._default) {
this._createCache([{ engine: CatboxMemory }]); // Defaults to memory-based
}
Plugin.call(this, this, [], '', null);
// Subscribe to server log events
if (this._settings.debug) {
const debug = (request, event) => {
const data = event.data;
console.error('Debug:', event.tags.join(', '), (data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify
(data) : data)) : ''));
};
if (this._settings.debug.log) {
this._events.on({ name: 'log', filter: this._settings.debug.log }, (event) => debug(null, event));
}
if (this._settings.debug.request) {
this.on({ name: 'request', filter: this._settings.debug.request }, debug);
this.on({ name: 'request-internal', filter: this._settings.debug.request }, debug);
}
}
}n/a
Server.super_ = function (server, connections, env, parent) { // env can be a realm or plugin name
Podium.call(this, [connections && connections.length ? connections : Connection._events, server._events]);
this._parent = parent;
// Public interface
this.root = server;
this.app = this.root._app;
this.connections = connections;
this.load = this.root._heavy.load;
this.methods = this.root._methods.methods;
this.mime = this.root._mime;
this.plugins = this.root._plugins;
this.settings = this.root._settings;
this.version = Package.version;
this.realm = typeof env !== 'string' ? env : {
_extensions: {
onPreAuth: new Ext('onPreAuth', this.root),
onPostAuth: new Ext('onPostAuth', this.root),
onPreHandler: new Ext('onPreHandler', this.root),
onPostHandler: new Ext('onPostHandler', this.root),
onPreResponse: new Ext('onPreResponse', this.root)
},
modifiers: {
route: {}
},
plugin: env,
pluginOptions: {},
plugins: {},
settings: {
bind: undefined,
files: {
relativeTo: undefined
}
}
};
this.auth = {
default: (opts) => this._applyChild('auth.default', 'auth', 'default', [opts]),
scheme: (name, scheme) => this._applyChild('auth.scheme', 'auth', 'scheme', [name, scheme]),
strategy: (name, scheme, mode, opts) => this._applyChild('auth.strategy', 'auth', 'strategy', [name, scheme, mode, opts]),
test: (name, request, next) => request.connection.auth.test(name, request, next)
};
this.cache.provision = (opts, callback) => {
if (!callback) {
return Promises.wrap(null, this.cache.provision, [opts]);
}
return this.root._createCache(opts, callback);
};
this._single();
// Decorations
const methods = Object.keys(this.root._decorations);
for (let i = 0; i < methods.length; ++i) {
const method = methods[i];
this[method] = this.root._decorations[method];
}
}n/a
auth = function (connection) {
this.connection = connection;
this._schemes = {};
this._strategies = {};
this.settings = {
default: null // Strategy used as default if route has no auth settings
};
this.api = {};
}n/a
compression = function () {
this.encodings = ['identity', 'gzip', 'deflate'];
this._encoders = {
identity: null,
gzip: (options) => Zlib.createGzip(options),
deflate: (options) => Zlib.createDeflate(options)
};
this._decoders = {
gzip: (options) => Zlib.createGunzip(options),
deflate: (options) => Zlib.createInflate(options)
};
}n/a
connection = function (server, options) {
const now = Date.now();
Podium.call(this, internals.Connection._events);
this.settings = options; // options cloned in server.connection()
this.server = server;
// Normalize settings
this.settings.labels = Hoek.unique(this.settings.labels || []); // Remove duplicates
if (this.settings.port === undefined) {
this.settings.port = 0;
}
this.type = (typeof this.settings.port === 'string' ? 'socket' : 'tcp');
if (this.type === 'socket') {
this.settings.port = (this.settings.port.indexOf('/') !== -1 ? Path.resolve(this.settings.port) : this.settings.port.toLowerCase
());
}
if (this.settings.autoListen === undefined) {
this.settings.autoListen = true;
}
Hoek.assert(this.settings.autoListen || !this.settings.port, 'Cannot specify port when autoListen is false');
Hoek.assert(this.settings.autoListen || !this.settings.address, 'Cannot specify address when autoListen is false');
// Connection facilities
this._started = false;
this._connections = {};
this._onConnection = null; // Used to remove event listener on stop
this.registrations = {}; // Tracks plugin for dependency validation { name -> { version } }
this._extensions = {
onRequest: new Ext('onRequest', this.server),
onPreAuth: new Ext('onPreAuth', this.server),
onPostAuth: new Ext('onPostAuth', this.server),
onPreHandler: new Ext('onPreHandler', this.server),
onPostHandler: new Ext('onPostHandler', this.server),
onPreResponse: new Ext('onPreResponse', this.server)
};
this._requestCounter = { value: internals.counter.min, min: internals.counter.min, max: internals.counter.max };
this._load = server._heavy.policy(this.settings.load);
this._compression = new Compression();
this.states = new Statehood.Definitions(this.settings.state);
this.auth = new Auth(this);
this._router = new Call.Router(this.settings.router);
this._defaultRoutes();
this.plugins = {}; // Registered plugin APIs by plugin name
this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used
by plugins
// Create listener
this.listener = this.settings.listener || (this.settings.tls ? Https.createServer(this.settings.tls) : Http.createServer());
this.listener.on('request', this._dispatch());
this._init();
this.listener.on('clientError', (err, socket) => {
this.server._log(['connection', 'client', 'error'], err);
});
// Connection information
this.info = {
created: now,
started: 0,
host: this.settings.host || Os.hostname() || 'localhost',
port: this.settings.port,
protocol: this.type === 'tcp' ? (this.settings.tls ? 'https' : 'http') : this.type,
id: Os.hostname() + ':' + process.pid + ':' + now.toString(36)
};
this.info.uri = (this.settings.uri || (this.info.protocol + ':' + (this.type === 'tcp' ? '//' + this.info.host + (this.info.
port ? ':' + this.info.port : '') : this.info.port)));
this.on('route', Cors.options);
}...
exports = module.exports = internals.Connection = function (server, options) {
const now = Date.now();
Podium.call(this, internals.Connection._events);
this.settings = options; // options cloned in server.connection()
this.server = server;
// Normalize settings
this.settings.labels = Hoek.unique(this.settings.labels || []); // Remove duplicates
if (this.settings.port === undefined) {
this.settings.port = 0;
...ext = function (type, server) {
this._topo = new Topo();
this._server = server;
this._routes = [];
this.type = type;
this.nodes = null;
}n/a
methods = function (server) {
this.server = server;
this.methods = {};
this._normalized = {};
}n/a
protect = function (request) {
this._error = null;
this.logger = request; // Replaced with server when request completes
if (!request.server.settings.useDomains) {
this.domain = null;
return;
}
Domain = Domain || require('domain');
this.domain = Domain.create();
this.domain.on('error', (err) => {
return this._onError(err);
});
}n/a
reply = function () {
this._decorations = null;
}n/a
request = function () {
this._decorations = null;
}...
req.socket.end();
}
});
}
// Create request
const request = this.server._requestor.request(this, req, res, options);
// Check load
const overload = this._load.check();
if (overload) {
this.server._log(['load'], this.server.load);
request._reply(overload);
...response = function (source, request, options) {
Podium.call(this, ['finish', { name: 'peek', spread: true }]);
options = options || {};
this.request = request;
this.statusCode = null;
this.headers = {}; // Incomplete as some headers are stored in flags
this.variety = null;
this.source = null;
this.app = {};
this.plugins = {};
this.send = null; // Set by reply()
this.hold = null; // Set by reply()
this.settings = {
encoding: 'utf8',
charset: 'utf-8', // '-' required by IANA
ttl: null,
stringify: null, // JSON.stringify options
passThrough: true,
varyEtag: false,
message: null
};
this._payload = null; // Readable stream
this._takeover = false;
this._contentEncoding = null; // Set during transmit
this._contentType = null; // Used if no explicit content-type is set and type is known
this._error = null; // The boom object when created from an error
this._processors = {
marshal: options.marshal,
prepare: options.prepare,
close: options.close
};
this._setSource(source, options.variety);
}...
if (!strategy.methods.response) {
return next();
}
request._protect.run(next, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.response(request, reply);
});
};
internals.Authenticator = class {
constructor(config, request, manager) {
...route = function (route, connection, plugin, options) {
options = options || {};
// Apply plugin environment (before schema validation)
const realm = plugin.realm;
if (realm.modifiers.route.vhost ||
realm.modifiers.route.prefix) {
route = Hoek.cloneWithShallow(route, ['config']); // config is left unchanged
route.path = (realm.modifiers.route.prefix ? realm.modifiers.route.prefix + (route.path !== '/' ? route.path : '') : route
.path);
route.vhost = realm.modifiers.route.vhost || route.vhost;
}
// Setup and validate route configuration
Hoek.assert(route.path, 'Route missing path');
const routeDisplay = route.method + ' ' + route.path;
let config = route.config;
if (typeof config === 'function') {
config = config.call(realm.settings.bind, connection.server);
}
Hoek.assert(route.handler || (config && config.handler), 'Missing or undefined handler:', routeDisplay);
Hoek.assert(!!route.handler ^ !!(config && config.handler), 'Handler must only appear once:', routeDisplay); // XOR
Hoek.assert(route.path === '/' || route.path[route.path.length - 1] !== '/' || !connection.settings.router.stripTrailingSlash
, 'Path cannot end with a trailing slash when connection configured to strip:', routeDisplay);
route = Schema.apply('route', route, routeDisplay);
const handler = route.handler || config.handler;
const method = route.method.toLowerCase();
Hoek.assert(method !== 'head', 'Method name not allowed:', routeDisplay);
// Apply settings in order: {connection} <- {handler} <- {realm} <- {route}
const handlerDefaults = Handler.defaults(method, handler, connection.server);
let base = Hoek.applyToDefaultsWithShallow(connection.settings.routes, handlerDefaults, ['bind']);
base = Hoek.applyToDefaultsWithShallow(base, realm.settings, ['bind']);
this.settings = Hoek.applyToDefaultsWithShallow(base, config || {}, ['bind', 'validate.headers', 'validate.payload', 'validate
.params', 'validate.query']);
this.settings.handler = handler;
this.settings = Schema.apply('routeConfig', this.settings, routeDisplay);
const socketTimeout = (this.settings.timeout.socket === undefined ? 2 * 60 * 1000 : this.settings.timeout.socket);
Hoek.assert(!this.settings.timeout.server || !socketTimeout || this.settings.timeout.server < socketTimeout, 'Server timeout
must be shorter than socket timeout:', routeDisplay);
Hoek.assert(!this.settings.payload.timeout || !socketTimeout || this.settings.payload.timeout < socketTimeout, 'Payload timeout
must be shorter than socket timeout:', routeDisplay);
this.connection = connection;
this.server = connection.server;
this.path = route.path;
this.method = method;
this.plugin = plugin;
this.settings.vhost = route.vhost;
this.settings.plugins = this.settings.plugins || {}; // Route-specific plugins settings, namespaced using plugin
name
this.settings.app = this.settings.app || {}; // Route-specific application settings
// Path parsing
this._special = !!options.special;
this._analysis = this.connection._router.analyze(this.path);
this.params = this._analysis.params;
this.fingerprint = this._analysis.fingerprint;
this.public = {
method: this.method,
path: this.path,
vhost: this.vhost,
realm: this.plugin.realm,
settings: this.settings,
fingerprint: this.fingerprint,
auth: {
access: (request) => Auth.access(request, this.public)
}
};
// Validation
const validation = this.settings.validate;
if (this.method === 'get') {
// Assert on config, not on merged settings
Hoek.assert(!config || !config.payload, 'Cannot set payload settings on HEAD or GET request:', routeDisplay);
Hoek.assert(!config || !config.validate || !config.validate.payload, 'Cannot validate HEAD or GET requests:', routeDisplay
);
validation.payload = null;
}
['headers', ' ......
internals.Connection.prototype.match = function (method, path, host) {
Hoek.assert(method && typeof method === 'string', 'Invalid method:', method);
Hoek.assert(path && typeof path === 'string' && path[0] === '/', 'Invalid path:
x27;, path);
Hoek.assert(!host || typeof host === 'string', 'Invalid host:', host);
const match = this._router.route(method.toLowerCase(), path, host);
Hoek.assert(match !== this._router.specials.badRequest, 'Invalid path:', path);
if (match === this._router.specials.notFound) {
return null;
}
return match.route.public;
};
...Server = function (options) {
Hoek.assert(this instanceof internals.Server, 'Server must be instantiated using new');
options = Schema.apply('server', options || {});
this._settings = Hoek.applyToDefaultsWithShallow(Defaults.server, options, ['connections.routes.bind']);
this._settings.connections = Hoek.applyToDefaultsWithShallow(Defaults.connection, this._settings.connections || {}, ['routes
.bind']);
this._settings.connections.routes.cors = Hoek.applyToDefaults(Defaults.cors, this._settings.connections.routes.cors);
this._settings.connections.routes.security = Hoek.applyToDefaults(Defaults.security, this._settings.connections.routes.security
);
this._caches = {}; // Cache clients
this._handlers = {}; // Registered handlers
this._methods = new Methods(this); // Server methods
this._events = new Podium([{ name: 'log', tags: true }, 'start', 'stop']); // Server-only events
this._dependencies = []; // Plugin dependencies
this._registrations = {}; // Tracks plugins registered before connection
added
this._heavy = new Heavy(this._settings.load);
this._mime = new Mimos(this._settings.mime);
this._replier = new Reply();
this._requestor = new Request();
this._decorations = {};
this._plugins = {}; // Exposed plugin properties by name
this._app = {};
this._registring = false; // true while register() is waiting for plugin
callbacks
this._state = 'stopped'; // 'stopped', 'initializing', 'initialized', '
starting', 'started', 'stopping', 'invalid'
this._extensionsSeq = 0; // Used to keep absolute order of extensions
based on the order added across locations
this._extensions = {
onPreStart: new Ext('onPreStart', this),
onPostStart: new Ext('onPostStart', this),
onPreStop: new Ext('onPreStop', this),
onPostStop: new Ext('onPostStop', this)
};
if (options.cache) {
this._createCache(options.cache);
}
if (!this._caches._default) {
this._createCache([{ engine: CatboxMemory }]); // Defaults to memory-based
}
Plugin.call(this, this, [], '', null);
// Subscribe to server log events
if (this._settings.debug) {
const debug = (request, event) => {
const data = event.data;
console.error('Debug:', event.tags.join(', '), (data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify
(data) : data)) : ''));
};
if (this._settings.debug.log) {
this._events.on({ name: 'log', filter: this._settings.debug.log }, (event) => debug(null, event));
}
if (this._settings.debug.request) {
this.on({ name: 'request', filter: this._settings.debug.request }, debug);
this.on({ name: 'request-internal', filter: this._settings.debug.request }, debug);
}
}
}n/a
super_ = function (server, connections, env, parent) { // env can be a realm or plugin name
Podium.call(this, [connections && connections.length ? connections : Connection._events, server._events]);
this._parent = parent;
// Public interface
this.root = server;
this.app = this.root._app;
this.connections = connections;
this.load = this.root._heavy.load;
this.methods = this.root._methods.methods;
this.mime = this.root._mime;
this.plugins = this.root._plugins;
this.settings = this.root._settings;
this.version = Package.version;
this.realm = typeof env !== 'string' ? env : {
_extensions: {
onPreAuth: new Ext('onPreAuth', this.root),
onPostAuth: new Ext('onPostAuth', this.root),
onPreHandler: new Ext('onPreHandler', this.root),
onPostHandler: new Ext('onPostHandler', this.root),
onPreResponse: new Ext('onPreResponse', this.root)
},
modifiers: {
route: {}
},
plugin: env,
pluginOptions: {},
plugins: {},
settings: {
bind: undefined,
files: {
relativeTo: undefined
}
}
};
this.auth = {
default: (opts) => this._applyChild('auth.default', 'auth', 'default', [opts]),
scheme: (name, scheme) => this._applyChild('auth.scheme', 'auth', 'scheme', [name, scheme]),
strategy: (name, scheme, mode, opts) => this._applyChild('auth.strategy', 'auth', 'strategy', [name, scheme, mode, opts]),
test: (name, request, next) => request.connection.auth.test(name, request, next)
};
this.cache.provision = (opts, callback) => {
if (!callback) {
return Promises.wrap(null, this.cache.provision, [opts]);
}
return this.root._createCache(opts, callback);
};
this._single();
// Decorations
const methods = Object.keys(this.root._decorations);
for (let i = 0; i < methods.length; ++i) {
const method = methods[i];
this[method] = this.root._decorations[method];
}
}n/a
_createCache = function (options, _callback) {
Hoek.assert(this._state !== 'initializing', 'Cannot provision server cache while server is initializing');
options = Schema.apply('cache', options);
const added = [];
for (let i = 0; i < options.length; ++i) {
let config = options[i];
if (typeof config === 'function') {
config = { engine: config };
}
const name = config.name || '_default';
Hoek.assert(!this._caches[name], 'Cannot configure the same cache more than once: ', name === '_default' ? 'default cache
' : name);
let client = null;
if (typeof config.engine === 'object') {
client = new Catbox.Client(config.engine);
}
else {
const settings = Hoek.clone(config);
settings.partition = settings.partition || 'hapi-cache';
delete settings.name;
delete settings.engine;
delete settings.shared;
client = new Catbox.Client(config.engine, settings);
}
this._caches[name] = {
client,
segments: {},
shared: config.shared || false
};
added.push(client);
}
if (!_callback) {
return;
}
// Start cache
if (['initialized', 'starting', 'started'].indexOf(this._state) !== -1) {
const each = (client, next) => client.start(next);
return Items.parallel(added, each, _callback);
}
return Hoek.nextTick(_callback)();
}n/a
_invoke = function (type, next) {
const exts = this._extensions[type];
if (!exts.nodes) {
return next();
}
Items.serial(exts.nodes, (ext, nextExt) => {
const bind = (ext.bind || ext.plugin.realm.settings.bind);
ext.func.call(bind, ext.plugin._select(), nextExt);
}, next);
}...
// Execute onRequest extensions (can change request method and url)
if (!this.connection._extensions.onRequest.nodes) {
return this._lifecycle();
}
this._invoke(this.connection._extensions.onRequest, (err) => {
return this._lifecycle(err);
});
};
internals.Request.prototype._lifecycle = function (err) {
..._start = function (callback) {
this._state = 'starting';
const each = (connection, next) => connection._start(next);
Items.parallel(this.connections, each, (err) => {
if (err) {
this._state = 'invalid';
return Hoek.nextTick(callback)(err);
}
this._events.emit('start', null, () => {
this._invoke('onPostStart', (err) => {
if (err) {
this._state = 'invalid';
return callback(err);
}
this._state = 'started';
return callback();
});
});
});
}n/a
_validateDeps = function () {
for (let i = 0; i < this._dependencies.length; ++i) {
const dependency = this._dependencies[i];
if (dependency.connections) {
for (let j = 0; j < dependency.connections.length; ++j) {
const connection = dependency.connections[j];
for (let k = 0; k < dependency.deps.length; ++k) {
const dep = dependency.deps[k];
if (!connection.registrations[dep]) {
return new Error('Plugin ' + dependency.plugin + ' missing dependency ' + dep + ' in connection: ' + connection
.info.uri);
}
}
}
}
else {
for (let j = 0; j < dependency.deps.length; ++j) {
const dep = dependency.deps[j];
if (!this._registrations[dep]) {
return new Error('Plugin ' + dependency.plugin + ' missing dependency ' + dep);
}
}
}
}
return null;
}n/a
connection = function (options) {
const root = this.root; // Explicitly use the root reference (for plugin invocation)
const connections = [];
[].concat(options).forEach((item) => {
let settings = Hoek.applyToDefaultsWithShallow(root._settings.connections, item || {}, ['listener', 'routes.bind']);
settings.routes.cors = Hoek.applyToDefaults(root._settings.connections.routes.cors || Defaults.cors, settings.routes.cors
) || false;
settings.routes.security = Hoek.applyToDefaults(root._settings.connections.routes.security || Defaults.security, settings
.routes.security);
settings = Schema.apply('connection', settings); // Applies validation changes (type cast)
const connection = new Connection(root, settings);
root.connections.push(connection);
root.registerPodium(connection);
root._single();
const registrations = Object.keys(root._registrations);
for (let i = 0; i < registrations.length; ++i) {
const name = registrations[i];
connection.registrations[name] = root._registrations[name];
}
connections.push(connection);
});
return this._clone(connections); // Use this for active realm
}...
exports = module.exports = internals.Connection = function (server, options) {
const now = Date.now();
Podium.call(this, internals.Connection._events);
this.settings = options; // options cloned in server.connection()
this.server = server;
// Normalize settings
this.settings.labels = Hoek.unique(this.settings.labels || []); // Remove duplicates
if (this.settings.port === undefined) {
this.settings.port = 0;
...initialize = function (callback) {
if (!callback) {
return Promises.wrap(this, this.initialize);
}
Hoek.assert(typeof callback === 'function', 'Missing required start callback function');
const nextTickCallback = Hoek.nextTick(callback);
if (this._registring) {
return nextTickCallback(new Error('Cannot start server before plugins finished registration'));
}
if (this._state === 'initialized') {
return nextTickCallback();
}
if (this._state !== 'stopped') {
return nextTickCallback(new Error('Cannot initialize server while it is in ' + this._state + ' state'));
}
const error = this._validateDeps();
if (error) {
return nextTickCallback(error);
}
this._state = 'initializing';
// Start cache
const caches = Object.keys(this._caches);
const each = (cache, next) => this._caches[cache].client.start(next);
Items.parallel(caches, each, (err) => {
if (err) {
this._state = 'invalid';
return callback(err);
}
// After hooks
this._invoke('onPreStart', (err) => {
if (err) {
this._state = 'invalid';
return callback(err);
}
// Load measurements
this._heavy.start();
// Listen to connections
this._state = 'initialized';
return callback();
});
});
}n/a
start = function (callback) {
if (!callback) {
return Promises.wrap(this, this.start);
}
Hoek.assert(typeof callback === 'function', 'Missing required start callback function');
const nextTickCallback = Hoek.nextTick(callback);
if (!this.connections.length) {
return nextTickCallback(new Error('No connections to start'));
}
if (this._state === 'initialized' ||
this._state === 'started') {
const error = this._validateDeps();
if (error) {
return nextTickCallback(error);
}
}
if (this._state === 'initialized') {
return this._start(callback);
}
if (this._state === 'started') {
const each = (connection, next) => connection._start(next);
return Items.parallel(this.connections, each, nextTickCallback);
}
if (this._state !== 'stopped') {
return nextTickCallback(new Error('Cannot start server while it is in ' + this._state + ' state'));
}
this.initialize((err) => {
if (err) {
return callback(err);
}
this._start(callback);
});
}n/a
stop = function () {
const args = arguments.length;
const lastArg = arguments[args - 1];
const callback = (!args ? null : (typeof lastArg === 'function' ? lastArg : null));
const options = (!args ? {} : (args === 1 ? (callback ? {} : arguments[0]) : arguments[0]));
if (!callback) {
return Promises.wrap(this, this.stop, [options]);
}
options.timeout = options.timeout || 5000; // Default timeout to 5 seconds
if (['stopped', 'initialized', 'started', 'invalid'].indexOf(this._state) === -1) {
return Hoek.nextTick(callback)(new Error('Cannot stop server while in ' + this._state + ' state'));
}
this._state = 'stopping';
this._invoke('onPreStop', (err) => {
if (err) {
this._state = 'invalid';
return callback(err);
}
const each = (connection, next) => connection._stop(options, next);
Items.parallel(this.connections, each, (err) => {
if (err) {
this._state = 'invalid';
return callback(err);
}
const caches = Object.keys(this._caches);
for (let i = 0; i < caches.length; ++i) {
this._caches[caches[i]].client.stop();
}
this._events.emit('stop', null, () => {
this._heavy.stop();
this._invoke('onPostStop', (err) => {
if (err) {
this._state = 'invalid';
return callback(err);
}
this._state = 'stopped';
return callback();
});
});
});
});
}n/a
super_ = function (events) {
// Use descriptive names to avoid conflict when inherited
this._eventListeners = Object.create(null);
this._notificationsQueue = [];
this._eventsProcessing = false;
this._sourcePodiums = [];
if (events) {
this.registerEvent(events);
}
}n/a
_apply = function (type, func, args) {
Hoek.assert(this.connections, 'Cannot add ' + type + ' from a connectionless plugin');
Hoek.assert(this.connections.length, 'Cannot add ' + type + ' without a connection');
for (let i = 0; i < this.connections.length; ++i) {
func.apply(this.connections[i], args);
}
}n/a
_applyChild = function (type, child, func, args) {
Hoek.assert(this.connections, 'Cannot add ' + type + ' from a connectionless plugin');
Hoek.assert(this.connections.length, 'Cannot add ' + type + ' without a connection');
for (let i = 0; i < this.connections.length; ++i) {
const obj = this.connections[i][child];
obj[func].apply(obj, args);
}
}n/a
_clone = function (connections, plugin) {
const env = (plugin !== undefined ? plugin : this.realm); // Allow empty string
return new internals.Plugin(this.root, connections, env, this);
}...
Hoek.assert(name, 'Authentication strategy must have a name');
Hoek.assert(name !== 'bypass', 'Cannot use reserved strategy name: bypass');
Hoek.assert(!this._strategies[name], 'Authentication strategy name already exists');
Hoek.assert(scheme, 'Authentication strategy', name, 'missing scheme');
Hoek.assert(this._schemes[scheme], 'Authentication strategy', name, 'uses unknown scheme:', scheme);
const server = this.connection.server._clone([this.connection], '');
const strategy = this._schemes[scheme](server, options);
Hoek.assert(strategy.authenticate, 'Invalid scheme:', name, 'missing authenticate() method');
Hoek.assert(typeof strategy.authenticate === 'function', 'Invalid scheme:', name, 'invalid authenticate
() method');
Hoek.assert(!strategy.payload || typeof strategy.payload === 'function', 'Invalid scheme:', name, 'invalid
payload() method');
Hoek.assert(!strategy.response || typeof strategy.response === 'function', 'Invalid scheme:', name, 'invalid
response() method');
strategy.options = strategy.options || {};
..._ext = function (event) {
event = Hoek.shallow(event);
event.plugin = this;
const type = event.type;
if (!this.root._extensions[type]) {
// Realm route extensions
if (event.options.sandbox === 'plugin') {
Hoek.assert(this.realm._extensions[type], 'Unknown event type', type);
return this.realm._extensions[type].add(event);
}
// Connection route extensions
return this._apply('ext', Connection.prototype._ext, [event]);
}
// Server extensions
Hoek.assert(!event.options.sandbox, 'Cannot specify sandbox option for server extension');
Hoek.assert(type !== 'onPreStart' || this.root._state === 'stopped', 'Cannot add onPreStart (after) extension after the server
was initialized');
this.root._extensions[type].add(event);
}n/a
_log = function (tags, data) {
return this.log(tags, data, null, true);
}...
if (config.mode === 'optional' ||
config.mode === 'try') {
request.auth.isAuthenticated = false;
request.auth.credentials = null;
request.auth.error = err;
request._log(['auth', 'unauthenticated']);
return next();
}
return next(err);
}
validate(err, result, next) { // err can be Boom, Error, or a valid response object
..._select = function (labels, plugin) {
let connections = this.connections;
if (labels &&
labels.length) { // Captures both empty arrays and empty strings
Hoek.assert(this.connections, 'Cannot select inside a connectionless plugin');
connections = [];
for (let i = 0; i < this.connections.length; ++i) {
const connection = this.connections[i];
if (Hoek.intersect(connection.settings.labels, labels).length) {
connections.push(connection);
}
}
if (!plugin &&
connections.length === this.connections.length) {
return this;
}
}
const env = (plugin !== undefined ? plugin : this.realm); // Allow empty string
return new internals.Plugin(this.root, connections, env, this);
}n/a
_single = function () {
if (this.connections &&
this.connections.length === 1) {
this.info = this.connections[0].info;
this.listener = this.connections[0].listener;
this.registrations = this.connections[0].registrations;
this.auth.api = this.connections[0].auth.api;
}
else {
this.info = null;
this.listener = null;
this.registrations = null;
this.auth.api = null;
}
}n/a
bind = function (context) {
Hoek.assert(typeof context === 'object', 'bind must be an object');
this.realm.settings.bind = context;
}...
Items.serial(request._route._prerequisites, each, (err) => {
if (err) {
return callback(err);
}
const wrapped = domain ? domain.bind(internals.handler) : internals.handler;
return wrapped(request, callback);
});
};
internals.handler = function (request, callback) {
...cache = function (options, _segment) {
options = Schema.apply('cachePolicy', options);
const segment = options.segment || _segment || (this.realm.plugin ? '!' + this.realm.plugin : '');
Hoek.assert(segment, 'Missing cache segment name');
const cacheName = options.cache || '_default';
const cache = this.root._caches[cacheName];
Hoek.assert(cache, 'Unknown cache', cacheName);
Hoek.assert(!cache.segments[segment] || cache.shared || options.shared, 'Cannot provision the same cache segment more than once
');
cache.segments[segment] = true;
return new Catbox.Policy(options, cache.client, segment);
}...
settings.cache.generateFunc = (id, next) => {
id.args.push(next); // function (err, result, ttl)
normalized.apply(bind, id.args);
};
const cache = this.server.cache(settings.cache, '#' + name);
const func = function (/* arguments, methodNext */) {
const args = [];
for (let i = 0; i < arguments.length - 1; ++i) {
args.push(arguments[i]);
}
...decoder = function (encoding, decoder) {
this._apply('decoder', Connection.prototype.decoder, [encoding, decoder]);
}n/a
decorate = function (type, property, method, options) {
Hoek.assert(['reply', 'request', 'server'].indexOf(type) !== -1, 'Unknown decoration type:', type);
Hoek.assert(property, 'Missing decoration property name');
Hoek.assert(typeof property === 'string', 'Decoration property must be a string');
Hoek.assert(property[0] !== '_', 'Property name cannot begin with an underscore:', property);
// Request
if (type === 'request') {
return this.root._requestor.decorate(property, method, options);
}
Hoek.assert(!options, 'Cannot specify options for non-request decoration');
// Reply
if (type === 'reply') {
return this.root._replier.decorate(property, method);
}
// Server
Hoek.assert(!this.root._decorations[property], 'Server decoration already defined:', property);
Hoek.assert(this[property] === undefined && this.root[property] === undefined, 'Cannot override the built-in server interface
method:', property);
this.root._decorations[property] = method;
this[property] = method;
let parent = this._parent;
while (parent) {
parent[property] = method;
parent = parent._parent;
}
}n/a
dependency = function (dependencies, after) {
Hoek.assert(this.realm.plugin, 'Cannot call dependency() outside of a plugin');
Hoek.assert(!after || typeof after === 'function', 'Invalid after method');
dependencies = [].concat(dependencies);
this.root._dependencies.push({ plugin: this.realm.plugin, connections: this.connections, deps: dependencies });
if (after) {
this.ext('onPreStart', after, { after: dependencies });
}
}n/a
emit = function (criteria, data, callback) {
this.root._events.emit(criteria, data, callback);
}...
for (let i = 0; i < vhosts.length; ++i) {
const vhost = vhosts[i];
const record = this._router.add({ method: route.method, path: route.path, vhost, analysis: route._analysis, id: route.settings
.id }, route);
route.fingerprint = record.fingerprint;
route.params = record.params;
}
this.emit('route', [route.public, this, plugin]);
};
internals.Connection.prototype._defaultRoutes = function () {
this._router.special('notFound', new Route({ method: '_special', path: '/{p*}', handler: internals
.notFound }, this, this.server, { special: true }));
this._router.special('badRequest', new Route({ method: '_special', path: '/{p*}', handler: internals
.badRequest }, this, this.server, { special: true }));
...encoder = function (encoding, encoder) {
this._apply('encoder', Connection.prototype.encoder, [encoding, encoder]);
}...
length !== 0 &&
response.statusCode !== 206 &&
response._isPayloadSupported()) {
delete response.headers['content-length'];
response._header('content-encoding', encoding);
compressor = request.connection._compression.encoder(request, encoding);
}
if ((response.headers['content-encoding'] || encoding) &&
response.headers.etag &&
response.settings.varyEtag) {
response.headers.etag = response.headers.etag.slice(0, -1) + '-' + (response.headers['content-encoding'] ||
encoding) + '"';
...event = function (event) {
this.root._events.registerEvent(event);
}n/a
expose = function (key, value) {
Hoek.assert(this.realm.plugin, 'Cannot call expose() outside of a plugin');
const plugin = this.realm.plugin;
this.root.plugins[plugin] = this.root.plugins[plugin] || {};
if (typeof key === 'string') {
this.root.plugins[plugin][key] = value;
}
else {
Hoek.merge(this.root.plugins[plugin], key);
}
}n/a
ext = function (events) { // (event, method, options) -OR- (events)
if (typeof events === 'string') {
events = { type: arguments[0], method: arguments[1], options: arguments[2] };
}
events = Schema.apply('exts', events);
for (let i = 0; i < events.length; ++i) {
this._ext(events[i]);
}
}n/a
handler = function (name, method) {
Hoek.assert(typeof name === 'string', 'Invalid handler name');
Hoek.assert(!this.root._handlers[name], 'Handler name already exists:', name);
Hoek.assert(typeof method === 'function', 'Handler must be a function:', name);
Hoek.assert(!method.defaults || typeof method.defaults === 'object' || typeof method.defaults === 'function', 'Handler defaults
property must be an object or function');
this.root._handlers[name] = method;
}...
internals.Connection.prototype._defaultRoutes = function () {
this._router.special('notFound', new Route({ method: '_special', path: '/{p*}', handler: internals
.notFound }, this, this.server, { special: true }));
this._router.special('badRequest', new Route({ method: '_special', path: '/{p*}', handler: internals
.badRequest }, this, this.server, { special: true }));
if (this.settings.routes.cors) {
Cors.handler(this);
}
};
internals.notFound = function (request, reply) {
return reply(Boom.notFound());
...inject = function (options, callback) {
Hoek.assert(this.connections.length === 1, 'Method not available when the selection has more than one connection or none');
return this.connections[0].inject(options, callback);
}...
};
};
internals.Connection.prototype.inject = function (options, callback) {
if (!callback) {
return new Promise((resolve, reject) => this.inject(options, (res) => resolve
(res)));
}
let settings = options;
if (typeof settings === 'string') {
settings = { url: settings };
}
...log = function (tags, data, timestamp, _internal) {
tags = [].concat(tags);
timestamp = (timestamp ? (timestamp instanceof Date ? timestamp.getTime() : timestamp) : Date.now());
const internal = !!_internal;
const update = (typeof data !== 'function' ? { timestamp, tags, data, internal } : () => {
return { timestamp, tags, data: data(), internal };
});
this.root._events.emit({ name: 'log', tags }, update);
}...
this.connection.emit({ name: internal ? 'request-internal' : 'request', tags }, update);
};
internals.Request.prototype._log = function (tags, data) {
return this.log(tags, data, null, true);
};
internals.Request.prototype.getLog = function (tags, internal) {
Hoek.assert(this.route.settings.log, 'Request logging is disabled');
...lookup = function (id) {
Hoek.assert(this.connections.length === 1, 'Method not available when the selection has more than one connection or none');
return this.connections[0].lookup(id);
}...
return auth._authenticate(request, next);
};
internals.Auth.access = function (request, route) {
const auth = request.connection.auth;
const config = auth.lookup(route);
if (!config) {
return true;
}
const credentials = request.auth.credentials;
if (!credentials) {
return false;
...match = function (method, path, host) {
Hoek.assert(this.connections.length === 1, 'Method not available when the selection has more than one connection or none');
return this.connections[0].match(method, path, host);
}...
const method = request.headers['access-control-request-method'];
if (!method) {
return reply(Boom.notFound('CORS error: Missing Access-Control-Request-Method header'));
}
// Lookup route
const route = request.connection.match(method, request.path, request.info.hostname);
if (!route) {
return reply(Boom.notFound());
}
const settings = route.settings.cors;
if (!settings) {
return reply({ message: 'CORS is disabled for this route' });
...method = function (name, method, options) {
return this.root._methods.add(name, method, options, this.realm);
}...
// Decorate
if (this._decorations) {
const properties = Object.keys(this._decorations);
for (let i = 0; i < properties.length; ++i) {
const property = properties[i];
const assignment = this._decorations[property];
request[property] = (assignment.apply ? assignment.method(request) : assignment
.method);
}
}
return request;
};
...path = function (relativeTo) {
Hoek.assert(relativeTo && typeof relativeTo === 'string', 'relativeTo must be a non-empty string');
this.realm.settings.files.relativeTo = relativeTo;
}n/a
register = function (plugins) {
let options = (typeof arguments[1] === 'object' ? arguments[1] : {});
const callback = (typeof arguments[1] === 'object' ? arguments[2] : arguments[1]);
if (!callback) {
return Promises.wrap(this, this.register, [plugins, options]);
}
if (this.realm.modifiers.route.prefix ||
this.realm.modifiers.route.vhost) {
options = Hoek.clone(options);
options.routes = options.routes || {};
options.routes.prefix = (this.realm.modifiers.route.prefix || '') + (options.routes.prefix || '') || undefined;
options.routes.vhost = this.realm.modifiers.route.vhost || options.routes.vhost;
}
options = Schema.apply('register', options);
/*
const register = function (server, options, next) { return next(); };
register.attributes = {
pkg: require('../package.json'),
name: 'plugin',
version: '1.1.1',
multiple: false,
dependencies: [],
connections: false,
once: true
};
const item = {
register: register,
options: options // -optional--
};
- OR -
const item = function () {}
item.register = register;
item.options = options;
const plugins = register, items, [register, item]
*/
const registrations = [];
plugins = [].concat(plugins);
for (let i = 0; i < plugins.length; ++i) {
let plugin = plugins[i];
if (typeof plugin === 'function') {
if (!plugin.register) { // plugin is register() function
plugin = { register: plugin };
}
else {
plugin = Hoek.shallow(plugin); // Convert function to object
}
}
if (plugin.register.register) { // Required plugin
plugin.register = plugin.register.register;
}
plugin = Schema.apply('plugin', plugin);
const attributes = plugin.register.attributes;
const registration = {
register: plugin.register,
name: attributes.name || attributes.pkg.name,
version: attributes.version || attributes.pkg.version,
multiple: attributes.multiple,
pluginOptions: plugin.options,
dependencies: attributes.dependencies,
connections: attributes.connections,
options: {
once: attributes.once || (plugin.once !== undefined ? plugin.once : options.once),
routes: {
prefix: plugin.routes.prefix || options.routes.prefix,
vhost: plugin.routes.vhost || options.routes.vhost
},
select: plugin.select || options.select
}
};
registrations.push(registration);
}
this.root._registring = true;
const each = (item, next) => {
const selection = this._select(item.options.select, item.name);
selection.realm.modifiers.route.prefix = item.options.routes.prefix;
selection.realm.modifiers.route.vhost = item.options.routes.vhost;
selection.realm.pluginOptions = item.pluginOptions || {};
const registrationData = {
version: item.version,
name: item.name,
options: item.pluginOptions,
attributes: item.register.attributes
};
// Protect against multiple registrations
const connectionless = (item.connections === 'conditional' ? selection.connections.length === 0 : !item.connections);
if (connectionless) {
if (this.root._registrations[item.name]) {
if (item.options.once) {
return next();
}
Hoek.assert(item.multiple, 'Plugin', item.name, 'already registered');
}
else {
this.root._registrations[item.name] = registrationData; ...n/a
route = function (options) {
Hoek.assert(arguments.length === 1, 'Method requires a single object argument or a single array of objects');
Hoek.assert(typeof options === 'object', 'Invalid route options');
Hoek.assert(this.connections, 'Cannot add route from a connectionless plugin');
Hoek.assert(this.connections.length, 'Cannot add a route without any connections');
this._apply('route', Connection.prototype._route, [options, this]);
}...
internals.Connection.prototype.match = function (method, path, host) {
Hoek.assert(method && typeof method === 'string', 'Invalid method:', method);
Hoek.assert(path && typeof path === 'string' && path[0] === '/', 'Invalid path:
x27;, path);
Hoek.assert(!host || typeof host === 'string', 'Invalid host:', host);
const match = this._router.route(method.toLowerCase(), path, host);
Hoek.assert(match !== this._router.specials.badRequest, 'Invalid path:', path);
if (match === this._router.specials.notFound) {
return null;
}
return match.route.public;
};
...select = function () {
let labels = [];
for (let i = 0; i < arguments.length; ++i) {
labels.push(arguments[i]);
}
labels = Hoek.flatten(labels);
return this._select(labels);
}n/a
state = function (name, options) {
this._applyChild('state', 'states', 'add', [name, options]);
}...
const response = request.response;
Cors.headers(response);
internals.content(response, false);
internals.security(response);
internals.unmodified(response);
internals.state(response, (err) => {
if (err) {
request._log(['state', 'response', 'error'], err);
request._states = {}; // Clear broken state
return next(err);
}
...table = function (host) {
Hoek.assert(this.connections, 'Cannot request routing table from a connectionless plugin');
const table = [];
for (let i = 0; i < this.connections.length; ++i) {
const connection = this.connections[i];
table.push({ info: connection.info, labels: connection.settings.labels, table: connection.table(host) });
}
return table;
}...
return callback(res);
});
};
internals.Connection.prototype.table = function (host) {
return this._router.table(host);
};
internals.Connection.prototype.lookup = function (id) {
Hoek.assert(id && typeof id === 'string', 'Invalid route id:', id);
..._emit = function (criteria, data, generated, callback) {
criteria = internals.criteria(criteria);
const name = criteria.name;
Hoek.assert(name, 'Criteria missing event name');
const event = this._eventListeners[name];
Hoek.assert(event, `Unknown event ${name}`);
Hoek.assert(!event.flags.spread || Array.isArray(data) || typeof data === 'function', 'Data must be an array for spread event
');
Hoek.assert(!criteria.channel || typeof criteria.channel === 'string', 'Invalid channel name');
Hoek.assert(!criteria.channel || !event.flags.channels || event.flags.channels.indexOf(criteria.channel) !== -1, `Unknown ${
criteria.channel} channel`);
if (typeof criteria.tags === 'string') {
criteria.tags = [criteria.tags];
}
if (criteria.tags &&
Array.isArray(criteria.tags)) {
criteria.tags = Hoek.mapToObject(criteria.tags);
}
internals.emit(this, { criteria, data, callback, generated });
}n/a
addListener = function (criteria, listener) {
criteria = internals.criteria(criteria);
criteria.listener = listener;
if (criteria.filter &&
(typeof criteria.filter === 'string' || Array.isArray(criteria.filter))) {
criteria.filter = { tags: criteria.filter };
}
criteria = Joi.attempt(criteria, internals.schema.listener, 'Invalid event listener options');
const name = criteria.name;
const event = this._eventListeners[name];
Hoek.assert(event, `Unknown event ${name}`);
Hoek.assert(!criteria.channels || !event.flags.channels || Hoek.intersect(event.flags.channels, criteria.channels).length ===
criteria.channels.length, `Unknown event channels ${criteria.channels && criteria.channels.join(', ')}`);
this._eventListeners[name].handlers = this._eventListeners[name].handlers || [];
this._eventListeners[name].handlers.push(criteria);
return this;
}n/a
emit = function (criteria, data, callback) {
return this._emit(criteria, data, false, callback);
}...
for (let i = 0; i < vhosts.length; ++i) {
const vhost = vhosts[i];
const record = this._router.add({ method: route.method, path: route.path, vhost, analysis: route._analysis, id: route.settings
.id }, route);
route.fingerprint = record.fingerprint;
route.params = record.params;
}
this.emit('route', [route.public, this, plugin]);
};
internals.Connection.prototype._defaultRoutes = function () {
this._router.special('notFound', new Route({ method: '_special', path: '/{p*}', handler: internals
.notFound }, this, this.server, { special: true }));
this._router.special('badRequest', new Route({ method: '_special', path: '/{p*}', handler: internals
.badRequest }, this, this.server, { special: true }));
...hasListeners = function (name) {
Hoek.assert(this._eventListeners[name], `Unknown event ${name}`);
return !!this._eventListeners[name].handlers;
}...
this._states[name] = state;
};
internals.Request.prototype._tap = function () {
return (this.hasListeners('finish') || this.hasListeners('peek'
;) ? new Response.Peek(this) : null);
};
internals.Request.prototype.generateResponse = function (source, options) {
return new Response(source, this, options);
};
...on = function (criteria, listener) {
criteria = internals.criteria(criteria);
criteria.listener = listener;
if (criteria.filter &&
(typeof criteria.filter === 'string' || Array.isArray(criteria.filter))) {
criteria.filter = { tags: criteria.filter };
}
criteria = Joi.attempt(criteria, internals.schema.listener, 'Invalid event listener options');
const name = criteria.name;
const event = this._eventListeners[name];
Hoek.assert(event, `Unknown event ${name}`);
Hoek.assert(!criteria.channels || !event.flags.channels || Hoek.intersect(event.flags.channels, criteria.channels).length ===
criteria.channels.length, `Unknown event channels ${criteria.channels && criteria.channels.join(', ')}`);
this._eventListeners[name].handlers = this._eventListeners[name].handlers || [];
this._eventListeners[name].handlers.push(criteria);
return this;
}...
this.plugins = {}; // Registered plugin APIs by plugin name
this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins
// Create listener
this.listener = this.settings.listener || (this.settings.tls ? Https.createServer(this.settings.tls) : Http.createServer());
this.listener.on('request', this._dispatch());
this._init();
this.listener.on('clientError', (err, socket) => {
this.server._log(['connection', 'client', 'error'], err);
});
...once = function (criteria, listener) {
criteria = internals.criteria(criteria);
return this.on(Object.assign(criteria, { count: 1 }), listener);
}...
internals.Connection.prototype._init = function () {
// Setup listener
this.listener.once('listening', () => {
// Update the address, port, and uri with active values
if (this.type === 'tcp') {
const address = this.listener.address();
this.info.address = address.address;
this.info.port = address.port;
...registerEvent = function (events) {
events = Hoek.flatten([].concat(events));
events.forEach((event) => {
if (!event) {
return;
}
if (event instanceof internals.Podium) {
return this.registerPodium(event);
}
if (typeof event === 'string') {
event = { name: event };
}
event = Joi.attempt(event, internals.schema.event, 'Invalid event options');
const name = event.name;
if (this._eventListeners[name]) {
Hoek.assert(event.shared, `Event ${name} exists`);
return;
}
this._eventListeners[name] = { handlers: null, flags: event };
this._sourcePodiums.forEach((podium) => {
if (!podium._eventListeners[name]) {
podium._eventListeners[name] = { handlers: null, flags: event };
}
});
});
}n/a
registerPodium = function (podiums) {
[].concat(podiums).forEach((podium) => {
if (podium._sourcePodiums.indexOf(this) !== -1) {
return;
}
podium._sourcePodiums.push(this);
Object.keys(podium._eventListeners).forEach((name) => {
if (!this._eventListeners[name]) {
this._eventListeners[name] = { handlers: null, flags: podium._eventListeners[name].flags };
}
});
});
}n/a
removeAllListeners = function (name) {
Hoek.assert(this._eventListeners[name], `Unknown event ${name}`);
this._eventListeners[name].handlers = null;
return this;
}n/a
removeListener = function (name, listener) {
Hoek.assert(this._eventListeners[name], `Unknown event ${name}`);
Hoek.assert(typeof listener === 'function', 'Listener must be a function');
const handlers = this._eventListeners[name].handlers;
if (!handlers) {
return this;
}
const filtered = handlers.filter((handler) => handler.listener !== listener);
this._eventListeners[name].handlers = (filtered.length ? filtered : null);
return this;
}...
return callback(err);
};
this.listener.once('error', onError);
const finalize = () => {
this.listener.removeListener('error', onError);
callback();
};
if (this.type !== 'tcp') {
this.listener.listen(this.settings.port, finalize);
}
else {
...auth = function (connection) {
this.connection = connection;
this._schemes = {};
this._strategies = {};
this.settings = {
default: null // Strategy used as default if route has no auth settings
};
this.api = {};
}n/a
access = function (request, route) {
const auth = request.connection.auth;
const config = auth.lookup(route);
if (!config) {
return true;
}
const credentials = request.auth.credentials;
if (!credentials) {
return false;
}
return !internals.access(request, config, credentials, 'bypass');
}...
}
const credentials = request.auth.credentials;
if (!credentials) {
return false;
}
return !internals.access(request, config, credentials, 'bypass');
};
internals.Auth.prototype._authenticate = function (request, next) {
const config = this.lookup(request.route);
if (!config) {
...authenticate = function (request, next) {
const auth = request.connection.auth;
return auth._authenticate(request, next);
}...
internals.Auth.prototype.test = function (name, request, next) {
Hoek.assert(name, 'Missing authentication strategy name');
const strategy = this._strategies[name];
Hoek.assert(strategy, 'Unknown authentication strategy:', name);
const reply = request.server._replier.interface(request, strategy.realm, { data: true }, (response) => next(response, reply
._data && reply._data.credentials));
strategy.methods.authenticate(request, reply);
};
internals.Auth.prototype._setupRoute = function (options, path) {
if (!options) {
return options; // Preserve the difference between undefined and false
...payload = function (request, next) {
if (!request.auth.isAuthenticated ||
request.auth.strategy === 'bypass') {
return next();
}
const auth = request.connection.auth;
const strategy = auth._strategies[request.auth.strategy];
if (!strategy.methods.payload) {
return next();
}
const config = auth.lookup(request.route);
const setting = config.payload || (strategy.methods.options.payload ? 'required' : false);
if (!setting) {
return next();
}
const finalize = (response) => {
if (response &&
response.isBoom &&
response.isMissing) {
return next(setting === 'optional' ? null : Boom.unauthorized('Missing payload authentication'));
}
return next(response);
};
request._protect.run(finalize, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.payload(request, reply);
});
}...
return next(response);
};
request._protect.run(finalize, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.payload(request, reply);
});
};
internals.Auth.response = function (request, next) {
const auth = request.connection.auth;
...response = function (request, next) {
const auth = request.connection.auth;
const config = auth.lookup(request.route);
if (!config ||
!request.auth.isAuthenticated ||
request.auth.strategy === 'bypass') {
return next();
}
const strategy = auth._strategies[request.auth.strategy];
if (!strategy.methods.response) {
return next();
}
request._protect.run(next, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.response(request, reply);
});
}...
if (!strategy.methods.response) {
return next();
}
request._protect.run(next, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.response(request, reply);
});
};
internals.Authenticator = class {
constructor(config, request, manager) {
..._authenticate = function (request, next) {
const config = this.lookup(request.route);
if (!config) {
return next();
}
const authenticator = new internals.Authenticator(config, request, this);
authenticator.authenticate(next);
}...
return route.settings.auth || this.settings.default;
};
internals.Auth.authenticate = function (request, next) {
const auth = request.connection.auth;
return auth._authenticate(request, next);
};
internals.Auth.access = function (request, route) {
const auth = request.connection.auth;
const config = auth.lookup(route);
..._setupRoute = function (options, path) {
if (!options) {
return options; // Preserve the difference between undefined and false
}
if (typeof options === 'string') {
options = { strategies: [options] };
}
else if (options.strategy) {
options.strategies = [options.strategy];
delete options.strategy;
}
if (path &&
!options.strategies) {
Hoek.assert(this.settings.default, 'Route missing authentication strategy and no default defined:', path);
options = Hoek.applyToDefaults(this.settings.default, options);
}
path = path || 'default strategy';
Hoek.assert(options.strategies && options.strategies.length, 'Missing authentication strategy:', path);
options.mode = options.mode || 'required';
if (options.entity !== undefined || // Backwards compatibility with <= 11.x.x
options.scope !== undefined) {
options.access = [{ entity: options.entity, scope: options.scope }];
delete options.entity;
delete options.scope;
}
if (options.access) {
for (let i = 0; i < options.access.length; ++i) {
const access = options.access[i];
access.scope = internals.setupScope(access);
}
}
if (options.payload === true) {
options.payload = 'required';
}
let hasAuthenticatePayload = false;
for (let i = 0; i < options.strategies.length; ++i) {
const name = options.strategies[i];
const strategy = this._strategies[name];
Hoek.assert(strategy, 'Unknown authentication strategy', name, 'in', path);
Hoek.assert(strategy.methods.payload || options.payload !== 'required', 'Payload validation can only be required when all
strategies support it in', path);
hasAuthenticatePayload = hasAuthenticatePayload || strategy.methods.payload;
Hoek.assert(!strategy.methods.options.payload || options.payload === undefined || options.payload === 'required', 'Cannot
set authentication payload to', options.payload, 'when a strategy requires payload validation in', path);
}
Hoek.assert(!options.payload || hasAuthenticatePayload, 'Payload authentication requires at least one strategy with payload
support in', path);
return options;
}...
internals.Auth.prototype.default = function (options) {
Hoek.assert(!this.settings.default, 'Cannot set default strategy more than once');
options = Schema.apply('auth', options, 'default strategy');
this.settings.default = this._setupRoute(Hoek.clone(options)); // Can change options
};
internals.Auth.prototype.test = function (name, request, next) {
Hoek.assert(name, 'Missing authentication strategy name');
const strategy = this._strategies[name];
...default = function (options) {
Hoek.assert(!this.settings.default, 'Cannot set default strategy more than once');
options = Schema.apply('auth', options, 'default strategy');
this.settings.default = this._setupRoute(Hoek.clone(options)); // Can change options
}...
};
if (strategy.api) {
this.api[name] = strategy.api;
}
if (mode) {
this.default({ strategies: [name], mode: mode === true ? 'required' : mode
});
}
};
internals.Auth.prototype.default = function (options) {
Hoek.assert(!this.settings.default, 'Cannot set default strategy more than once');
...lookup = function (route) {
if (route.settings.auth === false) {
return false;
}
return route.settings.auth || this.settings.default;
}...
return auth._authenticate(request, next);
};
internals.Auth.access = function (request, route) {
const auth = request.connection.auth;
const config = auth.lookup(route);
if (!config) {
return true;
}
const credentials = request.auth.credentials;
if (!credentials) {
return false;
...scheme = function (name, scheme) {
Hoek.assert(name, 'Authentication scheme must have a name');
Hoek.assert(!this._schemes[name], 'Authentication scheme name already exists:', name);
Hoek.assert(typeof scheme === 'function', 'scheme must be a function:', name);
this._schemes[name] = scheme;
}n/a
strategy = function (name, scheme) {
const hasMode = (typeof arguments[2] === 'string' || typeof arguments[2] === 'boolean');
const mode = (hasMode ? arguments[2] : false);
const options = (hasMode ? arguments[3] : arguments[2]) || null;
Hoek.assert(name, 'Authentication strategy must have a name');
Hoek.assert(name !== 'bypass', 'Cannot use reserved strategy name: bypass');
Hoek.assert(!this._strategies[name], 'Authentication strategy name already exists');
Hoek.assert(scheme, 'Authentication strategy', name, 'missing scheme');
Hoek.assert(this._schemes[scheme], 'Authentication strategy', name, 'uses unknown scheme:', scheme);
const server = this.connection.server._clone([this.connection], '');
const strategy = this._schemes[scheme](server, options);
Hoek.assert(strategy.authenticate, 'Invalid scheme:', name, 'missing authenticate() method');
Hoek.assert(typeof strategy.authenticate === 'function', 'Invalid scheme:', name, 'invalid authenticate() method');
Hoek.assert(!strategy.payload || typeof strategy.payload === 'function', 'Invalid scheme:', name, 'invalid payload() method');
Hoek.assert(!strategy.response || typeof strategy.response === 'function', 'Invalid scheme:', name, 'invalid response() method
');
strategy.options = strategy.options || {};
Hoek.assert(strategy.payload || !strategy.options.payload, 'Cannot require payload validation without a payload method');
this._strategies[name] = {
methods: strategy,
realm: server.realm
};
if (strategy.api) {
this.api[name] = strategy.api;
}
if (mode) {
this.default({ strategies: [name], mode: mode === true ? 'required' : mode });
}
}n/a
test = function (name, request, next) {
Hoek.assert(name, 'Missing authentication strategy name');
const strategy = this._strategies[name];
Hoek.assert(strategy, 'Unknown authentication strategy:', name);
const reply = request.server._replier.interface(request, strategy.realm, { data: true }, (response) => next(response, reply.
_data && reply._data.credentials));
strategy.methods.authenticate(request, reply);
}...
const prefix = value[0];
const type = (prefix === '+' ? 'required' : (prefix === '!' ? 'forbidden' : 'selection
'));
const clean = (type === 'selection' ? value : value.slice(1));
scope[type] = scope[type] || [];
scope[type].push(clean);
if ((!scope._parameters || !scope._parameters[type]) &&
/{([^}]+)}/.test(clean)) {
scope._parameters = scope._parameters || {};
scope._parameters[type] = true;
}
}
return scope;
...compression = function () {
this.encodings = ['identity', 'gzip', 'deflate'];
this._encoders = {
identity: null,
gzip: (options) => Zlib.createGzip(options),
deflate: (options) => Zlib.createDeflate(options)
};
this._decoders = {
gzip: (options) => Zlib.createGunzip(options),
deflate: (options) => Zlib.createInflate(options)
};
}n/a
accept = function (request) {
return Accept.encoding(request.headers['accept-encoding'], this.encodings);
}...
this.info = {
received: now,
responded: 0,
remoteAddress: req.connection.remoteAddress,
remotePort: req.connection.remotePort || '',
referrer: req.headers.referrer || req.headers.referer || '',
host: req.headers.host ? req.headers.host.replace(/\s/g, '') : '',
acceptEncoding: this.connection._compression.accept(this)
};
this.info.hostname = this.info.host.split(':')[0];
// Assigned elsewhere:
this.orig = {};
...addDecoder = function (encoding, decoder) {
Hoek.assert(this._decoders[encoding] === undefined, `Cannot override existing decoder for ${encoding}`);
Hoek.assert(typeof decoder === 'function', `Invalid decoder function for ${encoding}`);
this._decoders[encoding] = decoder;
}...
return match.route.public;
};
internals.Connection.prototype.decoder = function (encoding, decoder) {
return this._compression.addDecoder(encoding, decoder);
};
internals.Connection.prototype.encoder = function (encoding, encoder) {
return this._compression.addEncoder(encoding, encoder);
};
...addEncoder = function (encoding, encoder) {
Hoek.assert(this._encoders[encoding] === undefined, `Cannot override existing encoder for ${encoding}`);
Hoek.assert(typeof encoder === 'function', `Invalid encoder function for ${encoding}`);
this._encoders[encoding] = encoder;
this.encodings.push(encoding);
}...
return this._compression.addDecoder(encoding, decoder);
};
internals.Connection.prototype.encoder = function (encoding, encoder) {
return this._compression.addEncoder(encoding, encoder);
};
internals.Connection.prototype._ext = function (event) {
const type = event.type;
Hoek.assert(this._extensions[type], 'Unknown event type', type);
...encoder = function (request, encoding) {
const encoder = this._encoders[encoding];
Hoek.assert(encoder !== undefined, `Unknown encoding ${encoding}`);
return encoder(request.route.settings.compression[encoding]);
}...
length !== 0 &&
response.statusCode !== 206 &&
response._isPayloadSupported()) {
delete response.headers['content-length'];
response._header('content-encoding', encoding);
compressor = request.connection._compression.encoder(request, encoding);
}
if ((response.headers['content-encoding'] || encoding) &&
response.headers.etag &&
response.settings.varyEtag) {
response.headers.etag = response.headers.etag.slice(0, -1) + '-' + (response.headers['content-encoding'] ||
encoding) + '"';
...encoding = function (response) {
const request = response.request;
if (!request.connection.settings.compression) {
return null;
}
const mime = request.server.mime.type(response.headers['content-type'] || 'application/octet-stream');
if (!mime.compressible) {
return null;
}
response.vary('accept-encoding');
if (response.headers['content-encoding']) {
return null;
}
return (request.info.acceptEncoding === 'identity' ? null : request.info.acceptEncoding);
}...
Hoek.assert(typeof decoder === 'function', `Invalid decoder function for ${encoding}`);
this._decoders[encoding] = decoder;
};
internals.Compression.prototype.accept = function (request) {
return Accept.encoding(request.headers['accept-encoding'], this.encodings);
};
internals.Compression.prototype.encoding = function (response) {
const request = response.request;
if (!request.connection.settings.compression) {
...connection = function (server, options) {
const now = Date.now();
Podium.call(this, internals.Connection._events);
this.settings = options; // options cloned in server.connection()
this.server = server;
// Normalize settings
this.settings.labels = Hoek.unique(this.settings.labels || []); // Remove duplicates
if (this.settings.port === undefined) {
this.settings.port = 0;
}
this.type = (typeof this.settings.port === 'string' ? 'socket' : 'tcp');
if (this.type === 'socket') {
this.settings.port = (this.settings.port.indexOf('/') !== -1 ? Path.resolve(this.settings.port) : this.settings.port.toLowerCase
());
}
if (this.settings.autoListen === undefined) {
this.settings.autoListen = true;
}
Hoek.assert(this.settings.autoListen || !this.settings.port, 'Cannot specify port when autoListen is false');
Hoek.assert(this.settings.autoListen || !this.settings.address, 'Cannot specify address when autoListen is false');
// Connection facilities
this._started = false;
this._connections = {};
this._onConnection = null; // Used to remove event listener on stop
this.registrations = {}; // Tracks plugin for dependency validation { name -> { version } }
this._extensions = {
onRequest: new Ext('onRequest', this.server),
onPreAuth: new Ext('onPreAuth', this.server),
onPostAuth: new Ext('onPostAuth', this.server),
onPreHandler: new Ext('onPreHandler', this.server),
onPostHandler: new Ext('onPostHandler', this.server),
onPreResponse: new Ext('onPreResponse', this.server)
};
this._requestCounter = { value: internals.counter.min, min: internals.counter.min, max: internals.counter.max };
this._load = server._heavy.policy(this.settings.load);
this._compression = new Compression();
this.states = new Statehood.Definitions(this.settings.state);
this.auth = new Auth(this);
this._router = new Call.Router(this.settings.router);
this._defaultRoutes();
this.plugins = {}; // Registered plugin APIs by plugin name
this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used
by plugins
// Create listener
this.listener = this.settings.listener || (this.settings.tls ? Https.createServer(this.settings.tls) : Http.createServer());
this.listener.on('request', this._dispatch());
this._init();
this.listener.on('clientError', (err, socket) => {
this.server._log(['connection', 'client', 'error'], err);
});
// Connection information
this.info = {
created: now,
started: 0,
host: this.settings.host || Os.hostname() || 'localhost',
port: this.settings.port,
protocol: this.type === 'tcp' ? (this.settings.tls ? 'https' : 'http') : this.type,
id: Os.hostname() + ':' + process.pid + ':' + now.toString(36)
};
this.info.uri = (this.settings.uri || (this.info.protocol + ':' + (this.type === 'tcp' ? '//' + this.info.host + (this.info.
port ? ':' + this.info.port : '') : this.info.port)));
this.on('route', Cors.options);
}...
exports = module.exports = internals.Connection = function (server, options) {
const now = Date.now();
Podium.call(this, internals.Connection._events);
this.settings = options; // options cloned in server.connection()
this.server = server;
// Normalize settings
this.settings.labels = Hoek.unique(this.settings.labels || []); // Remove duplicates
if (this.settings.port === undefined) {
this.settings.port = 0;
...super_ = function (events) {
// Use descriptive names to avoid conflict when inherited
this._eventListeners = Object.create(null);
this._notificationsQueue = [];
this._eventsProcessing = false;
this._sourcePodiums = [];
if (events) {
this.registerEvent(events);
}
}n/a
_addRoute = function (config, plugin) {
const route = new Route(config, this, plugin); // Do no use config beyond this point, use route members
const vhosts = [].concat(route.settings.vhost || '*');
for (let i = 0; i < vhosts.length; ++i) {
const vhost = vhosts[i];
const record = this._router.add({ method: route.method, path: route.path, vhost, analysis: route._analysis, id: route.settings
.id }, route);
route.fingerprint = record.fingerprint;
route.params = record.params;
}
this.emit('route', [route.public, this, plugin]);
}...
if (Array.isArray(config.method)) {
for (let j = 0; j < config.method.length; ++j) {
const method = config.method[j];
const settings = Hoek.shallow(config);
settings.method = method;
this._addRoute(settings, plugin);
}
}
else {
this._addRoute(config, plugin);
}
}
};
..._defaultRoutes = function () {
this._router.special('notFound', new Route({ method: '_special', path: '/{p*}', handler: internals.notFound }, this, this.server
, { special: true }));
this._router.special('badRequest', new Route({ method: '_special', path: '/{p*}', handler: internals.badRequest }, this, this
.server, { special: true }));
if (this.settings.routes.cors) {
Cors.handler(this);
}
}...
this._requestCounter = { value: internals.counter.min, min: internals.counter.min, max: internals.counter.max };
this._load = server._heavy.policy(this.settings.load);
this._compression = new Compression();
this.states = new Statehood.Definitions(this.settings.state);
this.auth = new Auth(this);
this._router = new Call.Router(this.settings.router);
this._defaultRoutes();
this.plugins = {}; // Registered plugin APIs by plugin name
this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins
// Create listener
this.listener = this.settings.listener || (this.settings.tls ? Https.createServer(this.settings.tls) : Http.createServer());
..._dispatch = function (options) {
options = options || {};
return (req, res) => {
// Track socket request processing state
if (req.socket) {
req.socket._isHapiProcessing = true;
res.on('finish', () => {
req.socket._isHapiProcessing = false;
if (!this._started) {
req.socket.end();
}
});
}
// Create request
const request = this.server._requestor.request(this, req, res, options);
// Check load
const overload = this._load.check();
if (overload) {
this.server._log(['load'], this.server.load);
request._reply(overload);
}
else {
// Execute request lifecycle
request._protect.enter(() => {
request._execute();
});
}
};
}...
this.plugins = {}; // Registered plugin APIs by plugin name
this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins
// Create listener
this.listener = this.settings.listener || (this.settings.tls ? Https.createServer(this.settings.tls) : Http.createServer());
this.listener.on('request', this._dispatch());
this._init();
this.listener.on('clientError', (err, socket) => {
this.server._log(['connection', 'client', 'error'], err);
});
..._ext = function (event) {
const type = event.type;
Hoek.assert(this._extensions[type], 'Unknown event type', type);
this._extensions[type].add(event);
}n/a
_init = function () {
// Setup listener
this.listener.once('listening', () => {
// Update the address, port, and uri with active values
if (this.type === 'tcp') {
const address = this.listener.address();
this.info.address = address.address;
this.info.port = address.port;
this.info.uri = (this.settings.uri || (this.info.protocol + '://' + this.info.host + ':' + this.info.port));
}
this._onConnection = (connection) => {
const key = connection.remoteAddress + ':' + connection.remotePort;
this._connections[key] = connection;
connection.once('close', () => {
delete this._connections[key];
});
};
this.listener.on(this.settings.tls ? 'secureConnection' : 'connection', this._onConnection);
});
}...
this.plugins = {}; // Registered plugin APIs by plugin name
this.app = {}; // Place for application-specific state without conflicts with hapi, should not be used by plugins
// Create listener
this.listener = this.settings.listener || (this.settings.tls ? Https.createServer(this.settings.tls) : Http.createServer());
this.listener.on('request', this._dispatch());
this._init();
this.listener.on('clientError', (err, socket) => {
this.server._log(['connection', 'client', 'error'], err);
});
// Connection information
..._route = function (configs, plugin) {
configs = [].concat(configs);
for (let i = 0; i < configs.length; ++i) {
const config = configs[i];
if (Array.isArray(config.method)) {
for (let j = 0; j < config.method.length; ++j) {
const method = config.method[j];
const settings = Hoek.shallow(config);
settings.method = method;
this._addRoute(settings, plugin);
}
}
else {
this._addRoute(config, plugin);
}
}
}n/a
_start = function (callback) {
if (this._started) {
return process.nextTick(callback);
}
this._started = true;
this.info.started = Date.now();
if (!this.settings.autoListen) {
return process.nextTick(callback);
}
const onError = (err) => {
this._started = false;
return callback(err);
};
this.listener.once('error', onError);
const finalize = () => {
this.listener.removeListener('error', onError);
callback();
};
if (this.type !== 'tcp') {
this.listener.listen(this.settings.port, finalize);
}
else {
const address = this.settings.address || this.settings.host || '0.0.0.0';
this.listener.listen(this.settings.port, address, finalize);
}
}n/a
_stop = function (options, callback) {
if (!this._started) {
return process.nextTick(callback);
}
this._started = false;
this.info.started = 0;
const timeoutId = setTimeout(() => {
Object.keys(this._connections).forEach((key) => {
this._connections[key].destroy();
});
this._connections = {};
}, options.timeout);
this.listener.close(() => {
this.listener.removeListener(this.settings.tls ? 'secureConnection' : 'connection', this._onConnection);
clearTimeout(timeoutId);
this._init();
return callback();
});
// Tell idle keep-alive connections to close
Object.keys(this._connections).forEach((key) => {
const connection = this._connections[key];
if (!connection._isHapiProcessing) {
connection.end();
}
});
}n/a
decoder = function (encoding, decoder) {
return this._compression.addDecoder(encoding, decoder);
}n/a
encoder = function (encoding, encoder) {
return this._compression.addEncoder(encoding, encoder);
}...
length !== 0 &&
response.statusCode !== 206 &&
response._isPayloadSupported()) {
delete response.headers['content-length'];
response._header('content-encoding', encoding);
compressor = request.connection._compression.encoder(request, encoding);
}
if ((response.headers['content-encoding'] || encoding) &&
response.headers.etag &&
response.settings.varyEtag) {
response.headers.etag = response.headers.etag.slice(0, -1) + '-' + (response.headers['content-encoding'] ||
encoding) + '"';
...inject = function (options, callback) {
if (!callback) {
return new Promise((resolve, reject) => this.inject(options, (res) => resolve(res)));
}
let settings = options;
if (typeof settings === 'string') {
settings = { url: settings };
}
if (!settings.authority ||
settings.credentials ||
settings.app ||
settings.plugins ||
settings.allowInternals !== undefined) { // Can be false
settings = Hoek.shallow(settings); // options can be reused
delete settings.credentials;
delete settings.artifacts; // Cannot appear without credentials
delete settings.app;
delete settings.plugins;
delete settings.allowInternals;
settings.authority = settings.authority || (this.info.host + ':' + this.info.port);
}
const needle = this._dispatch({
credentials: options.credentials,
artifacts: options.artifacts,
allowInternals: options.allowInternals,
app: options.app,
plugins: options.plugins
});
Shot.inject(needle, settings, (res) => {
if (res.raw.res._hapi) {
res.result = res.raw.res._hapi.result;
res.request = res.raw.res._hapi.request;
delete res.raw.res._hapi;
}
if (res.result === undefined) {
res.result = res.payload;
}
return callback(res);
});
}...
};
};
internals.Connection.prototype.inject = function (options, callback) {
if (!callback) {
return new Promise((resolve, reject) => this.inject(options, (res) => resolve
(res)));
}
let settings = options;
if (typeof settings === 'string') {
settings = { url: settings };
}
...lookup = function (id) {
Hoek.assert(id && typeof id === 'string', 'Invalid route id:', id);
const record = this._router.ids[id];
if (!record) {
return null;
}
return record.route.public;
}...
return auth._authenticate(request, next);
};
internals.Auth.access = function (request, route) {
const auth = request.connection.auth;
const config = auth.lookup(route);
if (!config) {
return true;
}
const credentials = request.auth.credentials;
if (!credentials) {
return false;
...match = function (method, path, host) {
Hoek.assert(method && typeof method === 'string', 'Invalid method:', method);
Hoek.assert(path && typeof path === 'string' && path[0] === '/', 'Invalid path:', path);
Hoek.assert(!host || typeof host === 'string', 'Invalid host:', host);
const match = this._router.route(method.toLowerCase(), path, host);
Hoek.assert(match !== this._router.specials.badRequest, 'Invalid path:', path);
if (match === this._router.specials.notFound) {
return null;
}
return match.route.public;
}...
const method = request.headers['access-control-request-method'];
if (!method) {
return reply(Boom.notFound('CORS error: Missing Access-Control-Request-Method header'));
}
// Lookup route
const route = request.connection.match(method, request.path, request.info.hostname);
if (!route) {
return reply(Boom.notFound());
}
const settings = route.settings.cors;
if (!settings) {
return reply({ message: 'CORS is disabled for this route' });
...table = function (host) {
return this._router.table(host);
}...
return callback(res);
});
};
internals.Connection.prototype.table = function (host) {
return this._router.table(host);
};
internals.Connection.prototype.lookup = function (id) {
Hoek.assert(id && typeof id === 'string', 'Invalid route id:', id);
...handler = function (connection) {
Route = Route || require('./route');
if (connection._router.specials.options) {
return;
}
const route = new Route({ method: '_special', path: '/{p*}', handler: internals.handler }, connection, connection.server, {
special: true });
connection._router.special('options', route);
}...
internals.Connection.prototype._defaultRoutes = function () {
this._router.special('notFound', new Route({ method: '_special', path: '/{p*}', handler: internals
.notFound }, this, this.server, { special: true }));
this._router.special('badRequest', new Route({ method: '_special', path: '/{p*}', handler: internals
.badRequest }, this, this.server, { special: true }));
if (this.settings.routes.cors) {
Cors.handler(this);
}
};
internals.notFound = function (request, reply) {
return reply(Boom.notFound());
...headers = function (response) {
const request = response.request;
if (request._route._special) {
return;
}
const settings = request.route.settings.cors;
if (!settings) {
return;
}
response.vary('origin');
if (!request.info.cors.isOriginMatch) {
return;
}
response._header('access-control-allow-origin', request.headers.origin);
if (settings.credentials) {
response._header('access-control-allow-credentials', 'true');
}
if (settings._exposedHeaders) {
response._header('access-control-expose-headers', settings._exposedHeaders, { append: true });
}
}...
};
internals.marshal = function (request, next) {
const response = request.response;
Cors.headers(response);
internals.content(response, false);
internals.security(response);
internals.unmodified(response);
internals.state(response, (err) => {
if (err) {
...matchOrigin = function (origin, settings) {
if (!origin) {
return false;
}
if (settings._origin === true) {
return true;
}
if (settings._origin.qualified.indexOf(origin) !== -1) {
return true;
}
for (let i = 0; i < settings._origin.wildcards.length; ++i) {
if (origin.match(settings._origin.wildcards[i])) {
return true;
}
}
return false;
}...
const settings = route.settings.cors;
if (!settings) {
return reply({ message: 'CORS is disabled for this route' });
}
// Validate Origin header
if (!exports.matchOrigin(origin, settings)) {
return reply({ message: 'CORS error: Origin not allowed' });
}
// Validate allowed headers
let headers = request.headers['access-control-request-headers'];
if (headers) {
...options = function (route, connection, server) {
if (route.method === 'options' ||
!route.settings.cors) {
return;
}
exports.handler(connection);
}...
internals.cachePolicy = Joi.object({
cache: Joi.string().allow(null).allow(''),
segment: Joi.string(),
shared: Joi.boolean()
})
.options({ allowUnknown: true }); // Catbox validates other keys
internals.method = Joi.object({
bind: Joi.object().allow(null),
generateKey: Joi.func(),
cache: internals.cachePolicy,
callback: Joi.boolean()
...route = function (options) {
const settings = Hoek.applyToDefaults(Defaults.cors, options);
if (!settings) {
return false;
}
settings._headers = settings.headers.concat(settings.additionalHeaders);
settings._headersString = settings._headers.join(',');
for (let i = 0; i < settings._headers.length; ++i) {
settings._headers[i] = settings._headers[i].toLowerCase();
}
if (settings._headers.indexOf('origin') === -1) {
settings._headers.push('origin');
}
settings._exposedHeaders = settings.exposedHeaders.concat(settings.additionalExposedHeaders).join(',');
if (settings.origin.indexOf('*') !== -1) {
Hoek.assert(settings.origin.length === 1, 'Cannot specify cors.origin * together with other values');
settings._origin = true;
}
else {
settings._origin = {
qualified: [],
wildcards: []
};
for (let i = 0; i < settings.origin.length; ++i) {
const origin = settings.origin[i];
if (origin.indexOf('*') !== -1) {
settings._origin.wildcards.push(new RegExp('^' + Hoek.escapeRegex(origin).replace(/\\\*/g, '.*').replace(/\\\?/g
, '.') + '$'));
}
else {
settings._origin.qualified.push(origin);
}
}
}
return settings;
}...
internals.Connection.prototype.match = function (method, path, host) {
Hoek.assert(method && typeof method === 'string', 'Invalid method:', method);
Hoek.assert(path && typeof path === 'string' && path[0] === '/', 'Invalid path:
x27;, path);
Hoek.assert(!host || typeof host === 'string', 'Invalid host:', host);
const match = this._router.route(method.toLowerCase(), path, host);
Hoek.assert(match !== this._router.specials.badRequest, 'Invalid path:', path);
if (match === this._router.specials.notFound) {
return null;
}
return match.route.public;
};
...ext = function (type, server) {
this._topo = new Topo();
this._server = server;
this._routes = [];
this.type = type;
this.nodes = null;
}n/a
add = function (event) {
const methods = [].concat(event.method);
const options = event.options;
for (let i = 0; i < methods.length; ++i) {
const settings = {
before: options.before,
after: options.after,
group: event.plugin.realm.plugin,
sort: this._server._extensionsSeq++
};
const node = {
func: methods[i], // Connection: function (request, next), Server: function (server, next)
bind: options.bind,
plugin: event.plugin
};
this._topo.add(node, settings);
}
this.nodes = this._topo.nodes;
// Notify routes
for (let i = 0; i < this._routes.length; ++i) {
this._routes[i].rebuild(event);
}
}...
};
internals.Connection.prototype._ext = function (event) {
const type = event.type;
Hoek.assert(this._extensions[type], 'Unknown event type', type);
this._extensions[type].add(event);
};
internals.Connection.prototype._route = function (configs, plugin) {
configs = [].concat(configs);
for (let i = 0; i < configs.length; ++i) {
...merge = function (others) {
const merge = [];
for (let i = 0; i < others.length; ++i) {
merge.push(others[i]._topo);
}
this._topo.merge(merge);
this.nodes = (this._topo.nodes.length ? this._topo.nodes : null);
}...
internals.Ext.prototype.merge = function (others) {
const merge = [];
for (let i = 0; i < others.length; ++i) {
merge.push(others[i]._topo);
}
this._topo.merge(merge);
this.nodes = (this._topo.nodes.length ? this._topo.nodes : null);
};
internals.Ext.prototype.subscribe = function (route) {
this._routes.push(route);
...subscribe = function (route) {
this._routes.push(route);
}...
}
const connection = this.connection._extensions[type];
const realm = this.plugin.realm._extensions[type];
ext.merge([connection, realm]);
connection.subscribe(this);
realm.subscribe(this);
return ext;
};
internals.Route.prototype.rebuild = function (event) {
...configure = function (handler, route) {
if (typeof handler === 'object') {
const type = Object.keys(handler)[0];
const serverHandler = route.server._handlers[type];
Hoek.assert(serverHandler, 'Unknown handler:', type);
return serverHandler(route.public, handler[type]);
}
if (typeof handler === 'string') {
const parsed = internals.fromString('handler', handler, route.server);
return parsed.method;
}
return handler;
}...
security._xframe = security.xframe.rule.toUpperCase();
}
}
}
// Handler
this.settings.handler = Handler.configure(this.settings.handler, this);
this._prerequisites = Handler.prerequisitesConfig(this.settings.pre, this.server);
// Route lifecycle
this._extensions = {
onPreResponse: this._combineExtensions('onPreResponse')
};
...defaults = function (method, handler, server) {
let defaults = null;
if (typeof handler === 'object') {
const type = Object.keys(handler)[0];
const serverHandler = server._handlers[type];
Hoek.assert(serverHandler, 'Unknown handler:', type);
if (serverHandler.defaults) {
defaults = (typeof serverHandler.defaults === 'function' ? serverHandler.defaults(method) : serverHandler.defaults);
}
}
return defaults || {};
}...
if (typeof handler === 'object') {
const type = Object.keys(handler)[0];
const serverHandler = server._handlers[type];
Hoek.assert(serverHandler, 'Unknown handler:', type);
if (serverHandler.defaults) {
defaults = (typeof serverHandler.defaults === 'function' ? serverHandler.defaults(method) : serverHandler.defaults);
}
}
return defaults || {};
};
...execute = function (request, next) {
const finalize = (err, result) => {
request._setResponse(err || result);
return next(); // Must not include an argument
};
request._protect.run(finalize, (exit) => {
if (request._route._prerequisites) {
internals.prerequisites(request, Hoek.once(exit));
}
else {
internals.handler(request, exit);
}
});
}...
if (this.request.auth.credentials) {
return this.validate(null, { credentials: this.request.auth.credentials, artifacts: this.request.auth.artifacts }, next);
}
// Authenticate
return this.execute(next);
}
execute(next) {
const config = this.config;
const request = this.request;
...prerequisitesConfig = function (config, server) {
if (!config) {
return null;
}
/*
[
[
function (request, reply) { },
{
method: function (request, reply) { }
assign: key1
},
{
method: function (request, reply) { },
assign: key2
}
],
'user(params.id)'
]
*/
const prerequisites = [];
for (let i = 0; i < config.length; ++i) {
const pres = [].concat(config[i]);
const set = [];
for (let j = 0; j < pres.length; ++j) {
let pre = pres[j];
if (typeof pre !== 'object') {
pre = { method: pre };
}
const item = {
method: pre.method,
assign: pre.assign,
failAction: pre.failAction || 'error'
};
if (typeof item.method === 'string') {
const parsed = internals.fromString('pre', item.method, server);
item.method = parsed.method;
item.assign = item.assign || parsed.name;
}
set.push(internals.pre(item));
}
prerequisites.push(set);
}
return prerequisites.length ? prerequisites : null;
}...
}
}
}
// Handler
this.settings.handler = Handler.configure(this.settings.handler, this);
this._prerequisites = Handler.prerequisitesConfig(this.settings.pre, this.server);
// Route lifecycle
this._extensions = {
onPreResponse: this._combineExtensions('onPreResponse')
};
...methods = function (server) {
this.server = server;
this.methods = {};
this._normalized = {};
}n/a
_add = function (name, method, options, realm) {
Hoek.assert(typeof method === 'function', 'method must be a function');
Hoek.assert(typeof name === 'string', 'name must be a string');
Hoek.assert(name.match(exports.methodNameRx), 'Invalid name:', name);
Hoek.assert(!Hoek.reach(this.methods, name, { functions: false }), 'Server method function name already exists:', name);
options = Schema.apply('method', options || {}, name);
const settings = Hoek.cloneWithShallow(options, ['bind']);
settings.generateKey = settings.generateKey || internals.generateKey;
const bind = settings.bind || realm.settings.bind || null;
const apply = function () {
return method.apply(bind, arguments);
};
const bound = bind ? apply : method;
// Normalize methods
let normalized = bound;
if (settings.callback === false) { // Defaults to true
normalized = function (/* arg1, arg2, ..., argn, methodNext */) {
const args = [];
for (let i = 0; i < arguments.length - 1; ++i) {
args.push(arguments[i]);
}
const methodNext = arguments[arguments.length - 1];
let result = null;
let error = null;
try {
result = method.apply(bind, args);
}
catch (err) {
error = err;
}
if (result instanceof Error) {
error = result;
result = null;
}
if (error ||
typeof result !== 'object' ||
typeof result.then !== 'function') {
return methodNext(error, result);
}
// Promise object
const onFulfilled = (outcome) => methodNext(null, outcome);
const onRejected = (err) => methodNext(err);
result.then(onFulfilled, onRejected);
};
}
// Not cached
if (!settings.cache) {
return this._assign(name, bound, normalized);
}
// Cached
Hoek.assert(!settings.cache.generateFunc, 'Cannot set generateFunc with method caching:', name);
Hoek.assert(settings.cache.generateTimeout !== undefined, 'Method caching requires a timeout value in generateTimeout:', name
);
settings.cache.generateFunc = (id, next) => {
id.args.push(next); // function (err, result, ttl)
normalized.apply(bind, id.args);
};
const cache = this.server.cache(settings.cache, '#' + name);
const func = function (/* arguments, methodNext */) {
const args = [];
for (let i = 0; i < arguments.length - 1; ++i) {
args.push(arguments[i]);
}
const methodNext = arguments[arguments.length - 1];
const key = settings.generateKey.apply(bind, args);
if (key === null || // Value can be ''
typeof key !== 'string') { // When using custom generateKey
return Hoek.nextTick(methodNext)(Boom.badImplementation('Invalid method key when invoking: ' + name, { name, args }));
}
cache.get({ id: key, args }, methodNext);
};
func.cache = {
drop: function (/* arguments, callback */) {
const args = [];
for (let i = 0; i < arguments.length - 1; ++i) {
args.push(arguments[i]);
}
const methodNext = arguments[arguments.length - 1];
const key = settings.generateKey.apply(null, args);
if (key === null) { // Value can be ''
return Hoek.nextTick(methodNext)(Boom.badImplementation('Invalid method key'));
}
return cache.drop(key, methodNext);
},
stats: cache.stats
};
this._assign(name, func, func);
}...
this._normalized = {};
};
internals.Methods.prototype.add = function (name, method, options, realm) {
if (typeof name !== 'object') {
return this._add(name, method, options, realm);
}
// {} or [{}, {}]
const items = [].concat(name);
for (let i = 0; i < items.length; ++i) {
const item = Schema.apply('methodObject', items[i]);
..._assign = function (name, method, normalized) {
const path = name.split('.');
let ref = this.methods;
for (let i = 0; i < path.length; ++i) {
if (!ref[path[i]]) {
ref[path[i]] = (i + 1 === path.length ? method : {});
}
ref = ref[path[i]];
}
this._normalized[name] = normalized;
}...
result.then(onFulfilled, onRejected);
};
}
// Not cached
if (!settings.cache) {
return this._assign(name, bound, normalized);
}
// Cached
Hoek.assert(!settings.cache.generateFunc, 'Cannot set generateFunc with method caching:', name);
Hoek.assert(settings.cache.generateTimeout !== undefined, 'Method caching requires a timeout value in generateTimeout:'
;, name);
...add = function (name, method, options, realm) {
if (typeof name !== 'object') {
return this._add(name, method, options, realm);
}
// {} or [{}, {}]
const items = [].concat(name);
for (let i = 0; i < items.length; ++i) {
const item = Schema.apply('methodObject', items[i]);
this._add(item.name, item.method, item.options, realm);
}
}...
};
internals.Connection.prototype._ext = function (event) {
const type = event.type;
Hoek.assert(this._extensions[type], 'Unknown event type', type);
this._extensions[type].add(event);
};
internals.Connection.prototype._route = function (configs, plugin) {
configs = [].concat(configs);
for (let i = 0; i < configs.length; ++i) {
...wrap = function (bind, method, args) {
return new Promise((resolve, reject) => {
const callback = (err, result) => {
if (err) {
return reject(err);
}
return resolve(result);
};
method.apply(bind, args ? args.concat(callback) : [callback]);
});
}...
internals.handler = function (request, callback) {
const timer = new Hoek.Bench();
const finalize = (response) => {
if (response === null) { // reply.continue()
response = Response.wrap(null, request);
return response._prepare(finalize);
}
// Check for Error result
if (response.isBoom) {
request._log(['handler', 'error'], { msec: timer.elapsed(), error: response.message, data: response });
...protect = function (request) {
this._error = null;
this.logger = request; // Replaced with server when request completes
if (!request.server.settings.useDomains) {
this.domain = null;
return;
}
Domain = Domain || require('domain');
this.domain = Domain.create();
this.domain.on('error', (err) => {
return this._onError(err);
});
}n/a
_onError = function (err) {
const handler = this._error;
if (handler) {
this._error = null;
return handler(err);
}
this.logger._log(['internal', 'implementation', 'error'], err);
}...
}
Domain = Domain || require('domain');
this.domain = Domain.create();
this.domain.on('error', (err) => {
return this._onError(err);
});
};
internals.Protect.prototype._onError = function (err) {
const handler = this._error;
...enter = function (func) {
if (!this.domain) {
return func();
}
this.domain.run(func);
}...
this.server._log(['load'], this.server.load);
request._reply(overload);
}
else {
// Execute request lifecycle
request._protect.enter(() => {
request._execute();
});
}
};
};
...reset = function () {
this._error = null;
}...
return this._finalize();
}
if (exit) { // Can be a valid response or error (if returned from an ext, already
handled because this.response is also set)
this._setResponse(Response.wrap(exit, this));
}
this._protect.reset();
const transmit = (err) => {
if (err) { // Can be valid response or error
this._setResponse(Response.wrap(err, this));
}
...run = function (next, enter) { // enter: function (exit)
const finish = Hoek.once((arg0, arg1, arg2) => {
this._error = null;
return next(arg0, arg1, arg2);
});
if (this.domain) {
this._error = (err) => {
return finish(Boom.badImplementation('Uncaught error', err));
};
}
return enter(finish);
}...
return next(setting === 'optional' ? null : Boom.unauthorized('Missing payload authentication'));
}
return next(response);
};
request._protect.run(finalize, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.payload(request, reply);
});
};
...reply = function () {
this._decorations = null;
}n/a
decorate = function (property, method) {
Hoek.assert(!this._decorations || !this._decorations[property], 'Reply interface decoration already defined:', property);
Hoek.assert(['request', 'response', 'close', 'state', 'unstate', 'redirect', 'continue'].indexOf(property) === -1, 'Cannot override
built-in reply interface decoration:', property);
this._decorations = this._decorations || {};
this._decorations[property] = method;
}n/a
interface = function (request, realm, options, next) { // next(err || response, data);
const reply = (err, response, data) => {
Hoek.assert(data === undefined || options.data, 'Reply interface does not allow a third argument');
reply._data = data; // Held for later
return reply.response(err !== null && err !== undefined ? err : response);
};
reply._settings = options;
reply._replied = false;
reply._next = Hoek.once(next);
reply.realm = realm;
reply.request = request;
reply.close = internals.close;
reply.continue = internals.continue;
reply.state = internals.state;
reply.unstate = internals.unstate;
reply.redirect = internals.redirect;
reply.response = internals.response;
reply.entity = internals.entity;
if (this._decorations) {
const methods = Object.keys(this._decorations);
for (let i = 0; i < methods.length; ++i) {
const method = methods[i];
reply[method] = this._decorations[method];
}
}
return reply;
}...
internals.Auth.prototype.test = function (name, request, next) {
Hoek.assert(name, 'Missing authentication strategy name');
const strategy = this._strategies[name];
Hoek.assert(strategy, 'Unknown authentication strategy:', name);
const reply = request.server._replier.interface(request, strategy.realm, { data: true
}, (response) => next(response, reply._data && reply._data.credentials));
strategy.methods.authenticate(request, reply);
};
internals.Auth.prototype._setupRoute = function (options, path) {
if (!options) {
...request = function () {
this._decorations = null;
}...
req.socket.end();
}
});
}
// Create request
const request = this.server._requestor.request(this, req, res, options);
// Check load
const overload = this._load.check();
if (overload) {
this.server._log(['load'], this.server.load);
request._reply(overload);
...decorate = function (property, method, options) {
options = options || {};
Hoek.assert(!this._decorations || this._decorations[property] === undefined, 'Request interface decoration already defined:',
property);
Hoek.assert(internals.properties.indexOf(property) === -1, 'Cannot override built-in request interface decoration:', property
);
this._decorations = this._decorations || {};
this._decorations[property] = { method, apply: options.apply };
}n/a
request = function (connection, req, res, options) {
const request = new internals.Request(connection, req, res, options);
// Decorate
if (this._decorations) {
const properties = Object.keys(this._decorations);
for (let i = 0; i < properties.length; ++i) {
const property = properties[i];
const assignment = this._decorations[property];
request[property] = (assignment.apply ? assignment.method(request) : assignment.method);
}
}
return request;
}...
req.socket.end();
}
});
}
// Create request
const request = this.server._requestor.request(this, req, res, options);
// Check load
const overload = this._load.check();
if (overload) {
this.server._log(['load'], this.server.load);
request._reply(overload);
...response = function (source, request, options) {
Podium.call(this, ['finish', { name: 'peek', spread: true }]);
options = options || {};
this.request = request;
this.statusCode = null;
this.headers = {}; // Incomplete as some headers are stored in flags
this.variety = null;
this.source = null;
this.app = {};
this.plugins = {};
this.send = null; // Set by reply()
this.hold = null; // Set by reply()
this.settings = {
encoding: 'utf8',
charset: 'utf-8', // '-' required by IANA
ttl: null,
stringify: null, // JSON.stringify options
passThrough: true,
varyEtag: false,
message: null
};
this._payload = null; // Readable stream
this._takeover = false;
this._contentEncoding = null; // Set during transmit
this._contentType = null; // Used if no explicit content-type is set and type is known
this._error = null; // The boom object when created from an error
this._processors = {
marshal: options.marshal,
prepare: options.prepare,
close: options.close
};
this._setSource(source, options.variety);
}...
if (!strategy.methods.response) {
return next();
}
request._protect.run(next, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.response(request, reply);
});
};
internals.Authenticator = class {
constructor(config, request, manager) {
...Payload = function (payload, options) {
Stream.Readable.call(this);
this._data = payload;
this._prefix = null;
this._suffix = null;
this._sizeOffset = 0;
this._encoding = options.encoding;
}...
payload = payload + suffix;
}
}
else if (this.settings.stringify) {
return next(Boom.badImplementation('Cannot set formatting options on non object response'));
}
this._payload = new internals.Payload(payload, this.settings);
return next();
};
internals.Response.prototype._tap = function () {
return (this.hasListeners('finish') || this.hasListeners('peek') ? new internals.Peek(this) : null);
...Peek = function (podium) {
Stream.Transform.call(this);
this._podium = podium;
this.once('finish', () => {
podium.emit('finish');
});
}...
this._states[name] = state;
};
internals.Request.prototype._tap = function () {
return (this.hasListeners('finish') || this.hasListeners('peek') ? new Response.Peek(this) : null);
};
internals.Request.prototype.generateResponse = function (source, options) {
return new Response(source, this, options);
};
...super_ = function (events) {
// Use descriptive names to avoid conflict when inherited
this._eventListeners = Object.create(null);
this._notificationsQueue = [];
this._eventsProcessing = false;
this._sourcePodiums = [];
if (events) {
this.registerEvent(events);
}
}n/a
unmodified = function (request, options) {
if (request.method !== 'get' &&
request.method !== 'head') {
return false;
}
// Strong verifier
if (options.etag &&
request.headers['if-none-match']) {
const ifNoneMatch = request.headers['if-none-match'].split(/\s*,\s*/);
for (let i = 0; i < ifNoneMatch.length; ++i) {
const etag = ifNoneMatch[i];
if (etag === options.etag) {
return true;
}
if (options.vary) {
const etagBase = options.etag.slice(0, -1);
const encoders = request.connection._compression.encodings;
for (let j = 0; j < encoders.length; ++j) {
if (etag === etagBase + `-${encoders[j]}"`) {
return true;
}
}
}
}
return false;
}
// Weak verifier
const ifModifiedSinceHeader = request.headers['if-modified-since'];
if (ifModifiedSinceHeader &&
options.modified) {
const ifModifiedSince = internals.parseDate(ifModifiedSinceHeader);
const lastModified = internals.parseDate(options.modified);
if (ifModifiedSince &&
lastModified &&
ifModifiedSince >= lastModified) {
return true;
}
}
return false;
}...
internals.entity = function (options) {
Hoek.assert(options, 'Entity method missing required options');
Hoek.assert(options.etag || options.modified, 'Entity methods missing require options key');
this.request._entity = options;
if (Response.unmodified(this.request, options)) {
return this.response().code(304).takeover();
}
return null;
};
...wrap = function (result, request) {
return (result instanceof Error ? Boom.wrap(result)
: (result instanceof internals.Response ? result
: new internals.Response(result, request)));
}...
internals.handler = function (request, callback) {
const timer = new Hoek.Bench();
const finalize = (response) => {
if (response === null) { // reply.continue()
response = Response.wrap(null, request);
return response._prepare(finalize);
}
// Check for Error result
if (response.isBoom) {
request._log(['handler', 'error'], { msec: timer.elapsed(), error: response.message, data: response });
..._close = function () {
if (this._processors.close) {
this._processors.close(this);
}
const stream = this._payload || this.source;
if (stream instanceof Stream) {
if (stream.close) {
stream.close();
}
else if (stream.destroy) {
stream.destroy();
}
else {
const read = () => {
stream.read();
};
const end = () => {
stream.removeListener('readable', read);
stream.removeListener('error', end);
stream.removeListener('end', end);
};
stream.on('readable', read);
stream.once('error', end);
stream.once('end', end);
}
}
}...
this.raw.req.removeListener('end', this._onEnd);
this.raw.req.removeListener('close', this._onClose);
this.raw.req.removeListener('error', this._onError);
if (this.response &&
this.response._close) {
this.response._close();
}
this._protect.logger = this.server;
};
internals.Request.prototype._setResponse = function (response) {
..._header = function (key, value, options) {
options = options || {};
const append = options.append || false;
const separator = options.separator || ',';
const override = options.override !== false;
const duplicate = options.duplicate !== false;
if ((!append && override) ||
!this.headers[key]) {
this.headers[key] = value;
}
else if (override) {
if (key === 'set-cookie') {
this.headers[key] = [].concat(this.headers[key], value);
}
else {
const existing = this.headers[key];
if (!duplicate) {
const values = existing.split(separator);
for (let i = 0; i < values.length; ++i) {
if (values[i] === value) {
return this;
}
}
}
this.headers[key] = existing + separator + value;
}
}
return this;
}...
return reply({ message: 'CORS error: Some headers are not allowed' });
}
}
// Reply with the route CORS headers
const response = reply();
response._header('access-control-allow-origin', request.headers.origin);
response._header('access-control-allow-methods', method);
response._header('access-control-allow-headers', settings._headersString);
response._header('access-control-max-age', settings.maxAge);
if (settings.credentials) {
response._header('access-control-allow-credentials', 'true');
}
..._isPayloadSupported = function () {
return (this.request.method !== 'head' && this.statusCode !== 304 && this.statusCode !== 204);
}...
request._log(['state', 'response', 'error'], err);
request._states = {}; // Clear broken state
return next(err);
}
internals.cache(response);
if (!response._isPayloadSupported() &&
request.method !== 'head') {
// Set empty stream
response._close(); // Close unused file streams
response._payload = new internals.Empty();
delete response.headers['content-length'];
..._isRewritable = function () {
return this.statusCode === 301 || this.statusCode === 302;
}...
return this.statusCode === 301 || this.statusCode === 302;
};
internals.Response.prototype._setTemporary = function (isTemporary) {
if (isTemporary) {
if (this._isRewritable()) {
this.statusCode = 302;
}
else {
this.statusCode = 307;
}
}
else {
..._isTemporary = function () {
return this.statusCode === 302 || this.statusCode === 307;
}...
}
};
internals.Response.prototype._setRewritable = function (isRewritable) {
if (isRewritable) {
if (this._isTemporary()) {
this.statusCode = 302;
}
else {
this.statusCode = 301;
}
}
else {
..._marshal = function (next) {
if (!this._processors.marshal) {
return this._streamify(this.source, next);
}
this._processors.marshal(this, (err, source) => {
if (err) {
return next(err);
}
return this._streamify(source, next);
});
}...
response._close(); // Close unused file streams
response._payload = new internals.Empty();
delete response.headers['content-length'];
return Auth.response(request, next); // Must be last in case requires access to headers
}
response._marshal((err) => {
if (err) {
return next(Boom.wrap(err));
}
if (request.jsonp &&
response._payload.jsonp) {
..._passThrough = function () {
if (this.variety === 'stream' &&
this.settings.passThrough) {
if (this.source.statusCode &&
!this.statusCode) {
this.statusCode = this.source.statusCode; // Stream is an HTTP response
}
if (this.source.headers) {
let headerKeys = Object.keys(this.source.headers);
if (headerKeys.length) {
const localHeaders = this.headers;
this.headers = {};
for (let i = 0; i < headerKeys.length; ++i) {
const key = headerKeys[i];
this.header(key.toLowerCase(), Hoek.clone(this.source.headers[key])); // Clone arrays
}
headerKeys = Object.keys(localHeaders);
for (let i = 0; i < headerKeys.length; ++i) {
const key = headerKeys[i];
this.header(key, localHeaders[key], { append: key === 'set-cookie' });
}
}
}
}
this.statusCode = this.statusCode || 200;
}...
this._takeover = true;
return this;
};
internals.Response.prototype._prepare = function (next) {
this._passThrough();
if (this.variety !== 'promise') {
return this._processPrepare(next);
}
const onDone = Hoek.nextTick((source) => {
..._permanent = function (isPermanent) {
this._setTemporary(isPermanent === false); // Defaults to true
return this;
}n/a
_prepare = function (next) {
this._passThrough();
if (this.variety !== 'promise') {
return this._processPrepare(next);
}
const onDone = Hoek.nextTick((source) => {
if (source instanceof Error) {
return next(Boom.wrap(source));
}
if (source instanceof internals.Response) {
return source._processPrepare(next);
}
this._setSource(source);
this._passThrough();
this._processPrepare(next);
});
const onError = (source) => {
if (!(source instanceof Error)) {
const err = new Error('Rejected promise');
err.data = source;
return next(Boom.wrap(err));
}
return next(Boom.wrap(source));
};
this.source.then(onDone, onError);
}...
internals.handler = function (request, callback) {
const timer = new Hoek.Bench();
const finalize = (response) => {
if (response === null) { // reply.continue()
response = Response.wrap(null, request);
return response._prepare(finalize);
}
// Check for Error result
if (response.isBoom) {
request._log(['handler', 'error'], { msec: timer.elapsed(), error: response.message, data: response });
return callback(response);
..._processPrepare = function (next) {
if (!this._processors.prepare) {
return next(this);
}
return this._processors.prepare(this, next);
}...
internals.Response.prototype._prepare = function (next) {
this._passThrough();
if (this.variety !== 'promise') {
return this._processPrepare(next);
}
const onDone = Hoek.nextTick((source) => {
if (source instanceof Error) {
return next(Boom.wrap(source));
}
..._rewritable = function (isRewritable) {
this._setRewritable(isRewritable !== false); // Defaults to true
return this;
}n/a
_setRewritable = function (isRewritable) {
if (isRewritable) {
if (this._isTemporary()) {
this.statusCode = 302;
}
else {
this.statusCode = 301;
}
}
else {
if (this._isTemporary()) {
this.statusCode = 307;
}
else {
this.statusCode = 308;
}
}
}...
this._setTemporary(isPermanent === false); // Defaults to true
return this;
};
internals.Response.prototype._rewritable = function (isRewritable) {
this._setRewritable(isRewritable !== false); // Defaults to true
return this;
};
internals.Response.prototype._isTemporary = function () {
return this.statusCode === 302 || this.statusCode === 307;
..._setSource = function (source, variety) {
// Method must not set any headers or other properties as source can change later
this.variety = variety || 'plain';
if (source === null ||
source === undefined ||
source === '') {
source = null;
}
else if (Buffer.isBuffer(source)) {
this.variety = 'buffer';
this._contentType = 'application/octet-stream';
}
else if (source instanceof Stream) {
this.variety = 'stream';
}
else if (typeof source === 'object' &&
typeof source.then === 'function') { // Promise object
this.variety = 'promise';
}
this.source = source;
if (this.variety === 'plain' &&
this.source !== null) {
this._contentType = (typeof this.source === 'string' ? 'text/html' : 'application/json');
}
}...
this._processors = {
marshal: options.marshal,
prepare: options.prepare,
close: options.close
};
this._setSource(source, options.variety);
};
Hoek.inherits(internals.Response, Podium);
internals.Response.wrap = function (result, request) {
..._setTemporary = function (isTemporary) {
if (isTemporary) {
if (this._isRewritable()) {
this.statusCode = 302;
}
else {
this.statusCode = 307;
}
}
else {
if (this._isRewritable()) {
this.statusCode = 301;
}
else {
this.statusCode = 308;
}
}
}...
this.rewritable = this._rewritable;
return this;
};
internals.Response.prototype._temporary = function (isTemporary) {
this._setTemporary(isTemporary !== false); // Defaults to true
return this;
};
internals.Response.prototype._permanent = function (isPermanent) {
this._setTemporary(isPermanent === false); // Defaults to true
..._streamify = function (source, next) {
if (source instanceof Stream) {
if (typeof source._read !== 'function' || typeof source._readableState !== 'object') {
return next(Boom.badImplementation('Stream must have a streams2 readable interface'));
}
if (source._readableState.objectMode) {
return next(Boom.badImplementation('Cannot reply with stream in object mode'));
}
this._payload = source;
return next();
}
let payload = source;
if (this.variety === 'plain' &&
source !== null &&
typeof source !== 'string') {
const options = this.settings.stringify || {};
const space = options.space || this.request.route.settings.json.space;
const replacer = options.replacer || this.request.route.settings.json.replacer;
const suffix = options.suffix || this.request.route.settings.json.suffix || '';
try {
if (replacer || space) {
payload = JSON.stringify(payload, replacer, space);
}
else {
payload = JSON.stringify(payload);
}
}
catch (err) {
return next(err);
}
if (suffix) {
payload = payload + suffix;
}
}
else if (this.settings.stringify) {
return next(Boom.badImplementation('Cannot set formatting options on non object response'));
}
this._payload = new internals.Payload(payload, this.settings);
return next();
}...
return this._processors.prepare(this, next);
};
internals.Response.prototype._marshal = function (next) {
if (!this._processors.marshal) {
return this._streamify(this.source, next);
}
this._processors.marshal(this, (err, source) => {
if (err) {
return next(err);
}
..._tap = function () {
return (this.hasListeners('finish') || this.hasListeners('peek') ? new internals.Peek(this) : null);
}...
if (failAction === 'error') {
return next(err);
}
return next();
};
Subtext.parse(request.raw.req, request._tap(), request.route.settings.payload, (err
, parsed) => {
if (!err ||
!request._isPayloadPending) {
request._isPayloadPending = false;
return onParsed(err, parsed);
}
..._temporary = function (isTemporary) {
this._setTemporary(isTemporary !== false); // Defaults to true
return this;
}n/a
bytes = function (bytes) {
this._header('content-length', bytes);
return this;
}...
// Prepare transform
if (ranges.length === 1) { // Ignore requests for multiple ranges
const range = ranges[0];
ranger = new Ammo.Stream(range);
response.code(206);
response.bytes(range.to - range.from + 1);
response._header('content-range', 'bytes ' + range.from + '-' + range.to + '/
x27; + length);
}
}
}
response._header('accept-ranges', 'bytes');
}
...charset = function (charset) {
this.settings.charset = charset || null;
return this;
}n/a
code = function (statusCode) {
Hoek.assert(Hoek.isInteger(statusCode), 'Status code must be an integer');
this.statusCode = statusCode;
return this;
}...
Hoek.assert(options, 'Entity method missing required options');
Hoek.assert(options.etag || options.modified, 'Entity methods missing require options key');
this.request._entity = options;
if (Response.unmodified(this.request, options)) {
return this.response().code(304).takeover();
}
return null;
};
...created = function (location) {
Hoek.assert(this.request.method === 'post' || this.request.method === 'put', 'Cannot create resource on GET');
this.statusCode = 201;
this.location(location);
return this;
}n/a
encoding = function (encoding) {
this.settings.encoding = encoding;
return this;
}...
Hoek.assert(typeof decoder === 'function', `Invalid decoder function for ${encoding}`);
this._decoders[encoding] = decoder;
};
internals.Compression.prototype.accept = function (request) {
return Accept.encoding(request.headers['accept-encoding'], this.encodings);
};
internals.Compression.prototype.encoding = function (response) {
const request = response.request;
if (!request.connection.settings.compression) {
...etag = function (tag, options) {
Hoek.assert(tag !== '*', 'ETag cannot be *');
options = options || {};
this._header('etag', (options.weak ? 'W/' : '') + '"' + tag + '"');
this.settings.varyEtag = options.vary !== false && !options.weak; // vary defaults to true
return this;
}...
const request = response.request;
// Set headers from reply.entity()
if (request._entity.etag &&
!response.headers.etag) {
response.etag(request._entity.etag, { vary: request._entity.vary });
}
if (request._entity.modified &&
!response.headers['last-modified']) {
response.header('last-modified', request._entity.modified);
}
...header = function (key, value, options) {
key = key.toLowerCase();
if (key === 'vary') {
return this.vary(value);
}
return this._header(key, value, options);
}...
if (headerKeys.length) {
const localHeaders = this.headers;
this.headers = {};
for (let i = 0; i < headerKeys.length; ++i) {
const key = headerKeys[i];
this.header(key.toLowerCase(), Hoek.clone(this.source.headers[key])); // Clone
arrays
}
headerKeys = Object.keys(localHeaders);
for (let i = 0; i < headerKeys.length; ++i) {
const key = headerKeys[i];
this.header(key, localHeaders[key], { append: key === 'set-cookie' });
}
...location = function (uri) {
this._header('location', uri);
return this;
}...
internals.Response.prototype.created = function (location) {
Hoek.assert(this.request.method === 'post' || this.request.method === 'put', 'Cannot create resource on
GET');
this.statusCode = 201;
this.location(location);
return this;
};
internals.Response.prototype.replacer = function (method) {
this.settings.stringify = this.settings.stringify || {};
...message = function (httpMessage) {
this.settings.message = httpMessage;
return this;
}n/a
passThrough = function (enabled) {
this.settings.passThrough = (enabled !== false); // Defaults to true
return this;
}n/a
redirect = function (location) {
this.statusCode = 302;
this.location(location);
this.temporary = this._temporary;
this.permanent = this._permanent;
this.rewritable = this._rewritable;
return this;
}...
this.request._clearState(name, options);
};
internals.redirect = function (location) {
return this.response('').redirect(location);
};
internals.response = function (result) {
Hoek.assert(!this._replied, 'reply interface called twice');
this._replied = true;
...replacer = function (method) {
this.settings.stringify = this.settings.stringify || {};
this.settings.stringify.replacer = method;
return this;
}n/a
spaces = function (count) {
this.settings.stringify = this.settings.stringify || {};
this.settings.stringify.space = count;
return this;
}n/a
state = function (name, value, options) { // options: see Defaults.state
this.request._setState(name, value, options);
return this;
}...
const response = request.response;
Cors.headers(response);
internals.content(response, false);
internals.security(response);
internals.unmodified(response);
internals.state(response, (err) => {
if (err) {
request._log(['state', 'response', 'error'], err);
request._states = {}; // Clear broken state
return next(err);
}
...suffix = function (suffix) {
this.settings.stringify = this.settings.stringify || {};
this.settings.stringify.suffix = suffix;
return this;
}n/a
takeover = function () {
this._takeover = true;
return this;
}...
};
/*
const handler = function (request, reply) {
reply(error, result, ignore); -> error || result (continue)
reply(...).takeover(); -> ... (continue)
reply.continue(ignore); -> null (continue)
};
const ext = function (request, reply) {
reply(error, result, ignore); -> error || result (respond)
...ttl = function (ttl) {
this.settings.ttl = ttl;
return this;
}...
const policy = request.route.settings.cache &&
request._route._cache &&
(request.route.settings.cache._statuses[response.statusCode] || (response.statusCode === 304 && request
.route.settings.cache._statuses['200']));
if (policy ||
response.settings.ttl) {
const ttl = (response.settings.ttl !== null ? response.settings.ttl : request._route._cache.ttl());
const privacy = (request.auth.isAuthenticated || response.headers['set-cookie'] ? 'private' : request
.route.settings.cache.privacy || 'default');
response._header('cache-control', 'max-age=' + Math.floor(ttl / 1000) + ', must-revalidate'
; + (privacy !== 'default' ? ', ' + privacy : ''));
}
else if (request.route.settings.cache) {
response._header('cache-control', request.route.settings.cache.otherwise);
}
};
...type = function (type) {
this._header('content-type', type);
return this;
}...
internals.Compression.prototype.encoding = function (response) {
const request = response.request;
if (!request.connection.settings.compression) {
return null;
}
const mime = request.server.mime.type(response.headers['content-type'] ||
x27;application/octet-stream');
if (!mime.compressible) {
return null;
}
response.vary('accept-encoding');
if (response.headers['content-encoding']) {
...unstate = function (name, options) {
this.request._clearState(name, options);
return this;
}n/a
vary = function (value) {
if (value === '*') {
this.headers.vary = '*';
}
else if (!this.headers.vary) {
this.headers.vary = value;
}
else if (this.headers.vary !== '*') {
this._header('vary', value, { append: true, duplicate: false });
}
return this;
}...
}
const mime = request.server.mime.type(response.headers['content-type'] || 'application/octet-stream');
if (!mime.compressible) {
return null;
}
response.vary('accept-encoding');
if (response.headers['content-encoding']) {
return null;
}
return (request.info.acceptEncoding === 'identity' ? null : request.info.acceptEncoding);
};
...route = function (route, connection, plugin, options) {
options = options || {};
// Apply plugin environment (before schema validation)
const realm = plugin.realm;
if (realm.modifiers.route.vhost ||
realm.modifiers.route.prefix) {
route = Hoek.cloneWithShallow(route, ['config']); // config is left unchanged
route.path = (realm.modifiers.route.prefix ? realm.modifiers.route.prefix + (route.path !== '/' ? route.path : '') : route
.path);
route.vhost = realm.modifiers.route.vhost || route.vhost;
}
// Setup and validate route configuration
Hoek.assert(route.path, 'Route missing path');
const routeDisplay = route.method + ' ' + route.path;
let config = route.config;
if (typeof config === 'function') {
config = config.call(realm.settings.bind, connection.server);
}
Hoek.assert(route.handler || (config && config.handler), 'Missing or undefined handler:', routeDisplay);
Hoek.assert(!!route.handler ^ !!(config && config.handler), 'Handler must only appear once:', routeDisplay); // XOR
Hoek.assert(route.path === '/' || route.path[route.path.length - 1] !== '/' || !connection.settings.router.stripTrailingSlash
, 'Path cannot end with a trailing slash when connection configured to strip:', routeDisplay);
route = Schema.apply('route', route, routeDisplay);
const handler = route.handler || config.handler;
const method = route.method.toLowerCase();
Hoek.assert(method !== 'head', 'Method name not allowed:', routeDisplay);
// Apply settings in order: {connection} <- {handler} <- {realm} <- {route}
const handlerDefaults = Handler.defaults(method, handler, connection.server);
let base = Hoek.applyToDefaultsWithShallow(connection.settings.routes, handlerDefaults, ['bind']);
base = Hoek.applyToDefaultsWithShallow(base, realm.settings, ['bind']);
this.settings = Hoek.applyToDefaultsWithShallow(base, config || {}, ['bind', 'validate.headers', 'validate.payload', 'validate
.params', 'validate.query']);
this.settings.handler = handler;
this.settings = Schema.apply('routeConfig', this.settings, routeDisplay);
const socketTimeout = (this.settings.timeout.socket === undefined ? 2 * 60 * 1000 : this.settings.timeout.socket);
Hoek.assert(!this.settings.timeout.server || !socketTimeout || this.settings.timeout.server < socketTimeout, 'Server timeout
must be shorter than socket timeout:', routeDisplay);
Hoek.assert(!this.settings.payload.timeout || !socketTimeout || this.settings.payload.timeout < socketTimeout, 'Payload timeout
must be shorter than socket timeout:', routeDisplay);
this.connection = connection;
this.server = connection.server;
this.path = route.path;
this.method = method;
this.plugin = plugin;
this.settings.vhost = route.vhost;
this.settings.plugins = this.settings.plugins || {}; // Route-specific plugins settings, namespaced using plugin
name
this.settings.app = this.settings.app || {}; // Route-specific application settings
// Path parsing
this._special = !!options.special;
this._analysis = this.connection._router.analyze(this.path);
this.params = this._analysis.params;
this.fingerprint = this._analysis.fingerprint;
this.public = {
method: this.method,
path: this.path,
vhost: this.vhost,
realm: this.plugin.realm,
settings: this.settings,
fingerprint: this.fingerprint,
auth: {
access: (request) => Auth.access(request, this.public)
}
};
// Validation
const validation = this.settings.validate;
if (this.method === 'get') {
// Assert on config, not on merged settings
Hoek.assert(!config || !config.payload, 'Cannot set payload settings on HEAD or GET request:', routeDisplay);
Hoek.assert(!config || !config.validate || !config.validate.payload, 'Cannot validate HEAD or GET requests:', routeDisplay
);
validation.payload = null;
}
['headers', ' ......
internals.Connection.prototype.match = function (method, path, host) {
Hoek.assert(method && typeof method === 'string', 'Invalid method:', method);
Hoek.assert(path && typeof path === 'string' && path[0] === '/', 'Invalid path:
x27;, path);
Hoek.assert(!host || typeof host === 'string', 'Invalid host:', host);
const match = this._router.route(method.toLowerCase(), path, host);
Hoek.assert(match !== this._router.specials.badRequest, 'Invalid path:', path);
if (match === this._router.specials.notFound) {
return null;
}
return match.route.public;
};
..._combineExtensions = function (type, subscribe) {
const ext = new Ext(type, this.server);
const events = this.settings.ext[type];
if (events) {
for (let i = 0; i < events.length; ++i) {
const event = Hoek.shallow(events[i]);
Hoek.assert(!event.options.sandbox, 'Cannot specify sandbox option for route extension');
event.plugin = this.plugin;
ext.add(event);
}
}
const connection = this.connection._extensions[type];
const realm = this.plugin.realm._extensions[type];
ext.merge([connection, realm]);
connection.subscribe(this);
realm.subscribe(this);
return ext;
}...
this.settings.handler = Handler.configure(this.settings.handler, this);
this._prerequisites = Handler.prerequisitesConfig(this.settings.pre, this.server);
// Route lifecycle
this._extensions = {
onPreResponse: this._combineExtensions('onPreResponse')
};
if (this._special) {
this._cycle = [Handler.execute];
return;
}
...rebuild = function (event) {
if (event) {
this._extensions[event.type].add(event);
if (event.type === 'onPreResponse') {
return;
}
}
// Build lifecycle array
const cycle = [];
// 'onRequest'
if (this.settings.jsonp) {
cycle.push(internals.parseJSONP);
}
if (this.settings.state.parse) {
cycle.push(internals.state);
}
if (this._extensions.onPreAuth.nodes) {
cycle.push(this._extensions.onPreAuth);
}
const authenticate = (this.settings.auth !== false); // Anything other than 'false' can still require
authentication
if (authenticate) {
cycle.push(Auth.authenticate);
}
if (this.method !== 'get') {
cycle.push(internals.payload);
if (authenticate) {
cycle.push(Auth.payload);
}
}
if (this._extensions.onPostAuth.nodes) {
cycle.push(this._extensions.onPostAuth);
}
if (this.settings.validate.headers) {
cycle.push(Validation.headers);
}
if (this.settings.validate.params) {
cycle.push(Validation.params);
}
if (this.settings.jsonp) {
cycle.push(internals.cleanupJSONP);
}
if (this.settings.validate.query) {
cycle.push(Validation.query);
}
if (this.settings.validate.payload) {
cycle.push(Validation.payload);
}
if (this._extensions.onPreHandler.nodes) {
cycle.push(this._extensions.onPreHandler);
}
cycle.push(Handler.execute); // Must not call next() with an Error
if (this._extensions.onPostHandler.nodes) {
cycle.push(this._extensions.onPostHandler); // An error from here on will override any result set in handler
()
}
if (this.settings.response._validate &&
this.settings.response.sample !== 0) {
cycle.push(Validation.response);
}
this._cycle = cycle;
}...
}
this.nodes = this._topo.nodes;
// Notify routes
for (let i = 0; i < this._routes.length; ++i) {
this._routes[i].rebuild(event);
}
};
internals.Ext.prototype.merge = function (others) {
const merge = [];
...apply = function (type, options, message) {
const result = Joi.validate(options, internals[type]);
Hoek.assert(!result.error, 'Invalid', type, 'options', message ? '(' + message + ')' : '', result.error && result.error.annotate
());
return result.value;
}...
}
};
internals.Auth.prototype.default = function (options) {
Hoek.assert(!this.settings.default, 'Cannot set default strategy more than once');
options = Schema.apply('auth', options, 'default strategy');
this.settings.default = this._setupRoute(Hoek.clone(options)); // Can change options
};
internals.Auth.prototype.test = function (name, request, next) {
...send = function (request, callback) {
const response = request.response;
if (response.isBoom) {
return internals.fail(request, response, callback);
}
internals.marshal(request, (err) => {
if (err) {
request._setResponse(err);
return internals.fail(request, err, callback);
}
return internals.transmit(response, (err) => {
if (err) {
request._setResponse(err);
return internals.fail(request, err, callback);
}
return callback();
});
});
}...
const transmit = (err) => {
if (err) { // Can be valid response or error
this._setResponse(Response.wrap(err, this));
}
return Transmit.send(this, () => this._finalize());
};
if (!this._route._extensions.onPreResponse.nodes) {
return transmit();
}
return this._invoke(this._route._extensions.onPreResponse, transmit);
...compile = function (rule) {
// null, undefined, true - anything allowed
// false - nothing allowed
// {...} - ... allowed
return (rule === false ? Joi.object({}).allow(null)
: typeof rule === 'function' ? rule
: !rule || rule === true ? null // false tested earlier
: Joi.compile(rule));
}...
Hoek.assert(!config || !config.validate || !config.validate.payload, 'Cannot validate HEAD or GET requests:', routeDisplay
);
validation.payload = null;
}
['headers', 'params', 'query', 'payload'].forEach((type) => {
validation[type] = Validation.compile(validation[type]);
});
if (this.settings.response.schema !== undefined ||
this.settings.response.status) {
this.settings.response._validate = true;
...headers = function (request, next) {
return internals.input('headers', request, next);
}...
};
internals.marshal = function (request, next) {
const response = request.response;
Cors.headers(response);
internals.content(response, false);
internals.security(response);
internals.unmodified(response);
internals.state(response, (err) => {
if (err) {
...params = function (request, next) {
return internals.input('params', request, next);
}n/a
payload = function (request, next) {
if (request.method === 'get' ||
request.method === 'head') { // When route.method is '*'
return next();
}
return internals.input('payload', request, next);
}...
return next(response);
};
request._protect.run(finalize, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.payload(request, reply);
});
};
internals.Auth.response = function (request, next) {
const auth = request.connection.auth;
...query = function (request, next) {
return internals.input('query', request, next);
}n/a
response = function (request, next) {
if (request.route.settings.response.sample) {
const currentSample = Math.ceil((Math.random() * 100));
if (currentSample > request.route.settings.response.sample) {
return next();
}
}
const response = request.response;
const statusCode = response.isBoom ? response.output.statusCode : response.statusCode;
const statusSchema = request.route.settings.response.status[statusCode];
if (statusCode >= 400 &&
!statusSchema) {
return next(); // Do not validate errors by default
}
const schema = statusSchema || request.route.settings.response.schema;
if (schema === null) {
return next(); // No rules
}
if (!response.isBoom &&
request.response.variety !== 'plain') {
return next(Boom.badImplementation('Cannot validate non-object response'));
}
const postValidate = (err, value) => {
if (!err) {
if (value !== undefined &&
request.route.settings.response.modify) {
if (response.isBoom) {
response.output.payload = value;
}
else {
response.source = value;
}
}
return next();
}
// failAction: 'error', 'log'
if (request.route.settings.response.failAction === 'log') {
request._log(['validation', 'response', 'error'], err.message);
return next();
}
// Return error
if (typeof request.route.settings.response.failAction !== 'function') {
return next(Boom.badImplementation(err.message));
}
// Custom handler
request._protect.run(next, (exit) => {
const reply = request.server._replier.interface(request, request.route.realm, {}, exit);
request.route.settings.response.failAction(request, reply, err);
});
};
const localOptions = {
context: {
headers: request.headers,
params: request.params,
query: request.query,
payload: request.payload,
auth: {
isAuthenticated: request.auth.isAuthenticated,
credentials: request.auth.credentials
}
}
};
const source = response.isBoom ? response.output.payload : response.source;
Hoek.merge(localOptions, request.route.settings.response.options);
if (typeof schema !== 'function') {
return Joi.validate(source, schema, localOptions, postValidate);
}
request._protect.run(postValidate, (exit) => {
return schema(source, localOptions, exit);
});
}...
if (!strategy.methods.response) {
return next();
}
request._protect.run(next, (exit) => {
const reply = request.server._replier.interface(request, strategy.realm, {}, exit);
strategy.methods.response(request, reply);
});
};
internals.Authenticator = class {
constructor(config, request, manager) {
...