request/lib/index.js

526 lines
12 KiB
JavaScript
Raw Normal View History

2023-10-25 18:45:16 +08:00
import crypto from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
import { EventEmitter } from 'node:events'
import { Stream } from 'node:stream'
import { StringDecoder } from 'node:string_decoder'
2020-09-16 20:07:28 +08:00
2023-10-26 19:02:46 +08:00
import File from './file.js'
2020-09-16 20:07:28 +08:00
import { MultipartParser } from './multipart_parser.js'
import { QuerystringParser } from './querystring_parser.js'
import { OctetParser } from './octet_parser.js'
import { JSONParser } from './json_parser.js'
2023-10-25 18:45:16 +08:00
function dummyParser(self) {
return {
2023-10-26 19:02:46 +08:00
end: function () {
2023-10-25 18:45:16 +08:00
self.ended = true
self._maybeEnd()
return null
}
}
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
export default class IncomingForm extends EventEmitter {
#req = null
error = null
ended = false
headers = null
type = null
2023-10-25 18:45:16 +08:00
2023-10-26 19:02:46 +08:00
bytesReceived = null
bytesExpected = null
_parser = null
_flushing = 0
_fieldsSize = 0
openedFiles = []
constructor(req, opts = {}) {
super()
this.#req = req
2023-10-25 18:45:16 +08:00
this.maxFields = opts.maxFields || 1000
this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024
this.keepExtensions = opts.keepExtensions || false
this.uploadDir = opts.uploadDir
this.encoding = opts.encoding || 'utf-8'
this.multiples = opts.multiples || false
// Parse headers and setup the parser, ready to start listening for data.
this.writeHeaders(req.headers)
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
req
2023-10-26 19:02:46 +08:00
.on('error', err => {
this._error(err)
2023-10-25 18:45:16 +08:00
})
2023-10-26 19:02:46 +08:00
.on('aborted', () => {
this.emit('aborted')
this._error(new Error('Request aborted'))
2023-10-25 18:45:16 +08:00
})
2023-10-26 19:02:46 +08:00
.on('data', buffer => {
this.write(buffer)
2020-09-16 20:07:28 +08:00
})
2023-10-26 19:02:46 +08:00
.on('end', () => {
if (this.error) {
2023-10-25 18:45:16 +08:00
return
}
2023-10-26 19:02:46 +08:00
var err = this._parser.end()
2023-10-25 18:45:16 +08:00
if (err) {
2023-10-26 19:02:46 +08:00
this._error(err)
2023-10-25 18:45:16 +08:00
}
2020-09-16 20:07:28 +08:00
})
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
writeHeaders(headers) {
this.headers = headers
2023-10-26 19:02:46 +08:00
this.#parseContentLength()
this.#parseContentType()
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
write(buffer) {
if (this.error) {
return
}
if (!this._parser) {
this._error(new Error('uninitialized parser'))
return
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this.bytesReceived += buffer.length
this.emit('progress', this.bytesReceived, this.bytesExpected)
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var bytesParsed = this._parser.write(buffer)
if (bytesParsed !== buffer.length) {
this._error(
new Error(
'parser error, ' +
bytesParsed +
' of ' +
buffer.length +
' bytes parsed'
)
2020-09-16 20:07:28 +08:00
)
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
return bytesParsed
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
pause() {
2023-10-26 19:02:46 +08:00
try {
this.#req.pause()
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err)
}
return false
}
return true
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
resume() {
2023-10-26 19:02:46 +08:00
try {
this.#req.resume()
} catch (err) {
// the stream was destroyed
if (!this.ended) {
// before it was completed, crash & burn
this._error(err)
}
return false
}
return true
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
onPart(part) {
// this method can be overwritten by the user
this.handlePart(part)
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
handlePart(part) {
var self = this
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (part.filename === undefined) {
var value = '',
decoder = new StringDecoder(this.encoding)
2023-10-26 19:02:46 +08:00
part.on('data', function (buffer) {
2023-10-25 18:45:16 +08:00
self._fieldsSize += buffer.length
if (self._fieldsSize > self.maxFieldsSize) {
self._error(
new Error(
'maxFieldsSize exceeded, received ' +
self._fieldsSize +
' bytes of field data'
)
2020-09-16 20:07:28 +08:00
)
2023-10-25 18:45:16 +08:00
return
}
value += decoder.write(buffer)
})
2023-10-26 19:02:46 +08:00
part.on('end', function () {
2023-10-25 18:45:16 +08:00
self.emit('field', part.name, value)
})
return
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this._flushing++
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var file = new File({
path: this._uploadPath(part.filename),
name: part.filename,
type: part.mime,
hash: self.hash
})
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this.emit('fileBegin', part.name, file)
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
file.open()
this.openedFiles.push(file)
2023-10-26 19:02:46 +08:00
part.on('data', function (buffer) {
2023-10-25 18:45:16 +08:00
if (buffer.length == 0) {
2020-09-16 20:07:28 +08:00
return
}
2023-10-25 18:45:16 +08:00
self.pause()
2023-10-26 19:02:46 +08:00
file.write(buffer, function () {
2023-10-25 18:45:16 +08:00
self.resume()
})
2020-09-16 20:07:28 +08:00
})
2023-10-26 19:02:46 +08:00
part.on('end', function () {
file.end(function () {
2023-10-25 18:45:16 +08:00
self._flushing--
self.emit('file', part.name, file)
self._maybeEnd()
})
2020-09-16 20:07:28 +08:00
})
}
2023-10-26 19:02:46 +08:00
#parseContentType() {
2023-10-25 18:45:16 +08:00
if (this.bytesExpected === 0) {
this._parser = dummyParser(this)
2020-09-16 20:07:28 +08:00
return
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (!this.headers['content-type']) {
this._error(new Error('bad content-type header, no content-type'))
return
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.headers['content-type'].match(/octet-stream/i)) {
this._initOctetStream()
return
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.headers['content-type'].match(/urlencoded/i)) {
this._initUrlencoded()
return
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.headers['content-type'].match(/multipart/i)) {
var m = this.headers['content-type'].match(
/boundary=(?:"([^"]+)"|([^;]+))/i
)
2020-09-16 20:07:28 +08:00
if (m) {
2023-10-25 18:45:16 +08:00
this._initMultipart(m[1] || m[2])
} else {
this._error(new Error('bad content-type header, no multipart boundary'))
2020-09-16 20:07:28 +08:00
}
2023-10-25 18:45:16 +08:00
return
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.headers['content-type'].match(/json|appliation|plain|text/i)) {
this._initJSONencoded()
return
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this._error(
new Error(
'bad content-type header, unknown content-type: ' +
this.headers['content-type']
)
)
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_error(err) {
if (this.error || this.ended) {
return
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this.error = err
this.emit('error', err)
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (Array.isArray(this.openedFiles)) {
2023-10-26 19:02:46 +08:00
this.openedFiles.forEach(function (file) {
2023-10-25 18:45:16 +08:00
file._writeStream.destroy()
2023-10-26 19:02:46 +08:00
setTimeout(fs.unlink, 0, file.path, function (error) {})
2023-10-25 18:45:16 +08:00
})
}
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
#parseContentLength() {
2023-10-25 18:45:16 +08:00
this.bytesReceived = 0
if (this.headers['content-length']) {
2023-10-26 19:02:46 +08:00
this.bytesExpected = +this.headers['content-length']
2023-10-25 18:45:16 +08:00
} else if (this.headers['transfer-encoding'] === undefined) {
this.bytesExpected = 0
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.bytesExpected !== null) {
this.emit('progress', this.bytesReceived, this.bytesExpected)
}
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_newParser() {
return new MultipartParser()
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_initMultipart(boundary) {
this.type = 'multipart'
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var parser = new MultipartParser(),
self = this,
headerField,
headerValue,
part
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
parser.initWithBoundary(boundary)
2023-10-26 19:02:46 +08:00
parser.onPartBegin = function () {
2023-10-25 18:45:16 +08:00
part = new Stream()
part.readable = true
part.headers = {}
part.name = null
part.filename = null
part.mime = null
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
part.transferEncoding = 'binary'
part.transferBuffer = ''
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
headerField = ''
headerValue = ''
}
2023-10-26 19:02:46 +08:00
parser.onHeaderField = function (b, start, end) {
2023-10-25 18:45:16 +08:00
headerField += b.toString(self.encoding, start, end)
}
2023-10-26 19:02:46 +08:00
parser.onHeaderValue = function (b, start, end) {
2023-10-25 18:45:16 +08:00
headerValue += b.toString(self.encoding, start, end)
}
2023-10-26 19:02:46 +08:00
parser.onHeaderEnd = function () {
2023-10-25 18:45:16 +08:00
headerField = headerField.toLowerCase()
part.headers[headerField] = headerValue
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var m = headerValue.match(/\bname="([^"]+)"/i)
if (headerField == 'content-disposition') {
if (m) {
part.name = m[1]
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
part.filename = self._fileName(headerValue)
} else if (headerField == 'content-type') {
part.mime = headerValue
} else if (headerField == 'content-transfer-encoding') {
part.transferEncoding = headerValue.toLowerCase()
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
headerField = ''
headerValue = ''
}
2023-10-26 19:02:46 +08:00
parser.onHeadersEnd = function () {
2023-10-25 18:45:16 +08:00
switch (part.transferEncoding) {
case 'binary':
case '7bit':
case '8bit':
2023-10-26 19:02:46 +08:00
parser.onPartData = function (b, start, end) {
2023-10-25 18:45:16 +08:00
part.emit('data', b.slice(start, end))
}
2023-10-26 19:02:46 +08:00
parser.onPartEnd = function () {
2023-10-25 18:45:16 +08:00
part.emit('end')
}
break
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
case 'base64':
2023-10-26 19:02:46 +08:00
parser.onPartData = function (b, start, end) {
2023-10-25 18:45:16 +08:00
part.transferBuffer += b.slice(start, end).toString('ascii')
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
/*
four bytes (chars) in base64 converts to three bytes in binary
encoding. So we should always work with a number of bytes that
can be divided by 4, it will result in a number of buytes that
can be divided vy 3.
*/
var offset = parseInt(part.transferBuffer.length / 4, 10) * 4
part.emit(
'data',
Buffer.from(part.transferBuffer.substring(0, offset), 'base64')
)
part.transferBuffer = part.transferBuffer.substring(offset)
}
2023-10-26 19:02:46 +08:00
parser.onPartEnd = function () {
2023-10-25 18:45:16 +08:00
part.emit('data', Buffer.from(part.transferBuffer, 'base64'))
part.emit('end')
}
break
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
default:
return self._error(new Error('unknown transfer-encoding'))
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
self.onPart(part)
}
2023-10-26 19:02:46 +08:00
parser.onEnd = function () {
2023-10-25 18:45:16 +08:00
self.ended = true
self._maybeEnd()
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this._parser = parser
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_fileName(headerValue) {
var m = headerValue.match(/\bfilename="(.*?)"($|; )/i)
if (!m) return
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var filename = m[1].substr(m[1].lastIndexOf('\\') + 1)
filename = filename.replace(/%22/g, '"')
2023-10-26 19:02:46 +08:00
filename = filename.replace(/&#([\d]{4});/g, function (m, code) {
2023-10-25 18:45:16 +08:00
return String.fromCharCode(code)
2020-09-16 20:07:28 +08:00
})
2023-10-25 18:45:16 +08:00
return filename
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_initUrlencoded() {
this.type = 'urlencoded'
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var parser = new QuerystringParser(this.maxFields)
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
parser.onField = (key, val) => {
this.emit('field', key, val)
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
parser.onEnd = () => {
this.ended = true
this._maybeEnd()
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this._parser = parser
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_initOctetStream() {
this.type = 'octet-stream'
var filename = this.headers['x-file-name']
var mime = this.headers['content-type']
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var file = new File({
path: this._uploadPath(filename),
name: filename,
type: mime
})
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this.emit('fileBegin', filename, file)
file.open()
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this._flushing++
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var self = this
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
self._parser = new OctetParser()
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
//Keep track of writes that haven't finished so we don't emit the file before it's done being written
var outstandingWrites = 0
2023-10-26 19:02:46 +08:00
self._parser.on('data', function (buffer) {
2023-10-25 18:45:16 +08:00
self.pause()
outstandingWrites++
2023-10-26 19:02:46 +08:00
file.write(buffer, function () {
2023-10-25 18:45:16 +08:00
outstandingWrites--
self.resume()
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (self.ended) {
self._parser.emit('doneWritingFile')
}
})
})
2023-10-26 19:02:46 +08:00
self._parser.on('end', function () {
2023-10-25 18:45:16 +08:00
self._flushing--
self.ended = true
2023-10-26 19:02:46 +08:00
var done = function () {
file.end(function () {
2023-10-25 18:45:16 +08:00
self.emit('file', 'file', file)
self._maybeEnd()
})
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (outstandingWrites === 0) {
done()
} else {
self._parser.once('doneWritingFile', done)
}
})
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_initJSONencoded() {
this.type = 'json'
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
var parser = new JSONParser(),
self = this
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.bytesExpected) {
parser.initWithLength(this.bytesExpected)
}
2023-10-26 19:02:46 +08:00
parser.onField = function (key, val) {
2023-10-25 18:45:16 +08:00
self.emit('field', key, val)
}
2023-10-26 19:02:46 +08:00
parser.onEnd = function () {
2023-10-25 18:45:16 +08:00
self.ended = true
self._maybeEnd()
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this._parser = parser
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_uploadPath(filename) {
var name = 'upload_'
var buf = crypto.randomBytes(16)
for (var i = 0; i < buf.length; ++i) {
name += ('0' + buf[i].toString(16)).slice(-2)
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.keepExtensions) {
var ext = path.extname(filename)
ext = ext.replace(/(\.[a-z0-9]+).*/i, '$1')
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
name += ext
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
return path.join(this.uploadDir, name)
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
_maybeEnd() {
if (!this.ended || this._flushing || this.error) {
return
}
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
this.emit('end')
2020-09-16 20:07:28 +08:00
}
}