diff options
Diffstat (limited to 'cli/rt/24_body.js')
-rw-r--r-- | cli/rt/24_body.js | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/cli/rt/24_body.js b/cli/rt/24_body.js new file mode 100644 index 000000000..ebd0ddc6d --- /dev/null +++ b/cli/rt/24_body.js @@ -0,0 +1,207 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +((window) => { + const { Blob } = window.__bootstrap.blob; + const { ReadableStream, isReadableStreamDisturbed } = + window.__bootstrap.streams; + const { Buffer } = window.__bootstrap.buffer; + const { + getHeaderValueParams, + hasHeaderValueOf, + isTypedArray, + } = window.__bootstrap.webUtil; + const { MultipartParser } = window.__bootstrap.multipart; + + function validateBodyType(owner, bodySource) { + if (isTypedArray(bodySource)) { + return true; + } else if (bodySource instanceof ArrayBuffer) { + return true; + } else if (typeof bodySource === "string") { + return true; + } else if (bodySource instanceof ReadableStream) { + return true; + } else if (bodySource instanceof FormData) { + return true; + } else if (bodySource instanceof URLSearchParams) { + return true; + } else if (!bodySource) { + return true; // null body is fine + } + throw new Error( + `Bad ${owner.constructor.name} body type: ${bodySource.constructor.name}`, + ); + } + + async function bufferFromStream( + stream, + size, + ) { + const encoder = new TextEncoder(); + const buffer = new Buffer(); + + if (size) { + // grow to avoid unnecessary allocations & copies + buffer.grow(size); + } + + while (true) { + const { done, value } = await stream.read(); + + if (done) break; + + if (typeof value === "string") { + buffer.writeSync(encoder.encode(value)); + } else if (value instanceof ArrayBuffer) { + buffer.writeSync(new Uint8Array(value)); + } else if (value instanceof Uint8Array) { + buffer.writeSync(value); + } else if (!value) { + // noop for undefined + } else { + throw new Error("unhandled type on stream read"); + } + } + + return buffer.bytes().buffer; + } + + const BodyUsedError = + "Failed to execute 'clone' on 'Body': body is already used"; + + class Body { + #contentType = ""; + #size = undefined; + + constructor(_bodySource, meta) { + validateBodyType(this, _bodySource); + this._bodySource = _bodySource; + this.#contentType = meta.contentType; + this.#size = meta.size; + this._stream = null; + } + + get body() { + if (this._stream) { + return this._stream; + } + + if (this._bodySource instanceof ReadableStream) { + this._stream = this._bodySource; + } + if (typeof this._bodySource === "string") { + const bodySource = this._bodySource; + this._stream = new ReadableStream({ + start(controller) { + controller.enqueue(bodySource); + controller.close(); + }, + }); + } + return this._stream; + } + + get bodyUsed() { + if (this.body && isReadableStreamDisturbed(this.body)) { + return true; + } + return false; + } + + async blob() { + return new Blob([await this.arrayBuffer()], { + type: this.#contentType, + }); + } + + // ref: https://fetch.spec.whatwg.org/#body-mixin + async formData() { + const formData = new FormData(); + if (hasHeaderValueOf(this.#contentType, "multipart/form-data")) { + const params = getHeaderValueParams(this.#contentType); + + // ref: https://tools.ietf.org/html/rfc2046#section-5.1 + const boundary = params.get("boundary"); + const body = new Uint8Array(await this.arrayBuffer()); + const multipartParser = new MultipartParser(body, boundary); + + return multipartParser.parse(); + } else if ( + hasHeaderValueOf(this.#contentType, "application/x-www-form-urlencoded") + ) { + // From https://github.com/github/fetch/blob/master/fetch.js + // Copyright (c) 2014-2016 GitHub, Inc. MIT License + const body = await this.text(); + try { + body + .trim() + .split("&") + .forEach((bytes) => { + if (bytes) { + const split = bytes.split("="); + const name = split.shift().replace(/\+/g, " "); + const value = split.join("=").replace(/\+/g, " "); + formData.append( + decodeURIComponent(name), + decodeURIComponent(value), + ); + } + }); + } catch (e) { + throw new TypeError("Invalid form urlencoded format"); + } + return formData; + } else { + throw new TypeError("Invalid form data"); + } + } + + async text() { + if (typeof this._bodySource === "string") { + return this._bodySource; + } + + const ab = await this.arrayBuffer(); + const decoder = new TextDecoder("utf-8"); + return decoder.decode(ab); + } + + async json() { + const raw = await this.text(); + return JSON.parse(raw); + } + + arrayBuffer() { + if (isTypedArray(this._bodySource)) { + return Promise.resolve(this._bodySource.buffer); + } else if (this._bodySource instanceof ArrayBuffer) { + return Promise.resolve(this._bodySource); + } else if (typeof this._bodySource === "string") { + const enc = new TextEncoder(); + return Promise.resolve( + enc.encode(this._bodySource).buffer, + ); + } else if (this._bodySource instanceof ReadableStream) { + return bufferFromStream(this._bodySource.getReader(), this.#size); + } else if ( + this._bodySource instanceof FormData || + this._bodySource instanceof URLSearchParams + ) { + const enc = new TextEncoder(); + return Promise.resolve( + enc.encode(this._bodySource.toString()).buffer, + ); + } else if (!this._bodySource) { + return Promise.resolve(new ArrayBuffer(0)); + } + throw new Error( + `Body type not yet implemented: ${this._bodySource.constructor.name}`, + ); + } + } + + window.__bootstrap.body = { + Body, + BodyUsedError, + }; +})(this); |