summaryrefslogtreecommitdiff
path: root/multipart
diff options
context:
space:
mode:
authorVincent LE GOFF <g_n_s@hotmail.fr>2019-05-21 15:36:12 +0200
committerRyan Dahl <ry@tinyclouds.org>2019-05-21 09:36:12 -0400
commitb9ce3a6453d432df3150a4fceda5b205f5c21354 (patch)
tree7280e3dac691717247914673314c6c0bdb14b8cd /multipart
parent915b4f520b16bd5c9bc6ef0ba9e45eba27c4137d (diff)
Rename //multipart/multipart.ts to //mime/multipart.ts (denoland/deno_std#420)
Original: https://github.com/denoland/deno_std/commit/aad0896346805513dc87eb01cd867a1513f574b1
Diffstat (limited to 'multipart')
-rw-r--r--multipart/multipart.ts503
-rw-r--r--multipart/multipart_test.ts213
-rw-r--r--multipart/test.ts1
3 files changed, 0 insertions, 717 deletions
diff --git a/multipart/multipart.ts b/multipart/multipart.ts
deleted file mode 100644
index 0032e1cf5..000000000
--- a/multipart/multipart.ts
+++ /dev/null
@@ -1,503 +0,0 @@
-// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
-
-const { Buffer, copy, remove } = Deno;
-type Closer = Deno.Closer;
-type Reader = Deno.Reader;
-type ReadResult = Deno.ReadResult;
-type Writer = Deno.Writer;
-import { FormFile } from "./formfile.ts";
-import {
- bytesFindIndex,
- bytesFindLastIndex,
- bytesHasPrefix,
- bytesEqual
-} from "../bytes/bytes.ts";
-import { copyN } from "../io/ioutil.ts";
-import { MultiReader } from "../io/readers.ts";
-import { tempFile } from "../io/util.ts";
-import { BufReader, BufState, BufWriter } from "../io/bufio.ts";
-import { TextProtoReader } from "../textproto/mod.ts";
-import { encoder } from "../strings/strings.ts";
-import * as path from "../fs/path.ts";
-
-function randomBoundary(): string {
- let boundary = "--------------------------";
- for (let i = 0; i < 24; i++) {
- boundary += Math.floor(Math.random() * 10).toString(16);
- }
- return boundary;
-}
-
-export function matchAfterPrefix(
- a: Uint8Array,
- prefix: Uint8Array,
- bufState: BufState
-): number {
- if (a.length === prefix.length) {
- if (bufState) {
- return 1;
- }
- return 0;
- }
- const c = a[prefix.length];
- if (
- c === " ".charCodeAt(0) ||
- c === "\t".charCodeAt(0) ||
- c === "\r".charCodeAt(0) ||
- c === "\n".charCodeAt(0) ||
- c === "-".charCodeAt(0)
- ) {
- return 1;
- }
- return -1;
-}
-
-export function scanUntilBoundary(
- buf: Uint8Array,
- dashBoundary: Uint8Array,
- newLineDashBoundary: Uint8Array,
- total: number,
- state: BufState
-): [number, BufState] {
- if (total === 0) {
- if (bytesHasPrefix(buf, dashBoundary)) {
- switch (matchAfterPrefix(buf, dashBoundary, state)) {
- case -1:
- return [dashBoundary.length, null];
- case 0:
- return [0, null];
- case 1:
- return [0, "EOF"];
- }
- if (bytesHasPrefix(dashBoundary, buf)) {
- return [0, state];
- }
- }
- }
- const i = bytesFindIndex(buf, newLineDashBoundary);
- if (i >= 0) {
- switch (matchAfterPrefix(buf.slice(i), newLineDashBoundary, state)) {
- case -1:
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- return [i + newLineDashBoundary.length, null];
- case 0:
- return [i, null];
- case 1:
- return [i, "EOF"];
- }
- }
- if (bytesHasPrefix(newLineDashBoundary, buf)) {
- return [0, state];
- }
- const j = bytesFindLastIndex(buf, newLineDashBoundary.slice(0, 1));
- if (j >= 0 && bytesHasPrefix(newLineDashBoundary, buf.slice(j))) {
- return [j, null];
- }
- return [buf.length, state];
-}
-
-let i = 0;
-
-class PartReader implements Reader, Closer {
- n: number = 0;
- total: number = 0;
- bufState: BufState = null;
- index = i++;
-
- constructor(private mr: MultipartReader, public readonly headers: Headers) {}
-
- async read(p: Uint8Array): Promise<ReadResult> {
- const br = this.mr.bufReader;
- const returnResult = (nread: number, bufState: BufState): ReadResult => {
- if (bufState && bufState !== "EOF") {
- throw bufState;
- }
- return { nread, eof: bufState === "EOF" };
- };
- if (this.n === 0 && !this.bufState) {
- const [peek] = await br.peek(br.buffered());
- const [n, state] = scanUntilBoundary(
- peek,
- this.mr.dashBoundary,
- this.mr.newLineDashBoundary,
- this.total,
- this.bufState
- );
- this.n = n;
- this.bufState = state;
- if (this.n === 0 && !this.bufState) {
- // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
- const [, state] = await br.peek(peek.length + 1);
- this.bufState = state;
- if (this.bufState === "EOF") {
- this.bufState = new RangeError("unexpected eof");
- }
- }
- }
- if (this.n === 0) {
- return returnResult(0, this.bufState);
- }
-
- let n = 0;
- if (p.byteLength > this.n) {
- n = this.n;
- }
- const buf = p.slice(0, n);
- const [nread] = await this.mr.bufReader.readFull(buf);
- p.set(buf);
- this.total += nread;
- this.n -= nread;
- if (this.n === 0) {
- return returnResult(n, this.bufState);
- }
- return returnResult(n, null);
- }
-
- close(): void {}
-
- private contentDisposition: string;
- private contentDispositionParams: { [key: string]: string };
-
- private getContentDispositionParams(): { [key: string]: string } {
- if (this.contentDispositionParams) return this.contentDispositionParams;
- const cd = this.headers.get("content-disposition");
- const params = {};
- const comps = cd.split(";");
- this.contentDisposition = comps[0];
- comps
- .slice(1)
- .map((v): string => v.trim())
- .map(
- (kv): void => {
- const [k, v] = kv.split("=");
- if (v) {
- const s = v.charAt(0);
- const e = v.charAt(v.length - 1);
- if ((s === e && s === '"') || s === "'") {
- params[k] = v.substr(1, v.length - 2);
- } else {
- params[k] = v;
- }
- }
- }
- );
- return (this.contentDispositionParams = params);
- }
-
- get fileName(): string {
- return this.getContentDispositionParams()["filename"];
- }
-
- get formName(): string {
- const p = this.getContentDispositionParams();
- if (this.contentDisposition === "form-data") {
- return p["name"];
- }
- return "";
- }
-}
-
-function skipLWSPChar(u: Uint8Array): Uint8Array {
- const ret = new Uint8Array(u.length);
- const sp = " ".charCodeAt(0);
- const ht = "\t".charCodeAt(0);
- let j = 0;
- for (let i = 0; i < u.length; i++) {
- if (u[i] === sp || u[i] === ht) continue;
- ret[j++] = u[i];
- }
- return ret.slice(0, j);
-}
-
-/** Reader for parsing multipart/form-data */
-export class MultipartReader {
- readonly newLine = encoder.encode("\r\n");
- readonly newLineDashBoundary = encoder.encode(`\r\n--${this.boundary}`);
- readonly dashBoundaryDash = encoder.encode(`--${this.boundary}--`);
- readonly dashBoundary = encoder.encode(`--${this.boundary}`);
- readonly bufReader: BufReader;
-
- constructor(private reader: Reader, private boundary: string) {
- this.bufReader = new BufReader(reader);
- }
-
- /** Read all form data from stream.
- * If total size of stored data in memory exceed maxMemory,
- * overflowed file data will be written to temporal files.
- * String field values are never written to files */
- async readForm(
- maxMemory: number
- ): Promise<{ [key: string]: string | FormFile }> {
- const result = Object.create(null);
- let maxValueBytes = maxMemory + (10 << 20);
- const buf = new Buffer(new Uint8Array(maxValueBytes));
- for (;;) {
- const p = await this.nextPart();
- if (!p) {
- break;
- }
- if (p.formName === "") {
- continue;
- }
- buf.reset();
- if (!p.fileName) {
- // value
- const n = await copyN(buf, p, maxValueBytes);
- maxValueBytes -= n;
- if (maxValueBytes < 0) {
- throw new RangeError("message too large");
- }
- const value = buf.toString();
- result[p.formName] = value;
- continue;
- }
- // file
- let formFile: FormFile;
- const n = await copy(buf, p);
- if (n > maxMemory) {
- // too big, write to disk and flush buffer
- const ext = path.extname(p.fileName);
- const { file, filepath } = await tempFile(".", {
- prefix: "multipart-",
- postfix: ext
- });
- try {
- const size = await copyN(
- file,
- new MultiReader(buf, p),
- maxValueBytes
- );
- file.close();
- formFile = {
- filename: p.fileName,
- type: p.headers.get("content-type"),
- tempfile: filepath,
- size
- };
- } catch (e) {
- await remove(filepath);
- }
- } else {
- formFile = {
- filename: p.fileName,
- type: p.headers.get("content-type"),
- content: buf.bytes(),
- size: buf.bytes().byteLength
- };
- maxMemory -= n;
- maxValueBytes -= n;
- }
- result[p.formName] = formFile;
- }
- return result;
- }
-
- private currentPart: PartReader;
- private partsRead: number;
-
- private async nextPart(): Promise<PartReader> {
- if (this.currentPart) {
- this.currentPart.close();
- }
- if (bytesEqual(this.dashBoundary, encoder.encode("--"))) {
- throw new Error("boundary is empty");
- }
- let expectNewPart = false;
- for (;;) {
- const [line, state] = await this.bufReader.readSlice("\n".charCodeAt(0));
- if (state === "EOF" && this.isFinalBoundary(line)) {
- break;
- }
- if (state) {
- throw new Error(`aa${state.toString()}`);
- }
- if (this.isBoundaryDelimiterLine(line)) {
- this.partsRead++;
- const r = new TextProtoReader(this.bufReader);
- const [headers, state] = await r.readMIMEHeader();
- if (state) {
- throw state;
- }
- const np = new PartReader(this, headers);
- this.currentPart = np;
- return np;
- }
- if (this.isFinalBoundary(line)) {
- break;
- }
- if (expectNewPart) {
- throw new Error(`expecting a new Part; got line ${line}`);
- }
- if (this.partsRead === 0) {
- continue;
- }
- if (bytesEqual(line, this.newLine)) {
- expectNewPart = true;
- continue;
- }
- throw new Error(`unexpected line in next(): ${line}`);
- }
- }
-
- private isFinalBoundary(line: Uint8Array): boolean {
- if (!bytesHasPrefix(line, this.dashBoundaryDash)) {
- return false;
- }
- let rest = line.slice(this.dashBoundaryDash.length, line.length);
- return rest.length === 0 || bytesEqual(skipLWSPChar(rest), this.newLine);
- }
-
- private isBoundaryDelimiterLine(line: Uint8Array): boolean {
- if (!bytesHasPrefix(line, this.dashBoundary)) {
- return false;
- }
- const rest = line.slice(this.dashBoundary.length);
- return bytesEqual(skipLWSPChar(rest), this.newLine);
- }
-}
-
-class PartWriter implements Writer {
- closed = false;
- private readonly partHeader: string;
- private headersWritten: boolean = false;
-
- constructor(
- private writer: Writer,
- readonly boundary: string,
- public headers: Headers,
- isFirstBoundary: boolean
- ) {
- let buf = "";
- if (isFirstBoundary) {
- buf += `--${boundary}\r\n`;
- } else {
- buf += `\r\n--${boundary}\r\n`;
- }
- for (const [key, value] of headers.entries()) {
- buf += `${key}: ${value}\r\n`;
- }
- buf += `\r\n`;
- this.partHeader = buf;
- }
-
- close(): void {
- this.closed = true;
- }
-
- async write(p: Uint8Array): Promise<number> {
- if (this.closed) {
- throw new Error("part is closed");
- }
- if (!this.headersWritten) {
- await this.writer.write(encoder.encode(this.partHeader));
- this.headersWritten = true;
- }
- return this.writer.write(p);
- }
-}
-
-function checkBoundary(b: string): string {
- if (b.length < 1 || b.length > 70) {
- throw new Error(`invalid boundary length: ${b.length}`);
- }
- const end = b.length - 1;
- for (let i = 0; i < end; i++) {
- const c = b.charAt(i);
- if (!c.match(/[a-zA-Z0-9'()+_,\-./:=?]/) || (c === " " && i !== end)) {
- throw new Error("invalid boundary character: " + c);
- }
- }
- return b;
-}
-
-/** Writer for creating multipart/form-data */
-export class MultipartWriter {
- private readonly _boundary: string;
-
- get boundary(): string {
- return this._boundary;
- }
-
- private lastPart: PartWriter;
- private bufWriter: BufWriter;
- private isClosed: boolean = false;
-
- constructor(private readonly writer: Writer, boundary?: string) {
- if (boundary !== void 0) {
- this._boundary = checkBoundary(boundary);
- } else {
- this._boundary = randomBoundary();
- }
- this.bufWriter = new BufWriter(writer);
- }
-
- formDataContentType(): string {
- return `multipart/form-data; boundary=${this.boundary}`;
- }
-
- private createPart(headers: Headers): Writer {
- if (this.isClosed) {
- throw new Error("multipart: writer is closed");
- }
- if (this.lastPart) {
- this.lastPart.close();
- }
- const part = new PartWriter(
- this.writer,
- this.boundary,
- headers,
- !this.lastPart
- );
- this.lastPart = part;
- return part;
- }
-
- createFormFile(field: string, filename: string): Writer {
- const h = new Headers();
- h.set(
- "Content-Disposition",
- `form-data; name="${field}"; filename="${filename}"`
- );
- h.set("Content-Type", "application/octet-stream");
- return this.createPart(h);
- }
-
- createFormField(field: string): Writer {
- const h = new Headers();
- h.set("Content-Disposition", `form-data; name="${field}"`);
- h.set("Content-Type", "application/octet-stream");
- return this.createPart(h);
- }
-
- async writeField(field: string, value: string): Promise<void> {
- const f = await this.createFormField(field);
- await f.write(encoder.encode(value));
- }
-
- async writeFile(
- field: string,
- filename: string,
- file: Reader
- ): Promise<void> {
- const f = await this.createFormFile(field, filename);
- await copy(f, file);
- }
-
- private flush(): Promise<BufState> {
- return this.bufWriter.flush();
- }
-
- /** Close writer. No additional data can be writen to stream */
- async close(): Promise<void> {
- if (this.isClosed) {
- throw new Error("multipart: writer is closed");
- }
- if (this.lastPart) {
- this.lastPart.close();
- this.lastPart = void 0;
- }
- await this.writer.write(encoder.encode(`\r\n--${this.boundary}--\r\n`));
- await this.flush();
- this.isClosed = true;
- }
-}
diff --git a/multipart/multipart_test.ts b/multipart/multipart_test.ts
deleted file mode 100644
index ba4f05116..000000000
--- a/multipart/multipart_test.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
-
-const { Buffer, copy, open, remove } = Deno;
-import {
- assert,
- assertEquals,
- assertThrows,
- assertThrowsAsync
-} from "../testing/asserts.ts";
-import { test } from "../testing/mod.ts";
-import {
- matchAfterPrefix,
- MultipartReader,
- MultipartWriter,
- scanUntilBoundary
-} from "./multipart.ts";
-import * as path from "../fs/path.ts";
-import { FormFile, isFormFile } from "./formfile.ts";
-import { StringWriter } from "../io/writers.ts";
-
-const e = new TextEncoder();
-const boundary = "--abcde";
-const dashBoundary = e.encode("--" + boundary);
-const nlDashBoundary = e.encode("\r\n--" + boundary);
-
-test(function multipartScanUntilBoundary1(): void {
- const data = `--${boundary}`;
- const [n, err] = scanUntilBoundary(
- e.encode(data),
- dashBoundary,
- nlDashBoundary,
- 0,
- "EOF"
- );
- assertEquals(n, 0);
- assertEquals(err, "EOF");
-});
-
-test(function multipartScanUntilBoundary2(): void {
- const data = `foo\r\n--${boundary}`;
- const [n, err] = scanUntilBoundary(
- e.encode(data),
- dashBoundary,
- nlDashBoundary,
- 0,
- "EOF"
- );
- assertEquals(n, 3);
- assertEquals(err, "EOF");
-});
-
-test(function multipartScanUntilBoundary4(): void {
- const data = `foo\r\n--`;
- const [n, err] = scanUntilBoundary(
- e.encode(data),
- dashBoundary,
- nlDashBoundary,
- 0,
- null
- );
- assertEquals(n, 3);
- assertEquals(err, null);
-});
-
-test(function multipartScanUntilBoundary3(): void {
- const data = `foobar`;
- const [n, err] = scanUntilBoundary(
- e.encode(data),
- dashBoundary,
- nlDashBoundary,
- 0,
- null
- );
- assertEquals(n, data.length);
- assertEquals(err, null);
-});
-
-test(function multipartMatchAfterPrefix1(): void {
- const data = `${boundary}\r`;
- const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null);
- assertEquals(v, 1);
-});
-
-test(function multipartMatchAfterPrefix2(): void {
- const data = `${boundary}hoge`;
- const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null);
- assertEquals(v, -1);
-});
-
-test(function multipartMatchAfterPrefix3(): void {
- const data = `${boundary}`;
- const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null);
- assertEquals(v, 0);
-});
-
-test(async function multipartMultipartWriter(): Promise<void> {
- const buf = new Buffer();
- const mw = new MultipartWriter(buf);
- await mw.writeField("foo", "foo");
- await mw.writeField("bar", "bar");
- const f = await open(path.resolve("./multipart/fixtures/sample.txt"), "r");
- await mw.writeFile("file", "sample.txt", f);
- await mw.close();
-});
-
-test(function multipartMultipartWriter2(): void {
- const w = new StringWriter();
- assertThrows(
- (): MultipartWriter => new MultipartWriter(w, ""),
- Error,
- "invalid boundary length"
- );
- assertThrows(
- (): MultipartWriter =>
- new MultipartWriter(
- w,
- "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
- ),
- Error,
- "invalid boundary length"
- );
- assertThrows(
- (): MultipartWriter => new MultipartWriter(w, "aaa aaa"),
- Error,
- "invalid boundary character"
- );
- assertThrows(
- (): MultipartWriter => new MultipartWriter(w, "boundary¥¥"),
- Error,
- "invalid boundary character"
- );
-});
-
-test(async function multipartMultipartWriter3(): Promise<void> {
- const w = new StringWriter();
- const mw = new MultipartWriter(w);
- await mw.writeField("foo", "foo");
- await mw.close();
- await assertThrowsAsync(
- async (): Promise<void> => {
- await mw.close();
- },
- Error,
- "closed"
- );
- await assertThrowsAsync(
- async (): Promise<void> => {
- await mw.writeFile("bar", "file", null);
- },
- Error,
- "closed"
- );
- await assertThrowsAsync(
- async (): Promise<void> => {
- await mw.writeField("bar", "bar");
- },
- Error,
- "closed"
- );
- assertThrows(
- (): void => {
- mw.createFormField("bar");
- },
- Error,
- "closed"
- );
- assertThrows(
- (): void => {
- mw.createFormFile("bar", "file");
- },
- Error,
- "closed"
- );
-});
-
-test(async function multipartMultipartReader(): Promise<void> {
- // FIXME: path resolution
- const o = await open(path.resolve("./multipart/fixtures/sample.txt"));
- const mr = new MultipartReader(
- o,
- "--------------------------434049563556637648550474"
- );
- const form = await mr.readForm(10 << 20);
- assertEquals(form["foo"], "foo");
- assertEquals(form["bar"], "bar");
- const file = form["file"] as FormFile;
- assertEquals(isFormFile(file), true);
- assert(file.content !== void 0);
-});
-
-test(async function multipartMultipartReader2(): Promise<void> {
- const o = await open(path.resolve("./multipart/fixtures/sample.txt"));
- const mr = new MultipartReader(
- o,
- "--------------------------434049563556637648550474"
- );
- const form = await mr.readForm(20); //
- try {
- assertEquals(form["foo"], "foo");
- assertEquals(form["bar"], "bar");
- const file = form["file"] as FormFile;
- assertEquals(file.type, "application/octet-stream");
- const f = await open(file.tempfile);
- const w = new StringWriter();
- await copy(w, f);
- const json = JSON.parse(w.toString());
- assertEquals(json["compilerOptions"]["target"], "es2018");
- f.close();
- } finally {
- const file = form["file"] as FormFile;
- await remove(file.tempfile);
- }
-});
diff --git a/multipart/test.ts b/multipart/test.ts
index 89678d96d..9adde5158 100644
--- a/multipart/test.ts
+++ b/multipart/test.ts
@@ -1,3 +1,2 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import "./formfile_test.ts";
-import "./multipart_test.ts";