import { EventEmitter } from 'node:events' var s = 0, STATE_DICT = { PARSER_UNINITIALIZED: s++, START: s++, START_BOUNDARY: s++, HEADER_FIELD_START: s++, HEADER_FIELD: s++, HEADER_VALUE_START: s++, HEADER_VALUE: s++, HEADER_VALUE_ALMOST_DONE: s++, HEADERS_ALMOST_DONE: s++, PART_DATA_START: s++, PART_DATA: s++, PART_END: s++, END: s++ }, f = 1, F = { PART_BOUNDARY: f, LAST_BOUNDARY: (f *= 2) }, LF = 10, CR = 13, SPACE = 32, HYPHEN = 45, COLON = 58, A = 97, Z = 122, lower = function (c) { return c | 0x20 } function stateToString(stateNumber) { for (let state in STATE_DICT) { let number = STATE_DICT[state] if (number === stateNumber) { return state } } } export class MultipartParser extends EventEmitter { boundary = null boundaryChars = null lookbehind = null state = STATE_DICT.PARSER_UNINITIALIZED index = null flags = 0 constructor(str) { super() this.boundary = Buffer.alloc(str.length + 4) this.boundary.write('\r\n--', 0) this.boundary.write(str, 4) this.lookbehind = Buffer.alloc(this.boundary.length + 8) this.state = STATE_DICT.START this.boundaryChars = {} for (let i = 0; i < this.boundary.length; i++) { this.boundaryChars[this.boundary[i]] = true } } write(buffer) { var i = 0, len = buffer.length, prevIndex = this.index, index = this.index, state = this.state, flags = this.flags, lookbehind = this.lookbehind, boundary = this.boundary, boundaryChars = this.boundaryChars, boundaryLength = this.boundary.length, boundaryEnd = boundaryLength - 1, bufferLength = buffer.length, c, cl let mark = (name) => { this[name + 'Mark'] = i }, dataCallback = (name, clear) => { var markSymbol = name + 'Mark' if ((markSymbol in this)) { if (clear) { this.emit(name, buffer, this[markSymbol], i) delete this[markSymbol] } else { this.emit(name, buffer, this[markSymbol], buffer.length) this[markSymbol] = 0 } } } // console.log('???? ', state, 'len: ', len); for (i = 0; i < len; i++) { c = buffer[i] switch (state) { case STATE_DICT.PARSER_UNINITIALIZED: return i case STATE_DICT.START: index = 0 state = STATE_DICT.START_BOUNDARY case STATE_DICT.START_BOUNDARY: // console.log('=====>>>', index, c, boundary); if (index == boundary.length - 2) { if (c == HYPHEN) { flags |= F.LAST_BOUNDARY } else if (c != CR) { return i } index++ break } else if (index - 1 == boundary.length - 2) { if (flags & F.LAST_BOUNDARY && c == HYPHEN) { this.emit('end') state = STATE_DICT.END flags = 0 } else if (!(flags & F.LAST_BOUNDARY) && c == LF) { index = 0 this.emit('partBegin') state = STATE_DICT.HEADER_FIELD_START } else { return i } break } if (c != boundary[index + 2]) { index = -2 } if (c == boundary[index + 2]) { index++ } break case STATE_DICT.HEADER_FIELD_START: state = STATE_DICT.HEADER_FIELD mark('headerField') index = 0 case STATE_DICT.HEADER_FIELD: if (c == CR) { delete this.headerFieldMark state = STATE_DICT.HEADERS_ALMOST_DONE break } index++ if (c == HYPHEN) { break } if (c == COLON) { if (index == 1) { // empty header field return i } dataCallback('headerField', true) state = STATE_DICT.HEADER_VALUE_START break } cl = lower(c) if (cl < A || cl > Z) { return i } break case STATE_DICT.HEADER_VALUE_START: if (c == SPACE) { break } mark('headerValue') state = STATE_DICT.HEADER_VALUE case STATE_DICT.HEADER_VALUE: if (c == CR) { dataCallback('headerValue', true) this.emit('headerEnd') state = STATE_DICT.HEADER_VALUE_ALMOST_DONE } break case STATE_DICT.HEADER_VALUE_ALMOST_DONE: if (c != LF) { return i } state = STATE_DICT.HEADER_FIELD_START break case STATE_DICT.HEADERS_ALMOST_DONE: if (c != LF) { return i } this.emit('headersEnd') state = STATE_DICT.PART_DATA_START break case STATE_DICT.PART_DATA_START: state = STATE_DICT.PART_DATA mark('partData') case STATE_DICT.PART_DATA: prevIndex = index if (index === 0) { // boyer-moore derrived algorithm to safely skip non-boundary data i += boundaryEnd while (i < bufferLength && !(buffer[i] in boundaryChars)) { i += boundaryLength } i -= boundaryEnd c = buffer[i] } if (index < boundary.length) { if (boundary[index] == c) { if (index === 0) { dataCallback('partData', true) } index++ } else { index = 0 } } else if (index == boundary.length) { index++ if (c == CR) { // CR = part boundary flags |= F.PART_BOUNDARY } else if (c == HYPHEN) { // HYPHEN = end boundary flags |= F.LAST_BOUNDARY } else { index = 0 } } else if (index - 1 == boundary.length) { if (flags & F.PART_BOUNDARY) { index = 0 if (c == LF) { // unset the PART_BOUNDARY flag flags &= ~F.PART_BOUNDARY this.emit('partEnd') this.emit('partBegin') state = STATE_DICT.HEADER_FIELD_START break } } else if (flags & F.LAST_BOUNDARY) { if (c == HYPHEN) { this.emit('partEnd') this.emit('end') state = STATE_DICT.END flags = 0 } else { index = 0 } } else { index = 0 } } if (index > 0) { // when matching a possible boundary, keep a lookbehind reference // in case it turns out to be a false lead lookbehind[index - 1] = c } else if (prevIndex > 0) { // if our boundary turned out to be rubbish, the captured lookbehind // belongs to partData this.emit('partData', lookbehind, 0, prevIndex) prevIndex = 0 mark('partData') // reconsider the current character even so it interrupted the sequence // it could be the beginning of a new sequence i-- } break case STATE_DICT.END: break default: return i } } dataCallback('headerField') dataCallback('headerValue') dataCallback('partData') this.index = index this.state = state this.flags = flags } end() { if ( (this.state === STATE_DICT.HEADER_FIELD_START && this.index === 0) || (this.state === STATE_DICT.PART_DATA && this.index == this.boundary.length) ) { this.emit('end') } else if (this.state !== STATE_DICT.END) { return new Error( 'MultipartParser.end(): stream ended unexpectedly: ' + this.explain() ) } } explain() { return 'state = ' + stateToString(this.state) } }