From fb11d831566faeab27163d3bb7c29cb9c530a880 Mon Sep 17 00:00:00 2001 From: e3dio <85405955+e3dio@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:14:44 -0700 Subject: [PATCH 1/2] fix uws polling properly handle multiple onData events --- lib/transports-uws/polling.ts | 54 +++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 8d6b9827..1bf01a96 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -122,6 +122,18 @@ export class Polling extends Transport { return; } + const contentLengthHeader = Number(req.headers["content-length"]); + if (!contentLengthHeader) { + this.onError("content-length header required"); + res.writeStatus("411 Length Required").end(); + return; + } + if (contentLengthHeader > this.maxHttpBufferSize) { + this.onError("payload too large"); + res.writeStatus("413 Payload Too Large").end(); + return; + } + const isBinary = "application/octet-stream" === req.headers["content-type"]; if (isBinary && this.protocol === 4) { @@ -131,11 +143,11 @@ export class Polling extends Transport { this.dataReq = req; this.dataRes = res; - let chunks = []; + let buffer; let contentLength = 0; const cleanup = () => { - this.dataReq = this.dataRes = chunks = null; + this.dataReq = this.dataRes = null; }; const onClose = () => { @@ -154,8 +166,8 @@ export class Polling extends Transport { res.writeHeader(key, String(headers[key])); }); - const onEnd = () => { - this.onData(Buffer.concat(chunks).toString()); + const onEnd = (buffer) => { + this.onData(buffer.toString()); if (this.readyState !== "closing") { res.end("ok"); @@ -165,18 +177,36 @@ export class Polling extends Transport { res.onAborted(onClose); - res.onData((chunk, isLast) => { - chunks.push(Buffer.from(chunk)); - contentLength += Buffer.byteLength(chunk); - if (contentLength > this.maxHttpBufferSize) { - this.onError("payload too large"); - res.writeStatus("413 Payload Too Large"); - res.end(); + res.onData((arrayBuffer, isLast) => { + const totalLength = contentLength + arrayBuffer.byteLength; + if (totalLength > contentLengthHeader) { + this.onError("content-length mismatch"); + res.close(); // calls onAborted return; } + + if (!buffer) { + if (isLast) { + onEnd(Buffer.from(arrayBuffer)); + return; + } + buffer = Buffer.allocUnsafe(contentLengthHeader); + } + + Buffer.from(arrayBuffer).copy(buffer, contentLength); + if (isLast) { - onEnd(); + if (totalLength != contentLengthHeader) { + this.onError("content-length mismatch"); + res.writeStatus("400 content-length mismatch").end(); + cleanup(); + return; + } + onEnd(buffer); + return; } + + contentLength = totalLength; }); } From 9ccbc34da94bd0ed0893ce0d8b205b8044a0f908 Mon Sep 17 00:00:00 2001 From: e3dio <85405955+e3dio@users.noreply.github.com> Date: Tue, 22 Feb 2022 13:38:19 -0700 Subject: [PATCH 2/2] perf update replace Object.keys(headers).forEach remove nested inner functions --- lib/transports-uws/polling.ts | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 1bf01a96..42dd22fc 100644 --- a/lib/transports-uws/polling.ts +++ b/lib/transports-uws/polling.ts @@ -146,15 +146,6 @@ export class Polling extends Transport { let buffer; let contentLength = 0; - const cleanup = () => { - this.dataReq = this.dataRes = null; - }; - - const onClose = () => { - cleanup(); - this.onError("data request connection closed prematurely"); - }; - const headers = { // text/html is required instead of text/plain to avoid an // unwanted download dialog on certain user-agents (GH-43) @@ -162,20 +153,20 @@ export class Polling extends Transport { }; this.headers(req, headers); - Object.keys(headers).forEach(key => { + for (let key in headers) { res.writeHeader(key, String(headers[key])); - }); + } const onEnd = (buffer) => { this.onData(buffer.toString()); - - if (this.readyState !== "closing") { - res.end("ok"); - } - cleanup(); + this.onDataRequestCleanup(); + res.end("ok"); }; - res.onAborted(onClose); + res.onAborted(() => { + this.onDataRequestCleanup(); + this.onError("data request connection closed prematurely"); + }); res.onData((arrayBuffer, isLast) => { const totalLength = contentLength + arrayBuffer.byteLength; @@ -199,7 +190,7 @@ export class Polling extends Transport { if (totalLength != contentLengthHeader) { this.onError("content-length mismatch"); res.writeStatus("400 content-length mismatch").end(); - cleanup(); + this.onDataRequestCleanup(); return; } onEnd(buffer); @@ -210,6 +201,15 @@ export class Polling extends Transport { }); } + /** + * Cleanup onDataRequest. + * + * @api private + */ + onDataRequestCleanup() { + this.dataReq = this.dataRes = null; + } + /** * Processes the incoming data payload. *