diff --git a/lib/transports-uws/polling.ts b/lib/transports-uws/polling.ts index 8d6b9827..42dd22fc 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,18 +143,9 @@ 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; - }; - - 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) @@ -150,36 +153,63 @@ 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 = () => { - this.onData(Buffer.concat(chunks).toString()); + } - if (this.readyState !== "closing") { - res.end("ok"); - } - cleanup(); + const onEnd = (buffer) => { + this.onData(buffer.toString()); + this.onDataRequestCleanup(); + res.end("ok"); }; - res.onAborted(onClose); + res.onAborted(() => { + this.onDataRequestCleanup(); + this.onError("data request connection closed prematurely"); + }); - 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(); + this.onDataRequestCleanup(); + return; + } + onEnd(buffer); + return; } + + contentLength = totalLength; }); } + /** + * Cleanup onDataRequest. + * + * @api private + */ + onDataRequestCleanup() { + this.dataReq = this.dataRes = null; + } + /** * Processes the incoming data payload. *