1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-27 01:29:14 -05:00
denoland-deno/cli/js/web/body.ts

214 lines
6.3 KiB
TypeScript
Raw Normal View History

2020-07-06 21:45:39 -04:00
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import * as blob from "./blob.ts";
import * as encoding from "./text_encoding.ts";
import type * as domTypes from "./dom_types.d.ts";
2020-04-22 10:06:51 -04:00
import { ReadableStreamImpl } from "./streams/readable_stream.ts";
import { isReadableStreamDisturbed } from "./streams/internals.ts";
import { Buffer } from "../buffer.ts";
import {
getHeaderValueParams,
hasHeaderValueOf,
isTypedArray,
} from "./util.ts";
import { MultipartParser } from "./fetch/multipart.ts";
2019-05-01 23:56:42 -04:00
// only namespace imports work for now, plucking out what we need
const { TextEncoder, TextDecoder } = encoding;
const DenoBlob = blob.DenoBlob;
interface BodyMeta {
contentType: string;
size?: number;
}
function validateBodyType(owner: Body, bodySource: BodyInit | null): boolean {
if (isTypedArray(bodySource)) {
2019-05-01 23:56:42 -04:00
return true;
} else if (bodySource instanceof ArrayBuffer) {
return true;
} else if (typeof bodySource === "string") {
return true;
2020-04-22 10:06:51 -04:00
} else if (bodySource instanceof ReadableStreamImpl) {
return true;
2019-05-01 23:56:42 -04:00
} else if (bodySource instanceof FormData) {
return true;
} else if (bodySource instanceof URLSearchParams) {
return true;
2019-05-01 23:56:42 -04:00
} else if (!bodySource) {
return true; // null body is fine
}
throw new Error(
`Bad ${owner.constructor.name} body type: ${bodySource.constructor.name}`
);
}
2020-05-25 09:14:01 -04:00
async function bufferFromStream(
stream: ReadableStreamReader,
size?: number
2020-05-25 09:14:01 -04:00
): Promise<ArrayBuffer> {
const encoder = new TextEncoder();
const buffer = new Buffer();
if (size) {
// grow to avoid unnecessary allocations & copies
buffer.grow(size);
}
2020-05-25 09:14:01 -04:00
while (true) {
const { done, value } = await stream.read();
2020-05-25 09:14:01 -04:00
if (done) break;
if (typeof value === "string") {
buffer.writeSync(encoder.encode(value));
2020-05-25 09:14:01 -04:00
} else if (value instanceof ArrayBuffer) {
buffer.writeSync(new Uint8Array(value));
} else if (value instanceof Uint8Array) {
buffer.writeSync(value);
2020-05-25 09:14:01 -04:00
} else if (!value) {
// noop for undefined
} else {
throw new Error("unhandled type on stream read");
}
}
return buffer.bytes().buffer;
}
2019-05-01 23:56:42 -04:00
export const BodyUsedError =
"Failed to execute 'clone' on 'Body': body is already used";
export class Body implements domTypes.Body {
2020-04-22 10:06:51 -04:00
protected _stream: ReadableStreamImpl<string | ArrayBuffer> | null;
#contentType: string;
#size: number | undefined;
constructor(protected _bodySource: BodyInit | null, meta: BodyMeta) {
2019-05-01 23:56:42 -04:00
validateBodyType(this, _bodySource);
this._bodySource = _bodySource;
this.#contentType = meta.contentType;
this.#size = meta.size;
2019-05-01 23:56:42 -04:00
this._stream = null;
}
2020-04-22 10:06:51 -04:00
get body(): ReadableStream | null {
2019-05-01 23:56:42 -04:00
if (this._stream) {
return this._stream;
}
2020-04-22 10:06:51 -04:00
if (this._bodySource instanceof ReadableStreamImpl) {
this._stream = this._bodySource;
}
2019-05-01 23:56:42 -04:00
if (typeof this._bodySource === "string") {
const bodySource = this._bodySource;
2020-04-22 10:06:51 -04:00
this._stream = new ReadableStreamImpl<string | ArrayBuffer>({
start(controller: ReadableStreamDefaultController): void {
controller.enqueue(bodySource);
controller.close();
},
2020-04-22 10:06:51 -04:00
});
2019-05-01 23:56:42 -04:00
}
return this._stream;
}
get bodyUsed(): boolean {
if (this.body && isReadableStreamDisturbed(this.body)) {
2019-05-01 23:56:42 -04:00
return true;
}
return false;
}
public async blob(): Promise<Blob> {
return new DenoBlob([await this.arrayBuffer()], {
type: this.#contentType,
});
2019-05-01 23:56:42 -04:00
}
// ref: https://fetch.spec.whatwg.org/#body-mixin
public async formData(): Promise<FormData> {
2019-05-01 23:56:42 -04:00
const formData = new FormData();
if (hasHeaderValueOf(this.#contentType, "multipart/form-data")) {
const params = getHeaderValueParams(this.#contentType);
2019-05-01 23:56:42 -04:00
// 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);
2019-05-01 23:56:42 -04:00
return multipartParser.parse();
2019-05-01 23:56:42 -04:00
} else if (
hasHeaderValueOf(this.#contentType, "application/x-www-form-urlencoded")
2019-05-01 23:56:42 -04:00
) {
// 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): void => {
if (bytes) {
const split = bytes.split("=");
const name = split.shift()!.replace(/\+/g, " ");
const value = split.join("=").replace(/\+/g, " ");
formData.append(
decodeURIComponent(name),
decodeURIComponent(value)
);
2019-05-01 23:56:42 -04:00
}
});
2019-05-01 23:56:42 -04:00
} catch (e) {
throw new TypeError("Invalid form urlencoded format");
}
return formData;
} else {
throw new TypeError("Invalid form data");
}
}
public async text(): Promise<string> {
if (typeof this._bodySource === "string") {
return this._bodySource;
}
const ab = await this.arrayBuffer();
const decoder = new TextDecoder("utf-8");
return decoder.decode(ab);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public async json(): Promise<any> {
const raw = await this.text();
return JSON.parse(raw);
}
2020-03-20 09:38:34 -04:00
public arrayBuffer(): Promise<ArrayBuffer> {
if (isTypedArray(this._bodySource)) {
2020-03-20 09:38:34 -04:00
return Promise.resolve(this._bodySource.buffer as ArrayBuffer);
2019-05-01 23:56:42 -04:00
} else if (this._bodySource instanceof ArrayBuffer) {
2020-03-20 09:38:34 -04:00
return Promise.resolve(this._bodySource);
2019-05-01 23:56:42 -04:00
} else if (typeof this._bodySource === "string") {
const enc = new TextEncoder();
2020-03-20 09:38:34 -04:00
return Promise.resolve(
enc.encode(this._bodySource).buffer as ArrayBuffer
);
2020-04-22 10:06:51 -04:00
} else if (this._bodySource instanceof ReadableStreamImpl) {
return bufferFromStream(this._bodySource.getReader(), this.#size);
} else if (
this._bodySource instanceof FormData ||
this._bodySource instanceof URLSearchParams
) {
2019-05-01 23:56:42 -04:00
const enc = new TextEncoder();
2020-03-20 09:38:34 -04:00
return Promise.resolve(
enc.encode(this._bodySource.toString()).buffer as ArrayBuffer
);
2019-05-01 23:56:42 -04:00
} else if (!this._bodySource) {
2020-03-20 09:38:34 -04:00
return Promise.resolve(new ArrayBuffer(0));
2019-05-01 23:56:42 -04:00
}
throw new Error(
`Body type not yet implemented: ${this._bodySource.constructor.name}`
);
}
}