184 lines
3.9 KiB
JavaScript
184 lines
3.9 KiB
JavaScript
|
// Load modules
|
||
|
|
||
|
var Hoek = require('hoek');
|
||
|
var Sntp = require('sntp');
|
||
|
var Boom = require('boom');
|
||
|
|
||
|
|
||
|
// Declare internals
|
||
|
|
||
|
var internals = {};
|
||
|
|
||
|
|
||
|
// Import Hoek Utilities
|
||
|
|
||
|
internals.import = function () {
|
||
|
|
||
|
for (var i in Hoek) {
|
||
|
if (Hoek.hasOwnProperty(i)) {
|
||
|
exports[i] = Hoek[i];
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
internals.import();
|
||
|
|
||
|
|
||
|
// Hawk version
|
||
|
|
||
|
exports.version = function () {
|
||
|
|
||
|
return exports.loadPackage(__dirname + '/..').version;
|
||
|
};
|
||
|
|
||
|
|
||
|
// Extract host and port from request
|
||
|
|
||
|
exports.parseHost = function (req, hostHeaderName) {
|
||
|
|
||
|
hostHeaderName = (hostHeaderName ? hostHeaderName.toLowerCase() : 'host');
|
||
|
var hostHeader = req.headers[hostHeaderName];
|
||
|
if (!hostHeader) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
var hostHeaderRegex;
|
||
|
if (hostHeader[0] === '[') {
|
||
|
hostHeaderRegex = /^(?:(?:\r\n)?\s)*(\[[^\]]+\])(?::(\d+))?(?:(?:\r\n)?\s)*$/; // IPv6
|
||
|
}
|
||
|
else {
|
||
|
hostHeaderRegex = /^(?:(?:\r\n)?\s)*([^:]+)(?::(\d+))?(?:(?:\r\n)?\s)*$/; // IPv4, hostname
|
||
|
}
|
||
|
|
||
|
var hostParts = hostHeader.match(hostHeaderRegex);
|
||
|
|
||
|
if (!hostParts ||
|
||
|
hostParts.length !== 3 ||
|
||
|
!hostParts[1]) {
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
name: hostParts[1],
|
||
|
port: (hostParts[2] ? hostParts[2] : (req.connection && req.connection.encrypted ? 443 : 80))
|
||
|
};
|
||
|
};
|
||
|
|
||
|
|
||
|
// Parse Content-Type header content
|
||
|
|
||
|
exports.parseContentType = function (header) {
|
||
|
|
||
|
if (!header) {
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
return header.split(';')[0].trim().toLowerCase();
|
||
|
};
|
||
|
|
||
|
|
||
|
// Convert node's to request configuration object
|
||
|
|
||
|
exports.parseRequest = function (req, options) {
|
||
|
|
||
|
if (!req.headers) {
|
||
|
return req;
|
||
|
}
|
||
|
|
||
|
// Obtain host and port information
|
||
|
|
||
|
if (!options.host || !options.port) {
|
||
|
var host = exports.parseHost(req, options.hostHeaderName);
|
||
|
if (!host) {
|
||
|
return new Error('Invalid Host header');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var request = {
|
||
|
method: req.method,
|
||
|
url: req.url,
|
||
|
host: options.host || host.name,
|
||
|
port: options.port || host.port,
|
||
|
authorization: req.headers.authorization,
|
||
|
contentType: req.headers['content-type'] || ''
|
||
|
};
|
||
|
|
||
|
return request;
|
||
|
};
|
||
|
|
||
|
|
||
|
exports.now = function () {
|
||
|
|
||
|
return Sntp.now();
|
||
|
};
|
||
|
|
||
|
|
||
|
// Parse Hawk HTTP Authorization header
|
||
|
|
||
|
exports.parseAuthorizationHeader = function (header, keys) {
|
||
|
|
||
|
keys = keys || ['id', 'ts', 'nonce', 'hash', 'ext', 'mac', 'app', 'dlg'];
|
||
|
|
||
|
if (!header) {
|
||
|
return Boom.unauthorized(null, 'Hawk');
|
||
|
}
|
||
|
|
||
|
var headerParts = header.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something]
|
||
|
if (!headerParts) {
|
||
|
return Boom.badRequest('Invalid header syntax');
|
||
|
}
|
||
|
|
||
|
var scheme = headerParts[1];
|
||
|
if (scheme.toLowerCase() !== 'hawk') {
|
||
|
return Boom.unauthorized(null, 'Hawk');
|
||
|
}
|
||
|
|
||
|
var attributesString = headerParts[2];
|
||
|
if (!attributesString) {
|
||
|
return Boom.badRequest('Invalid header syntax');
|
||
|
}
|
||
|
|
||
|
var attributes = {};
|
||
|
var errorMessage = '';
|
||
|
var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) {
|
||
|
|
||
|
// Check valid attribute names
|
||
|
|
||
|
if (keys.indexOf($1) === -1) {
|
||
|
errorMessage = 'Unknown attribute: ' + $1;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
|
||
|
|
||
|
if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) {
|
||
|
errorMessage = 'Bad attribute value: ' + $1;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check for duplicates
|
||
|
|
||
|
if (attributes.hasOwnProperty($1)) {
|
||
|
errorMessage = 'Duplicate attribute: ' + $1;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
attributes[$1] = $2;
|
||
|
return '';
|
||
|
});
|
||
|
|
||
|
if (verify !== '') {
|
||
|
return Boom.badRequest(errorMessage || 'Bad header format');
|
||
|
}
|
||
|
|
||
|
return attributes;
|
||
|
};
|
||
|
|
||
|
|
||
|
exports.unauthorized = function (message) {
|
||
|
|
||
|
return Boom.unauthorized(message, 'Hawk');
|
||
|
};
|
||
|
|