diff --git a/index.js b/index.js index 8cd60bf..327fd09 100644 --- a/index.js +++ b/index.js @@ -8,15 +8,15 @@ import 'es.shim' import Parser from './lib/index.js' import { parseCookie } from './lib/cookie.js' import fs from 'iofs' -import URL from 'url' -import QS from 'querystring' -import PATH from 'path' +import { fileURLToPath, parse } from 'node:url' +import QS from 'node:querystring' +import { dirname, resolve } from 'node:path' const DEFAULT_FORM_TYPE = 'application/x-www-form-urlencoded' -const __dirname = PATH.dirname(URL.fileURLToPath(import.meta.url)) +const __dirname = dirname(fileURLToPath(import.meta.url)) -const tmpdir = PATH.resolve(__dirname, '.tmp/') +const tmpdir = resolve(__dirname, '.tmp/') const encode = encodeURIComponent const decode = decodeURIComponent @@ -36,33 +36,46 @@ function hideProperty(host, name, value) { } export default class Request { + #req = null + #res = null + + #query = null + #body = null + #cookies = Object.create(null) + + method = 'GET' + path = [] + + url = '' + host = '127.0.0.1' + constructor(req, res) { this.method = req.method.toUpperCase() - this.params = {} - hideProperty(this, 'origin', { req, res }) - hideProperty(this, '__GET__', null) - hideProperty(this, '__POST__', null) - hideProperty(this, '__COOKIE__', parseCookie(this.header('cookie') || '')) - this.__fixUrl() + this.#req = req + this.#res = res + + this.host = req.headers['host'] + this.#cookies = parseCookie(this.headers['cookie'] || '') + + this.#init() } // 修正请求的url - __fixUrl() { - let _url = URL.parse(this.origin.req.url) + #init() { + let _url = parse(this.#req.url) .pathname.slice(1) .replace(/[\/]+$/, '') let app = '' // 将作为主控制器(即apps目录下的应用) let pathArr = [] - let tmpArr = [] // URL上不允许有非法字符 - if (/[^\w-/.@~!$&:+'=]/.test(decode(_url))) { - this.origin.res.rendered = true - this.origin.res.writeHead(400, { + if (/[^\w-/.,@~!$&:+'"=]/.test(decode(_url))) { + this.#res.rendered = true + this.#res.writeHead(400, { 'X-debug': `url [/${encode(_url)}] contains invalid characters` }) - return this.origin.res.end(`Invalid characters: /${_url}`) + return this.#res.end(`Invalid characters: /${_url}`) } // 修正url中可能出现的"多斜杠" @@ -85,200 +98,153 @@ export default class Request { pathArr.shift() - // 将path第3段之后的部分, 每2个一组转为key-val数据对象, 存入params中 - tmpArr = pathArr.slice(1).concat() - while (tmpArr.length) { - this.params[tmpArr.shift()] = tmpArr.shift() || null - } - tmpArr = undefined - - for (let i in this.params) { - if (!this.params[i]) { - continue - } - // 修正数字类型,把符合条件的数字字符串转为数字(也许会误转, 但总的来说是利大弊) - this.params[i] = Number.parse(this.params[i]) - } - this.app = app this.url = _url this.path = pathArr } /** - * [get 同php的$_GET] - */ - get(key = '', xss = true) { - xss = !!xss - if (!this.__GET__) { - let para = URL.parse(this.origin.req.url).query - para = Object.assign({}, QS.parse(para)) - if (xss) { - for (let i in para) { - if (!para[i]) { - continue - } - - if (Array.isArray(para[i])) { - para[i] = para[i].map(it => { - it = Number.parse(it.trim().xss()) - return it - }) - } else { - para[i] = Number.parse(para[i].trim().xss()) - } - } - } - this.__GET__ = para - } - - return key - ? this.__GET__.hasOwnProperty(key) - ? this.__GET__[key] - : null - : this.__GET__ - } - - /** - * [post 接收post, 需要 await ] + * [解析请求体, 需要 await ] * @param {Str} key [字段] */ - post(key = '', xss = true) { - let para = {} + #parseBody() { let out = Promise.defer() let form, contentType - xss = !!xss - - //如果之前已经缓存过,则直接从缓存读取 - if (this.__POST__) { - if (key) { - return this.__POST__.hasOwnProperty(key) ? this.__POST__[key] : null - } else { - return this.__POST__ - } - } + this.#body = {} contentType = this.header('content-type') || DEFAULT_FORM_TYPE - form = new Parser() - form.uploadDir = tmpdir - form.parse(this.origin.req) + form = new Parser(this.#req, { uploadDir: tmpdir }) - form.on('field', (name, value) => { - if (name === false) { - para = value - return - } - if (~contentType.indexOf('urlencoded')) { - if ( - name.slice(0, 2) === '{"' && - (name.slice(-2) === '"}' || value.slice(-2) === '"}') - ) { - name = name.replace(/\s/g, '+') - - if (value.slice(0, 1) === '=') value = '=' + value - - return Object.assign(para, JSON.parse(name + value)) + form + .on('field', (name, value) => { + if (name === false) { + this.#body = value + return } - } + if (~contentType.indexOf('urlencoded')) { + if ( + name.slice(0, 2) === '{"' && + (name.slice(-2) === '"}' || value.slice(-2) === '"}') + ) { + name = name.replace(/\s/g, '+') - if (typeof value === 'string') { - value = xss ? value.xss() : value - } + if (value.slice(0, 1) === '=') value = '=' + value - if (name.slice(-2) === '[]') { - name = name.slice(0, -2) - if (typeof value === 'string') { - value = [value] + return Object.assign(this.#body, JSON.parse(name + value)) + } } - } else if (name.slice(-1) === ']') { - let key = name.slice(name.lastIndexOf('[') + 1, -1) - name = name.slice(0, name.lastIndexOf('[')) - //多解析一层对象(也仅支持到这一层) - if (name.slice(-1) === ']') { - let pkey = name.slice(name.lastIndexOf('[') + 1, -1) + if (name.slice(-2) === '[]') { + name = name.slice(0, -2) + if (typeof value === 'string') { + value = [value] + } + } else if (name.slice(-1) === ']') { + let key = name.slice(name.lastIndexOf('[') + 1, -1) name = name.slice(0, name.lastIndexOf('[')) - if (!para.hasOwnProperty(name)) { - para[name] = {} - } + //多解析一层对象(也仅支持到这一层) + if (name.slice(-1) === ']') { + let pkey = name.slice(name.lastIndexOf('[') + 1, -1) + name = name.slice(0, name.lastIndexOf('[')) - if (!para[name].hasOwnProperty(pkey)) { - para[name][pkey] = {} - } - - para[name][pkey][key] = value - } else { - if (!para.hasOwnProperty(name)) { - para[name] = {} - } - - para[name][key] = value - } - return - } - - para[name] = value - }) - - form.on('file', (name, file) => { - if (name.slice(-2) === '[]') { - name = name.slice(0, -2) - } - if (!para.hasOwnProperty(name)) { - para[name] = file - } else { - if (!Array.isArray(para[name])) { - para[name] = [para[name]] - } - para[name].push(file) - } - }) - - form.on('error', out.reject) - - form.on('end', err => { - if (~contentType.indexOf('urlencoded')) { - for (let i in para) { - if (typeof para[i] === 'string') { - if (!para[i]) { - continue + if (!this.#body.hasOwnProperty(name)) { + this.#body[name] = {} + } + + if (!this.#body[name].hasOwnProperty(pkey)) { + this.#body[name][pkey] = {} + } + + this.#body[name][pkey][key] = value + } else { + if (!this.#body.hasOwnProperty(name)) { + this.#body[name] = {} + } + + this.#body[name][key] = value + } + return + } + + this.#body[name] = value + }) + .on('file', (name, file) => { + if (name.slice(-2) === '[]') { + name = name.slice(0, -2) + } + if (!this.#body.hasOwnProperty(name)) { + this.#body[name] = file + } else { + if (!Array.isArray(this.#body[name])) { + this.#body[name] = [this.#body[name]] + } + this.#body[name].push(file) + } + }) + .on('error', out.reject) + .on('end', err => { + if (~contentType.indexOf('urlencoded')) { + for (let i in this.#body) { + if (typeof this.#body[i] === 'string') { + if (!this.#body[i]) { + continue + } + this.#body[i] = Number.parse(this.#body[i]) } - para[i] = Number.parse(para[i]) } } - } - this._postParam = para - if (key) { - return out.resolve(para.hasOwnProperty(key) ? para[key] : null) - } else { - return out.resolve(para) - } - }) + + out.resolve(this.#body) + }) return out.promise } //获取响应头 header(key = '') { key = key ? (key + '').toLowerCase() : null - return !!key ? this.origin.req.headers[key] : this.origin.req.headers + return !!key ? this.#req.headers[key] : this.#req.headers } // 读取cookie cookie(key) { if (key) { - return this.__COOKIE__[key] + return this.#cookies[key] } - return this.__COOKIE__ + return this.#cookies + } + + get query() { + if (!this.#query) { + let para = parse(this.#req.url).query + this.#query = {} + para = Object.assign(this.#query, QS.parse(para)) + } + return this.#query + } + + get body() { + if (this.#body) { + return this.#body + } + return this.#parseBody() + } + + get cookies() { + return this.#cookies + } + + get headers() { + return this.#req.headers } //获取客户端IP - ip() { + get ip() { return ( - this.header('x-real-ip') || - this.header('x-forwarded-for') || - this.origin.req.connection.remoteAddress.replace('::ffff:', '') + this.headers['x-real-ip'] || + this.headers['x-forwarded-for'] || + this.#req.connection.remoteAddress.replace('::ffff:', '') ) } } diff --git a/lib/file.js b/lib/file.js index 89393cb..7c44b96 100644 --- a/lib/file.js +++ b/lib/file.js @@ -1,9 +1,7 @@ import { WriteStream } from 'node:fs' import { EventEmitter } from 'node:events' - export default class File extends EventEmitter { - #stream = null size = 0 @@ -12,7 +10,7 @@ export default class File extends EventEmitter { type = null lastModifiedDate = null - constructor(props = {}){ + constructor(props = {}) { super() for (var key in props) { @@ -23,7 +21,7 @@ export default class File extends EventEmitter { open() { this.#stream = new WriteStream(this.path) } - + toJSON() { return { size: this.size, @@ -36,26 +34,20 @@ export default class File extends EventEmitter { mime: this.mime } } - - write(buffer, cb) { - - this.#stream.write(buffer, _ =>{ + write(buffer, cb) { + this.#stream.write(buffer, _ => { this.lastModifiedDate = new Date() this.size += buffer.length this.emit('progress', this.size) cb() }) } - + end(cb) { - - this.#stream.end(() => { this.emit('end') cb() }) } } - - diff --git a/lib/index.js b/lib/index.js index 8df205c..ca638fe 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,21 +1,19 @@ import crypto from 'node:crypto' import fs from 'node:fs' -import util from 'node:util' import path from 'node:path' -import File from './file.js' import { EventEmitter } from 'node:events' import { Stream } from 'node:stream' import { StringDecoder } from 'node:string_decoder' +import File from './file.js' import { MultipartParser } from './multipart_parser.js' import { QuerystringParser } from './querystring_parser.js' import { OctetParser } from './octet_parser.js' import { JSONParser } from './json_parser.js' - function dummyParser(self) { return { - end: function() { + end: function () { self.ended = true self._maybeEnd() return null @@ -23,132 +21,66 @@ function dummyParser(self) { } } -export default class IncomingForm{ +export default class IncomingForm extends EventEmitter { + #req = null - constructor(opts = {}) { + error = null + ended = false + headers = null + type = null + + bytesReceived = null + bytesExpected = null + + _parser = null + _flushing = 0 + _fieldsSize = 0 + openedFiles = [] + + constructor(req, opts = {}) { + super() + + this.#req = req - this.error = null - this.ended = false - 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.headers = null - this.type = null - this.hash = opts.hash || false this.multiples = opts.multiples || false - - this.bytesReceived = null - this.bytesExpected = null - - this._parser = null - this._flushing = 0 - this._fieldsSize = 0 - this.openedFiles = [] - } - - - - - parse(req, cb) { - this.pause = function() { - try { - req.pause() - } catch (err) { - // the stream was destroyed - if (!this.ended) { - // before it was completed, crash & burn - this._error(err) - } - return false - } - return true - } - - this.resume = function() { - try { - req.resume() - } catch (err) { - // the stream was destroyed - if (!this.ended) { - // before it was completed, crash & burn - this._error(err) - } - return false - } - - return true - } - - // Setup callback first, so we don't miss anything from data events emitted - // immediately. - if (cb) { - var fields = {}, - files = {} - this.on('field', function(name, value) { - fields[name] = value - }) - .on('file', function(name, file) { - if (this.multiples) { - if (files[name]) { - if (!Array.isArray(files[name])) { - files[name] = [files[name]] - } - files[name].push(file) - } else { - files[name] = file - } - } else { - files[name] = file - } - }) - .on('error', function(err) { - cb(err, fields, files) - }) - .on('end', function() { - cb(null, fields, files) - }) - } - // Parse headers and setup the parser, ready to start listening for data. this.writeHeaders(req.headers) - - // Start listening for data. - var self = this + req - .on('error', function(err) { - self._error(err) + .on('error', err => { + this._error(err) }) - .on('aborted', function() { - self.emit('aborted') - self._error(new Error('Request aborted')) + .on('aborted', () => { + this.emit('aborted') + this._error(new Error('Request aborted')) }) - .on('data', function(buffer) { - self.write(buffer) + .on('data', buffer => { + this.write(buffer) }) - .on('end', function() { - if (self.error) { + .on('end', () => { + if (this.error) { return } - - var err = self._parser.end() + + var err = this._parser.end() if (err) { - self._error(err) + this._error(err) } }) - - return this } - + writeHeaders(headers) { this.headers = headers - this._parseContentLength() - this._parseContentType() + this.#parseContentLength() + this.#parseContentType() } - + write(buffer) { if (this.error) { return @@ -157,10 +89,10 @@ export default class IncomingForm{ this._error(new Error('uninitialized parser')) return } - + this.bytesReceived += buffer.length this.emit('progress', this.bytesReceived, this.bytesExpected) - + var bytesParsed = this._parser.write(buffer) if (bytesParsed !== buffer.length) { this._error( @@ -173,33 +105,52 @@ export default class IncomingForm{ ) ) } - + return bytesParsed } - + pause() { - // this does nothing, unless overwritten in IncomingForm.parse - return false + 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 } - + resume() { - // this does nothing, unless overwritten in IncomingForm.parse - return false + 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 } - + onPart(part) { // this method can be overwritten by the user this.handlePart(part) } - + handlePart(part) { var self = this - + if (part.filename === undefined) { var value = '', decoder = new StringDecoder(this.encoding) - - part.on('data', function(buffer) { + + part.on('data', function (buffer) { self._fieldsSize += buffer.length if (self._fieldsSize > self.maxFieldsSize) { self._error( @@ -213,68 +164,67 @@ export default class IncomingForm{ } value += decoder.write(buffer) }) - - part.on('end', function() { + + part.on('end', function () { self.emit('field', part.name, value) }) return } - + this._flushing++ - + var file = new File({ path: this._uploadPath(part.filename), name: part.filename, type: part.mime, hash: self.hash }) - + this.emit('fileBegin', part.name, file) - + file.open() this.openedFiles.push(file) - - part.on('data', function(buffer) { + + part.on('data', function (buffer) { if (buffer.length == 0) { return } self.pause() - file.write(buffer, function() { + file.write(buffer, function () { self.resume() }) }) - - part.on('end', function() { - file.end(function() { + + part.on('end', function () { + file.end(function () { self._flushing-- self.emit('file', part.name, file) self._maybeEnd() }) }) } - - - _parseContentType() { + + #parseContentType() { if (this.bytesExpected === 0) { this._parser = dummyParser(this) return } - + if (!this.headers['content-type']) { this._error(new Error('bad content-type header, no content-type')) return } - + if (this.headers['content-type'].match(/octet-stream/i)) { this._initOctetStream() return } - + if (this.headers['content-type'].match(/urlencoded/i)) { this._initUrlencoded() return } - + if (this.headers['content-type'].match(/multipart/i)) { var m = this.headers['content-type'].match( /boundary=(?:"([^"]+)"|([^;]+))/i @@ -286,12 +236,12 @@ export default class IncomingForm{ } return } - + if (this.headers['content-type'].match(/json|appliation|plain|text/i)) { this._initJSONencoded() return } - + this._error( new Error( 'bad content-type header, unknown content-type: ' + @@ -299,113 +249,113 @@ export default class IncomingForm{ ) ) } - + _error(err) { if (this.error || this.ended) { return } - + this.error = err this.emit('error', err) - + if (Array.isArray(this.openedFiles)) { - this.openedFiles.forEach(function(file) { + this.openedFiles.forEach(function (file) { file._writeStream.destroy() - setTimeout(fs.unlink, 0, file.path, function(error) {}) + setTimeout(fs.unlink, 0, file.path, function (error) {}) }) } } - - _parseContentLength() { + + #parseContentLength() { this.bytesReceived = 0 if (this.headers['content-length']) { - this.bytesExpected = parseInt(this.headers['content-length'], 10) + this.bytesExpected = +this.headers['content-length'] } else if (this.headers['transfer-encoding'] === undefined) { this.bytesExpected = 0 } - + if (this.bytesExpected !== null) { this.emit('progress', this.bytesReceived, this.bytesExpected) } } - + _newParser() { return new MultipartParser() } - + _initMultipart(boundary) { this.type = 'multipart' - + var parser = new MultipartParser(), self = this, headerField, headerValue, part - + parser.initWithBoundary(boundary) - - parser.onPartBegin = function() { + + parser.onPartBegin = function () { part = new Stream() part.readable = true part.headers = {} part.name = null part.filename = null part.mime = null - + part.transferEncoding = 'binary' part.transferBuffer = '' - + headerField = '' headerValue = '' } - - parser.onHeaderField = function(b, start, end) { + + parser.onHeaderField = function (b, start, end) { headerField += b.toString(self.encoding, start, end) } - - parser.onHeaderValue = function(b, start, end) { + + parser.onHeaderValue = function (b, start, end) { headerValue += b.toString(self.encoding, start, end) } - - parser.onHeaderEnd = function() { + + parser.onHeaderEnd = function () { headerField = headerField.toLowerCase() part.headers[headerField] = headerValue - + var m = headerValue.match(/\bname="([^"]+)"/i) if (headerField == 'content-disposition') { if (m) { part.name = m[1] } - + part.filename = self._fileName(headerValue) } else if (headerField == 'content-type') { part.mime = headerValue } else if (headerField == 'content-transfer-encoding') { part.transferEncoding = headerValue.toLowerCase() } - + headerField = '' headerValue = '' } - - parser.onHeadersEnd = function() { + + parser.onHeadersEnd = function () { switch (part.transferEncoding) { case 'binary': case '7bit': case '8bit': - parser.onPartData = function(b, start, end) { + parser.onPartData = function (b, start, end) { part.emit('data', b.slice(start, end)) } - - parser.onPartEnd = function() { + + parser.onPartEnd = function () { part.emit('end') } break - + case 'base64': - parser.onPartData = function(b, start, end) { + parser.onPartData = function (b, start, end) { part.transferBuffer += b.slice(start, end).toString('ascii') - + /* four bytes (chars) in base64 converts to three bytes in binary encoding. So we should always work with a number of bytes that @@ -419,105 +369,105 @@ export default class IncomingForm{ ) part.transferBuffer = part.transferBuffer.substring(offset) } - - parser.onPartEnd = function() { + + parser.onPartEnd = function () { part.emit('data', Buffer.from(part.transferBuffer, 'base64')) part.emit('end') } break - + default: return self._error(new Error('unknown transfer-encoding')) } - + self.onPart(part) } - - parser.onEnd = function() { + + parser.onEnd = function () { self.ended = true self._maybeEnd() } - + this._parser = parser } - + _fileName(headerValue) { var m = headerValue.match(/\bfilename="(.*?)"($|; )/i) if (!m) return - + var filename = m[1].substr(m[1].lastIndexOf('\\') + 1) filename = filename.replace(/%22/g, '"') - filename = filename.replace(/&#([\d]{4});/g, function(m, code) { + filename = filename.replace(/&#([\d]{4});/g, function (m, code) { return String.fromCharCode(code) }) return filename } - + _initUrlencoded() { this.type = 'urlencoded' - + var parser = new QuerystringParser(this.maxFields) - + parser.onField = (key, val) => { this.emit('field', key, val) } - + parser.onEnd = () => { this.ended = true this._maybeEnd() } - + this._parser = parser } - + _initOctetStream() { this.type = 'octet-stream' var filename = this.headers['x-file-name'] var mime = this.headers['content-type'] - + var file = new File({ path: this._uploadPath(filename), name: filename, type: mime }) - + this.emit('fileBegin', filename, file) file.open() - + this._flushing++ - + var self = this - + self._parser = new OctetParser() - + //Keep track of writes that haven't finished so we don't emit the file before it's done being written var outstandingWrites = 0 - - self._parser.on('data', function(buffer) { + + self._parser.on('data', function (buffer) { self.pause() outstandingWrites++ - - file.write(buffer, function() { + + file.write(buffer, function () { outstandingWrites-- self.resume() - + if (self.ended) { self._parser.emit('doneWritingFile') } }) }) - - self._parser.on('end', function() { + + self._parser.on('end', function () { self._flushing-- self.ended = true - - var done = function() { - file.end(function() { + + var done = function () { + file.end(function () { self.emit('file', 'file', file) self._maybeEnd() }) } - + if (outstandingWrites === 0) { done() } else { @@ -525,53 +475,51 @@ export default class IncomingForm{ } }) } - + _initJSONencoded() { this.type = 'json' - + var parser = new JSONParser(), self = this - + if (this.bytesExpected) { parser.initWithLength(this.bytesExpected) } - - parser.onField = function(key, val) { + + parser.onField = function (key, val) { self.emit('field', key, val) } - - parser.onEnd = function() { + + parser.onEnd = function () { self.ended = true self._maybeEnd() } - + this._parser = parser } - + _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) } - + if (this.keepExtensions) { var ext = path.extname(filename) ext = ext.replace(/(\.[a-z0-9]+).*/i, '$1') - + name += ext } - + return path.join(this.uploadDir, name) } - + _maybeEnd() { if (!this.ended || this._flushing || this.error) { return } - + this.emit('end') } - } - diff --git a/lib/json_parser.js b/lib/json_parser.js index 633dafe..62c1abf 100644 --- a/lib/json_parser.js +++ b/lib/json_parser.js @@ -1,12 +1,11 @@ export class JSONParser { - data = Buffer.from('') bytesWritten = 0 initWithLength(length) { this.data = Buffer.alloc(length) } - + write(buffer) { if (this.data.length >= this.bytesWritten + buffer.length) { buffer.copy(this.data, this.bytesWritten) @@ -16,7 +15,7 @@ export class JSONParser { this.bytesWritten += buffer.length return buffer.length } - + end() { var data = this.data.toString('utf8') var fields @@ -25,12 +24,10 @@ export class JSONParser { } catch (e) { fields = Function(`try{return ${data}}catch(e){}`)() || data } - + this.onField(false, fields) this.data = null - + this.onEnd() } - } - diff --git a/lib/multipart_parser.js b/lib/multipart_parser.js index 4c18dbb..c176008 100644 --- a/lib/multipart_parser.js +++ b/lib/multipart_parser.js @@ -26,7 +26,7 @@ var s = 0, COLON = 58, A = 97, Z = 122, - lower = function(c) { + lower = function (c) { return c | 0x20 } @@ -39,28 +39,26 @@ export class MultipartParser { index = null flags = 0 - - static stateToString(stateNumber) { + static stateToString(stateNumber) { for (var state in S) { var number = S[state] if (number === stateNumber) return state } } - initWithBoundary(str) { 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 = S.START - + this.boundaryChars = {} for (var i = 0; i < this.boundary.length; i++) { this.boundaryChars[this.boundary[i]] = true } } - + write(buffer) { var self = this, i = 0, @@ -77,29 +75,29 @@ export class MultipartParser { bufferLength = buffer.length, c, cl, - mark = function(name) { + mark = function (name) { self[name + 'Mark'] = i }, - clear = function(name) { + clear = function (name) { delete self[name + 'Mark'] }, - callback = function(name, buffer, start, end) { + callback = function (name, buffer, start, end) { if (start !== undefined && start === end) { return } - + var callbackSymbol = 'on' + name.substr(0, 1).toUpperCase() + name.substr(1) if (callbackSymbol in self) { self[callbackSymbol](buffer, start, end) } }, - dataCallback = function(name, clear) { + dataCallback = function (name, clear) { var markSymbol = name + 'Mark' if (!(markSymbol in self)) { return } - + if (!clear) { callback(name, buffer, self[markSymbol], buffer.length) self[markSymbol] = 0 @@ -108,7 +106,7 @@ export class MultipartParser { delete self[markSymbol] } } - + for (i = 0; i < len; i++) { c = buffer[i] switch (state) { @@ -140,7 +138,7 @@ export class MultipartParser { } break } - + if (c != boundary[index + 2]) { index = -2 } @@ -158,12 +156,12 @@ export class MultipartParser { state = S.HEADERS_ALMOST_DONE break } - + index++ if (c == HYPHEN) { break } - + if (c == COLON) { if (index == 1) { // empty header field @@ -173,7 +171,7 @@ export class MultipartParser { state = S.HEADER_VALUE_START break } - + cl = lower(c) if (cl < A || cl > Z) { return i @@ -183,7 +181,7 @@ export class MultipartParser { if (c == SPACE) { break } - + mark('headerValue') state = S.HEADER_VALUE case S.HEADER_VALUE: @@ -203,7 +201,7 @@ export class MultipartParser { if (c != LF) { return i } - + callback('headersEnd') state = S.PART_DATA_START break @@ -212,7 +210,7 @@ export class MultipartParser { mark('partData') case S.PART_DATA: prevIndex = index - + if (index === 0) { // boyer-moore derrived algorithm to safely skip non-boundary data i += boundaryEnd @@ -222,7 +220,7 @@ export class MultipartParser { i -= boundaryEnd c = buffer[i] } - + if (index < boundary.length) { if (boundary[index] == c) { if (index === 0) { @@ -267,7 +265,7 @@ export class MultipartParser { index = 0 } } - + if (index > 0) { // when matching a possible boundary, keep a lookbehind reference // in case it turns out to be a false lead @@ -278,12 +276,12 @@ export class MultipartParser { callback('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 S.END: break @@ -291,21 +289,22 @@ export class MultipartParser { return i } } - + dataCallback('headerField') dataCallback('headerValue') dataCallback('partData') - + this.index = index this.state = state this.flags = flags - + return len } - + end() { - var callback = function(self, name) { - var callbackSymbol = 'on' + name.substr(0, 1).toUpperCase() + name.substr(1) + var callback = function (self, name) { + var callbackSymbol = + 'on' + name.substr(0, 1).toUpperCase() + name.substr(1) if (callbackSymbol in self) { self[callbackSymbol]() } @@ -322,10 +321,8 @@ export class MultipartParser { ) } } - + explain() { return 'state = ' + MultipartParser.stateToString(this.state) } } - - diff --git a/lib/octet_parser.js b/lib/octet_parser.js index db1fab6..8b0e925 100644 --- a/lib/octet_parser.js +++ b/lib/octet_parser.js @@ -1,15 +1,12 @@ import { EventEmitter } from 'events' - export class OctetParser extends EventEmitter { -write(buffer) { - this.emit('data', buffer) - return buffer.length -} + write(buffer) { + this.emit('data', buffer) + return buffer.length + } -end () { - this.emit('end') + end() { + this.emit('end') + } } -} - - diff --git a/lib/querystring_parser.js b/lib/querystring_parser.js index e502c77..b694010 100644 --- a/lib/querystring_parser.js +++ b/lib/querystring_parser.js @@ -1,6 +1,6 @@ // This is a buffering parser, not quite as nice as the multipart one. // If I find time I'll rewrite this to be fully streaming as well -import {parse} from 'node:querystring' +import { parse } from 'node:querystring' export class QuerystringParser { constructor(maxKeys) { diff --git a/package.json b/package.json index d23d734..34c6da2 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "@gm5/request", - "version": "1.2.8", - "description": "对Http的request进一步封装, 提供常用的API", + "version": "2.0.0", + "description": "对Http的Request进一步封装, 提供常用的API", "main": "index.js", "author": "yutent", "type": "module", "keywords": [ "five", - "node-five", + "gmf", + "gm5", "five.js", "fivejs", "request",