| 'use strict'; |
| |
| // Load modules |
| |
| const Sntp = require('sntp'); |
| const Boom = require('boom'); |
| |
| |
| // Declare internals |
| |
| const internals = {}; |
| |
| |
| exports.version = function () { |
| |
| return require('../package.json').version; |
| }; |
| |
| |
| exports.limits = { |
| maxMatchLength: 4096 // Limit the length of uris and headers to avoid a DoS attack on string matching |
| }; |
| |
| |
| // Extract host and port from request |
| |
| // $1 $2 |
| internals.hostHeaderRegex = /^(?:(?:\r\n)?\s)*((?:[^:]+)|(?:\[[^\]]+\]))(?::(\d+))?(?:(?:\r\n)?\s)*$/; // (IPv4, hostname)|(IPv6) |
| |
| |
| exports.parseHost = function (req, hostHeaderName) { |
| |
| hostHeaderName = (hostHeaderName ? hostHeaderName.toLowerCase() : 'host'); |
| const hostHeader = req.headers[hostHeaderName]; |
| if (!hostHeader) { |
| return null; |
| } |
| |
| if (hostHeader.length > exports.limits.maxMatchLength) { |
| return null; |
| } |
| |
| const hostParts = hostHeader.match(internals.hostHeaderRegex); |
| if (!hostParts) { |
| 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 |
| |
| let host; |
| if (!options.host || |
| !options.port) { |
| |
| host = exports.parseHost(req, options.hostHeaderName); |
| if (!host) { |
| return new Error('Invalid Host header'); |
| } |
| } |
| |
| const 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 (localtimeOffsetMsec) { |
| |
| return Sntp.now() + (localtimeOffsetMsec || 0); |
| }; |
| |
| |
| exports.nowSecs = function (localtimeOffsetMsec) { |
| |
| return Math.floor(exports.now(localtimeOffsetMsec) / 1000); |
| }; |
| |
| |
| internals.authHeaderRegex = /^(\w+)(?:\s+(.*))?$/; // Header: scheme[ something] |
| internals.attributeRegex = /^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/; // !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9 |
| |
| |
| // 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'); |
| } |
| |
| if (header.length > exports.limits.maxMatchLength) { |
| return Boom.badRequest('Header length too long'); |
| } |
| |
| const headerParts = header.match(internals.authHeaderRegex); |
| if (!headerParts) { |
| return Boom.badRequest('Invalid header syntax'); |
| } |
| |
| const scheme = headerParts[1]; |
| if (scheme.toLowerCase() !== 'hawk') { |
| return Boom.unauthorized(null, 'Hawk'); |
| } |
| |
| const attributesString = headerParts[2]; |
| if (!attributesString) { |
| return Boom.badRequest('Invalid header syntax'); |
| } |
| |
| const attributes = {}; |
| let errorMessage = ''; |
| const verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, ($0, $1, $2) => { |
| |
| // Check valid attribute names |
| |
| if (keys.indexOf($1) === -1) { |
| errorMessage = 'Unknown attribute: ' + $1; |
| return; |
| } |
| |
| // Allowed attribute value characters |
| |
| if ($2.match(internals.attributeRegex) === 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, attributes) { |
| |
| return Boom.unauthorized(message || null, 'Hawk', attributes); |
| }; |
| |