1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-23 15:16:54 -05:00
denoland-deno/cli/rt/24_body.js
2020-08-20 11:47:58 -04:00

220 lines
6 KiB
JavaScript

// 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;
}
function bodyToArrayBuffer(bodySource) {
if (isTypedArray(bodySource)) {
return bodySource.buffer;
} else if (bodySource instanceof ArrayBuffer) {
return bodySource;
} else if (typeof bodySource === "string") {
const enc = new TextEncoder();
return enc.encode(bodySource).buffer;
} else if (bodySource instanceof ReadableStream) {
throw new Error(
`Can't convert stream to ArrayBuffer (try bufferFromStream)`,
);
} else if (
bodySource instanceof FormData ||
bodySource instanceof URLSearchParams
) {
const enc = new TextEncoder();
return enc.encode(bodySource.toString()).buffer;
} else if (!bodySource) {
return null;
}
throw new Error(
`Body type not implemented: ${bodySource.constructor.name}`,
);
}
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) {
return null;
} else if (this._bodySource instanceof ReadableStream) {
this._stream = this._bodySource;
} else {
const buf = bodyToArrayBuffer(this._bodySource);
if (!(buf instanceof ArrayBuffer)) {
throw new Error(
`Expected ArrayBuffer from body`,
);
}
this._stream = new ReadableStream({
start(controller) {
controller.enqueue(buf);
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 (this._bodySource instanceof ReadableStream) {
return bufferFromStream(this._bodySource.getReader(), this.#size);
}
return bodyToArrayBuffer(this._bodySource);
}
}
window.__bootstrap.body = {
Body,
BodyUsedError,
};
})(this);