deno/cli/rt/24_body.js

208 lines
5.8 KiB
JavaScript
Raw Normal View History

// 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);