utils.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. 'use strict';
  2. // Load modules
  3. const Sntp = require('sntp');
  4. const Boom = require('boom');
  5. // Declare internals
  6. const internals = {};
  7. exports.version = function () {
  8. return require('../package.json').version;
  9. };
  10. exports.limits = {
  11. maxMatchLength: 4096 // Limit the length of uris and headers to avoid a DoS attack on string matching
  12. };
  13. // Extract host and port from request
  14. // $1 $2
  15. internals.hostHeaderRegex = /^(?:(?:\r\n)?\s)*((?:[^:]+)|(?:\[[^\]]+\]))(?::(\d+))?(?:(?:\r\n)?\s)*$/; // (IPv4, hostname)|(IPv6)
  16. exports.parseHost = function (req, hostHeaderName) {
  17. hostHeaderName = (hostHeaderName ? hostHeaderName.toLowerCase() : 'host');
  18. const hostHeader = req.headers[hostHeaderName];
  19. if (!hostHeader) {
  20. return null;
  21. }
  22. if (hostHeader.length > exports.limits.maxMatchLength) {
  23. return null;
  24. }
  25. const hostParts = hostHeader.match(internals.hostHeaderRegex);
  26. if (!hostParts) {
  27. return null;
  28. }
  29. return {
  30. name: hostParts[1],
  31. port: (hostParts[2] ? hostParts[2] : (req.connection && req.connection.encrypted ? 443 : 80))
  32. };
  33. };
  34. // Parse Content-Type header content
  35. exports.parseContentType = function (header) {
  36. if (!header) {
  37. return '';
  38. }
  39. return header.split(';')[0].trim().toLowerCase();
  40. };
  41. // Convert node's to request configuration object
  42. exports.parseRequest = function (req, options) {
  43. if (!req.headers) {
  44. return req;
  45. }
  46. // Obtain host and port information
  47. let host;
  48. if (!options.host ||
  49. !options.port) {
  50. host = exports.parseHost(req, options.hostHeaderName);
  51. if (!host) {
  52. return new Error('Invalid Host header');
  53. }
  54. }
  55. const request = {
  56. method: req.method,
  57. url: req.url,
  58. host: options.host || host.name,
  59. port: options.port || host.port,
  60. authorization: req.headers.authorization,
  61. contentType: req.headers['content-type'] || ''
  62. };
  63. return request;
  64. };
  65. exports.now = function (localtimeOffsetMsec) {
  66. return Sntp.now() + (localtimeOffsetMsec || 0);
  67. };
  68. exports.nowSecs = function (localtimeOffsetMsec) {
  69. return Math.floor(exports.now(localtimeOffsetMsec) / 1000);
  70. };
  71. internals.authHeaderRegex = /^(\w+)(?:\s+(.*))?$/; // Header: scheme[ something]
  72. internals.attributeRegex = /^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/; // !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
  73. // Parse Hawk HTTP Authorization header
  74. exports.parseAuthorizationHeader = function (header, keys) {
  75. keys = keys || ['id', 'ts', 'nonce', 'hash', 'ext', 'mac', 'app', 'dlg'];
  76. if (!header) {
  77. return Boom.unauthorized(null, 'Hawk');
  78. }
  79. if (header.length > exports.limits.maxMatchLength) {
  80. return Boom.badRequest('Header length too long');
  81. }
  82. const headerParts = header.match(internals.authHeaderRegex);
  83. if (!headerParts) {
  84. return Boom.badRequest('Invalid header syntax');
  85. }
  86. const scheme = headerParts[1];
  87. if (scheme.toLowerCase() !== 'hawk') {
  88. return Boom.unauthorized(null, 'Hawk');
  89. }
  90. const attributesString = headerParts[2];
  91. if (!attributesString) {
  92. return Boom.badRequest('Invalid header syntax');
  93. }
  94. const attributes = {};
  95. let errorMessage = '';
  96. const verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, ($0, $1, $2) => {
  97. // Check valid attribute names
  98. if (keys.indexOf($1) === -1) {
  99. errorMessage = 'Unknown attribute: ' + $1;
  100. return;
  101. }
  102. // Allowed attribute value characters
  103. if ($2.match(internals.attributeRegex) === null) {
  104. errorMessage = 'Bad attribute value: ' + $1;
  105. return;
  106. }
  107. // Check for duplicates
  108. if (attributes.hasOwnProperty($1)) {
  109. errorMessage = 'Duplicate attribute: ' + $1;
  110. return;
  111. }
  112. attributes[$1] = $2;
  113. return '';
  114. });
  115. if (verify !== '') {
  116. return Boom.badRequest(errorMessage || 'Bad header format');
  117. }
  118. return attributes;
  119. };
  120. exports.unauthorized = function (message, attributes) {
  121. return Boom.unauthorized(message || null, 'Hawk', attributes);
  122. };