mirror of
https://github.com/denoland/deno.git
synced 2024-11-29 16:30:56 -05:00
220 lines
6 KiB
JavaScript
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 new ArrayBuffer(0);
|
|
}
|
|
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);
|