From 835c70bd6fdc5b1ba7e7ffd322490b6d66fb407e Mon Sep 17 00:00:00 2001 From: Szymon Marczak <36894700+szmarczak@users.noreply.github.com> Date: Wed, 22 Apr 2020 11:12:07 +0200 Subject: [PATCH] Do not alter `options.body` Fixes #1165 --- source/core/index.ts | 24 +++++++++++++++--------- source/core/utils/get-body-size.ts | 9 +-------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/source/core/index.ts b/source/core/index.ts index 98fd3d565..f322fa719 100644 --- a/source/core/index.ts +++ b/source/core/index.ts @@ -41,6 +41,7 @@ const kCancelTimeouts = Symbol('cancelTimeouts'); const kStartedReading = Symbol('startedReading'); const kStopReading = Symbol('stopReading'); const kTriggerRead = Symbol('triggerRead'); +const kBody = Symbol('body'); export const kIsNormalizedAlready = Symbol('isNormalizedAlready'); const supportsBrotli = is.string((process.versions as any).brotli); @@ -452,6 +453,7 @@ export default class Request extends Duplex implements RequestEvents { [kUploadedSize]: number; [kStopReading]: boolean; [kTriggerRead]: boolean; + [kBody]: Options['body']; [kBodySize]?: number; [kServerResponsesPiped]: Set; [kIsFromCache]?: boolean; @@ -889,21 +891,23 @@ export default class Request extends Duplex implements RequestEvents { if (isFormData(options.body) && noContentType) { headers['content-type'] = `multipart/form-data; boundary=${options.body.getBoundary()}`; } + + this[kBody] = options.body; } else if (isForm) { if (noContentType) { headers['content-type'] = 'application/x-www-form-urlencoded'; } - options.body = (new URLSearchParams(options.form as Record)).toString(); + this[kBody] = (new URLSearchParams(options.form as Record)).toString(); } else { if (noContentType) { headers['content-type'] = 'application/json'; } - options.body = JSON.stringify(options.json); + this[kBody] = JSON.stringify(options.json); } - const uploadBodySize = await getBodySize(options); + const uploadBodySize = await getBodySize(this[kBody], options.headers); // See https://tools.ietf.org/html/rfc7230#section-3.3.2 // A user agent SHOULD send a Content-Length in a request message when @@ -1149,21 +1153,23 @@ export default class Request extends Duplex implements RequestEvents { this.emit('uploadProgress', this.uploadProgress); // Send body + const body = this[kBody]; const currentRequest = this.redirects.length === 0 ? this : request; - if (is.nodeStream(options.body)) { - options.body.pipe(currentRequest); - options.body.once('error', (error: NodeJS.ErrnoException) => { + + if (is.nodeStream(body)) { + body.pipe(currentRequest); + body.once('error', (error: NodeJS.ErrnoException) => { this._beforeError(new UploadError(error, this)); }); - options.body.once('end', () => { + body.once('end', () => { delete options.body; }); } else { this._unlockWrite(); - if (!is.undefined(options.body)) { - this._writeRequest(options.body, null as unknown as string, () => {}); + if (!is.undefined(body)) { + this._writeRequest(body, null as unknown as string, () => {}); currentRequest.end(); this._lockWrite(); diff --git a/source/core/utils/get-body-size.ts b/source/core/utils/get-body-size.ts index b6570cda9..51956eccb 100644 --- a/source/core/utils/get-body-size.ts +++ b/source/core/utils/get-body-size.ts @@ -4,16 +4,9 @@ import {ClientRequestArgs} from 'http'; import is from '@sindresorhus/is'; import isFormData from './is-form-data'; -interface Options { - body?: unknown; - headers: ClientRequestArgs['headers']; -} - const statAsync = promisify(stat); -export default async (options: Options): Promise => { - const {body, headers} = options; - +export default async (body: unknown, headers: ClientRequestArgs['headers']): Promise => { if (headers && 'content-length' in headers) { return Number(headers['content-length']); }