utils.js 4.3 KB

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