request/lib/index.js

347 lines
8.2 KiB
JavaScript
Raw Normal View History

2023-10-25 18:45:16 +08:00
import crypto from 'node:crypto'
import fs from 'node:fs'
2023-10-27 19:16:32 +08:00
import { join } from 'node:path'
2023-10-25 18:45:16 +08:00
import { EventEmitter } from 'node:events'
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'
2023-10-27 19:16:32 +08:00
import { UrlencodedParser } from './urlencoded_parser.js'
import { OctetParser, EmptyParser } from './octet_parser.js'
2020-09-16 20:07:28 +08:00
import { JSONParser } from './json_parser.js'
2023-10-27 19:16:32 +08:00
function randomPath(uploadDir) {
var name = 'upload_' + crypto.randomBytes(16).toString('hex')
return join(uploadDir, name)
2020-09-16 20:07:28 +08:00
}
2023-10-30 16:41:37 +08:00
function parseFilename(headerValue) {
let matches = headerValue.match(/\bfilename="(.*?)"($|; )/i)
if (!matches) {
return
}
let filename = matches[1].slice(matches[1].lastIndexOf('\\') + 1)
filename = filename.replace(/%22/g, '"')
filename = filename.replace(/&#([\d]{4});/g, function (m, code) {
return String.fromCharCode(code)
})
return filename
}
2023-10-27 19:16:32 +08:00
/* ------------------------------------- */
2023-10-26 19:02:46 +08:00
export default class IncomingForm extends EventEmitter {
#req = null
2023-10-27 19:16:32 +08:00
#error = false
#ended = false
2023-10-26 19:02:46 +08:00
ended = false
headers = null
2023-10-25 18:45:16 +08:00
2023-10-26 19:02:46 +08:00
bytesReceived = null
bytesExpected = null
2023-10-27 19:16:32 +08:00
#parser = null
2023-10-30 16:41:37 +08:00
#pending = 0
2023-10-27 19:16:32 +08:00
#openedFiles = []
2023-10-26 19:02:46 +08:00
constructor(req, opts = {}) {
super()
this.#req = req
2023-10-25 18:45:16 +08:00
this.uploadDir = opts.uploadDir
this.encoding = opts.encoding || 'utf-8'
2023-10-30 16:59:54 +08:00
this.headers = req.headers
this.#parseContentLength()
this.#parseContentType()
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 => {
2023-10-27 19:16:32 +08:00
this.#handleError(err)
this.#clearUploads()
2023-10-25 18:45:16 +08:00
})
2023-10-26 19:02:46 +08:00
.on('aborted', () => {
this.emit('aborted')
2023-10-27 19:16:32 +08:00
this.#clearUploads()
2020-09-16 20:07:28 +08:00
})
2023-10-30 16:59:54 +08:00
.on('data', buffer => this.#write(buffer))
2023-10-26 19:02:46 +08:00
.on('end', () => {
2023-10-27 19:16:32 +08:00
if (this.#error) {
2023-10-25 18:45:16 +08:00
return
}
2023-10-27 19:16:32 +08:00
let err = this.#parser.end()
2023-10-25 18:45:16 +08:00
if (err) {
2023-10-27 19:16:32 +08:00
this.#handleError(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-30 16:59:54 +08:00
#write(buffer) {
2023-10-27 19:16:32 +08:00
if (this.#error) {
2023-10-25 18:45:16 +08:00
return
}
2023-10-27 19:16:32 +08:00
if (!this.#parser) {
return this.#handleError(new Error('uninitialized parser'))
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-27 19:16:32 +08:00
this.#parser.write(buffer)
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
#handlePart(part) {
2023-10-25 18:45:16 +08:00
if (part.filename === undefined) {
2023-10-30 16:59:54 +08:00
let value = Buffer.from('')
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
part
2023-10-30 16:59:54 +08:00
.on('data', buff => {
value = Buffer.concat([value, buff])
2023-10-27 19:16:32 +08:00
})
.on('end', () => {
2023-10-30 16:59:54 +08:00
this.emit('field', part.name, value.toString(this.encoding))
2023-10-27 19:16:32 +08:00
})
} else {
let file = new File({
path: randomPath(this.uploadDir),
name: part.filename,
type: part.mime
2023-10-25 18:45:16 +08:00
})
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
file.open()
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
this.#openedFiles.push(file)
2023-10-30 16:41:37 +08:00
// 表单解析完的时候文件写入不一定完成了, 所以需要加入pending计数
this.#pending++
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
part
.on('data', buffer => {
if (buffer.length == 0) {
return
}
file.write(buffer)
})
.on('end', () => {
2023-10-30 16:41:37 +08:00
if (part.ended) {
return
}
part.ended = true
2023-10-27 19:16:32 +08:00
file.end(() => {
this.emit('file', part.name, file)
2023-10-30 16:41:37 +08:00
this.#pending--
2023-10-27 19:16:32 +08:00
})
})
}
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
#parseContentType() {
let contentType = this.headers['content-type'] || ''
2023-10-27 19:16:32 +08:00
let lower = contentType.toLowerCase()
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
if (this.bytesExpected === 0) {
return (this.#parser = new EmptyParser())
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
if (lower.includes('octet-stream')) {
return this.#createStreamParser()
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
if (lower.includes('urlencoded')) {
return this.#createUrlencodedParser()
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
if (lower.includes('multipart')) {
let matches = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/)
if (matches) {
this.#createMultipartParser(matches[1] || matches[2])
2023-10-25 18:45:16 +08:00
} else {
2023-10-27 19:16:32 +08:00
this.#handleError(new TypeError('unknow 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-27 19:16:32 +08:00
if (lower.match(/json|appliation|plain|text/)) {
return this.#createJsonParser()
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
this.#handleError(new TypeError('unknown content-type: ' + contentType))
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-27 19:16:32 +08:00
#createMultipartParser(boundary) {
let headerField, headerValue, part
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
this.#parser = new MultipartParser(boundary)
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
this.#parser.$partBegin = function () {
2023-10-30 16:59:54 +08:00
part = new EventEmitter()
2023-10-30 16:41:37 +08:00
part.readable = true
part.headers = {}
part.name = null
part.filename = null
part.mime = null
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
part.transferEncoding = 'binary'
part.transferBuffer = ''
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
headerField = ''
headerValue = ''
}
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
this.#parser.$headerField = b => {
headerField += b.toString(this.encoding)
}
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
this.#parser.$headerValue = b => {
headerValue += b.toString(this.encoding)
}
this.#parser.$headerEnd = () => {
headerField = headerField.toLowerCase()
part.headers[headerField] = headerValue
let matches = headerValue.match(/\bname="([^"]+)"/i)
if (headerField == 'content-disposition') {
if (matches) {
part.name = matches[1]
2023-10-27 19:16:32 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
part.filename = parseFilename(headerValue)
} else if (headerField == 'content-type') {
part.mime = headerValue
} else if (headerField == 'content-transfer-encoding') {
part.transferEncoding = headerValue.toLowerCase()
}
headerField = ''
headerValue = ''
}
this.#parser.$headersEnd = () => {
switch (part.transferEncoding) {
case 'binary':
case '7bit':
case '8bit':
this.#parser.$partData = function (b) {
part.emit('data', b)
}
this.#parser.$partEnd = function () {
part.emit('end')
}
break
case 'base64':
this.#parser.$partData = function (b) {
part.transferBuffer += b.toString('ascii')
// 确保offset的值能被4整除
let offset = ~~(part.transferBuffer.length / 4) * 4
part.emit(
'data',
Buffer.from(part.transferBuffer.slice(0, offset), 'base64')
)
part.transferBuffer = part.transferBuffer.slice(offset)
}
this.#parser.$partEnd = function () {
part.emit('data', Buffer.from(part.transferBuffer, 'base64'))
part.emit('end')
}
break
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
default:
return this.#handleError(new Error('unknown transfer-encoding'))
}
2023-10-26 19:02:46 +08:00
2023-10-30 16:41:37 +08:00
this.#handlePart(part)
}
this.#parser.$end = () => {
if (this.#pending > 0) {
setTimeout(_ => this.#parser.$end())
} else {
this.#handleEnd()
}
}
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
#createUrlencodedParser() {
this.#parser = new UrlencodedParser()
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
this.#parser
.on('field', fields => this.emit('field', false, fields))
.on('end', () => this.#handleEnd())
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
#createStreamParser() {
let filename = this.headers['x-file-name']
let mime = this.headers['x-file-type']
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
this.#parser = new OctetParser(filename, mime, randomPath(this.uploadDir))
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
if (this.bytesExpected) {
this.#parser.initLength(this.bytesExpected)
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
this.#parser
.on('file', file => {
this.emit('file', false, file)
2023-10-25 18:45:16 +08:00
})
2023-10-27 19:16:32 +08:00
.on('end', () => this.#handleEnd())
.on('error', err => this.#handleError(err))
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
#createJsonParser() {
this.#parser = new JSONParser()
2023-10-26 19:02:46 +08:00
2023-10-25 18:45:16 +08:00
if (this.bytesExpected) {
2023-10-27 19:16:32 +08:00
this.#parser.initLength(this.bytesExpected)
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
this.#parser
.on('field', (key, val) => {
this.emit('field', key, val)
})
.on('end', () => this.#handleEnd())
.on('error', err => this.#handleError(err))
2023-10-25 18:45:16 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
#clearUploads() {
while (this.#openedFiles.length) {
let file = this.#openedFiles.pop()
file._writeStream.destroy()
setTimeout(_ => {
try {
fs.unlink(file.path)
} catch (e) {}
})
2023-10-25 18:45:16 +08:00
}
2023-10-27 19:16:32 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
#handleError(err) {
if (this.#error || this.#ended) {
return
2023-10-25 18:45:16 +08:00
}
2023-10-27 19:16:32 +08:00
this.error = true
this.emit('error', err)
2020-09-16 20:07:28 +08:00
}
2023-10-26 19:02:46 +08:00
2023-10-27 19:16:32 +08:00
#handleEnd() {
if (this.#ended || this.#error) {
2023-10-25 18:45:16 +08:00
return
}
2023-10-27 19:16:32 +08:00
this.#ended = true
2023-10-25 18:45:16 +08:00
this.emit('end')
2020-09-16 20:07:28 +08:00
}
}