HeaderParser.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. 'use strict'
  2. const EventEmitter = require('node:events').EventEmitter
  3. const inherits = require('node:util').inherits
  4. const getLimit = require('../../../lib/utils/getLimit')
  5. const StreamSearch = require('../../streamsearch/sbmh')
  6. const B_DCRLF = Buffer.from('\r\n\r\n')
  7. const RE_CRLF = /\r\n/g
  8. const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/ // eslint-disable-line no-control-regex
  9. function HeaderParser (cfg) {
  10. EventEmitter.call(this)
  11. cfg = cfg || {}
  12. const self = this
  13. this.nread = 0
  14. this.maxed = false
  15. this.npairs = 0
  16. this.maxHeaderPairs = getLimit(cfg, 'maxHeaderPairs', 2000)
  17. this.maxHeaderSize = getLimit(cfg, 'maxHeaderSize', 80 * 1024)
  18. this.buffer = ''
  19. this.header = {}
  20. this.finished = false
  21. this.ss = new StreamSearch(B_DCRLF)
  22. this.ss.on('info', function (isMatch, data, start, end) {
  23. if (data && !self.maxed) {
  24. if (self.nread + end - start >= self.maxHeaderSize) {
  25. end = self.maxHeaderSize - self.nread + start
  26. self.nread = self.maxHeaderSize
  27. self.maxed = true
  28. } else { self.nread += (end - start) }
  29. self.buffer += data.toString('binary', start, end)
  30. }
  31. if (isMatch) { self._finish() }
  32. })
  33. }
  34. inherits(HeaderParser, EventEmitter)
  35. HeaderParser.prototype.push = function (data) {
  36. const r = this.ss.push(data)
  37. if (this.finished) { return r }
  38. }
  39. HeaderParser.prototype.reset = function () {
  40. this.finished = false
  41. this.buffer = ''
  42. this.header = {}
  43. this.ss.reset()
  44. }
  45. HeaderParser.prototype._finish = function () {
  46. if (this.buffer) { this._parseHeader() }
  47. this.ss.matches = this.ss.maxMatches
  48. const header = this.header
  49. this.header = {}
  50. this.buffer = ''
  51. this.finished = true
  52. this.nread = this.npairs = 0
  53. this.maxed = false
  54. this.emit('header', header)
  55. }
  56. HeaderParser.prototype._parseHeader = function () {
  57. if (this.npairs === this.maxHeaderPairs) { return }
  58. const lines = this.buffer.split(RE_CRLF)
  59. const len = lines.length
  60. let m, h
  61. for (var i = 0; i < len; ++i) { // eslint-disable-line no-var
  62. if (lines[i].length === 0) { continue }
  63. if (lines[i][0] === '\t' || lines[i][0] === ' ') {
  64. // folded header content
  65. // RFC2822 says to just remove the CRLF and not the whitespace following
  66. // it, so we follow the RFC and include the leading whitespace ...
  67. if (h) {
  68. this.header[h][this.header[h].length - 1] += lines[i]
  69. continue
  70. }
  71. }
  72. const posColon = lines[i].indexOf(':')
  73. if (
  74. posColon === -1 ||
  75. posColon === 0
  76. ) {
  77. return
  78. }
  79. m = RE_HDR.exec(lines[i])
  80. h = m[1].toLowerCase()
  81. this.header[h] = this.header[h] || []
  82. this.header[h].push((m[2] || ''))
  83. if (++this.npairs === this.maxHeaderPairs) { break }
  84. }
  85. }
  86. module.exports = HeaderParser