summaryrefslogtreecommitdiff
path: root/cli/rt/24_body.js
diff options
context:
space:
mode:
Diffstat (limited to 'cli/rt/24_body.js')
-rw-r--r--cli/rt/24_body.js207
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);