You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					185 lines
				
				4.4 KiB
			
		
		
			
		
	
	
					185 lines
				
				4.4 KiB
			| 
											3 years ago
										 | // Load modules
 | ||
|  | 
 | ||
|  | var Sntp = require('sntp'); | ||
|  | var Boom = require('boom'); | ||
|  | 
 | ||
|  | 
 | ||
|  | // Declare internals
 | ||
|  | 
 | ||
|  | var 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'); | ||
|  |     var hostHeader = req.headers[hostHeaderName]; | ||
|  |     if (!hostHeader) { | ||
|  |         return null; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (hostHeader.length > exports.limits.maxMatchLength) { | ||
|  |         return null; | ||
|  |     } | ||
|  | 
 | ||
|  |     var 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
 | ||
|  | 
 | ||
|  |     var host; | ||
|  |     if (!options.host || | ||
|  |         !options.port) { | ||
|  | 
 | ||
|  |         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 (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'); | ||
|  |     } | ||
|  | 
 | ||
|  |     var headerParts = header.match(internals.authHeaderRegex); | ||
|  |     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
 | ||
|  | 
 | ||
|  |         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, 'Hawk', attributes); | ||
|  | }; | ||
|  | 
 |