diff --git a/mime/multipart.ts b/mime/multipart.ts index 832211a27fae..25dba1f4b405 100644 --- a/mime/multipart.ts +++ b/mime/multipart.ts @@ -10,11 +10,13 @@ import * as bytes from "../bytes/mod.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 { BufReader, BufWriter, EOF, UnexpectedEOFError } from "../io/bufio.ts"; import { TextProtoReader } from "../textproto/mod.ts"; import { encoder } from "../strings/mod.ts"; import * as path from "../fs/path.ts"; +const EMPTY_BUF = new Uint8Array(0); + function randomBoundary(): string { let boundary = "--------------------------"; for (let i = 0; i < 24; i++) { @@ -26,10 +28,10 @@ function randomBoundary(): string { export function matchAfterPrefix( a: Uint8Array, prefix: Uint8Array, - bufState: BufState -): number { + eof: boolean +): -1 | 0 | 1 { if (a.length === prefix.length) { - if (bufState) { + if (eof) { return 1; } return 0; @@ -52,43 +54,46 @@ export function scanUntilBoundary( dashBoundary: Uint8Array, newLineDashBoundary: Uint8Array, total: number, - state: BufState -): [number, BufState] { + eof: boolean +): number | EOF { if (total === 0) { if (bytes.hasPrefix(buf, dashBoundary)) { - switch (matchAfterPrefix(buf, dashBoundary, state)) { + switch (matchAfterPrefix(buf, dashBoundary, eof)) { case -1: - return [dashBoundary.length, null]; + return dashBoundary.length; case 0: - return [0, null]; + return 0; case 1: - return [0, "EOF"]; - } - if (bytes.hasPrefix(dashBoundary, buf)) { - return [0, state]; + return EOF; } } + if (bytes.hasPrefix(dashBoundary, buf)) { + return 0; + } } + const i = bytes.findIndex(buf, newLineDashBoundary); if (i >= 0) { - switch (matchAfterPrefix(buf.slice(i), newLineDashBoundary, state)) { + switch (matchAfterPrefix(buf.slice(i), newLineDashBoundary, eof)) { case -1: // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - return [i + newLineDashBoundary.length, null]; + return i + newLineDashBoundary.length; case 0: - return [i, null]; + return i; case 1: - return [i, "EOF"]; + return i === 0 && eof ? EOF : i; } } if (bytes.hasPrefix(newLineDashBoundary, buf)) { - return [0, state]; + return 0; } + const j = bytes.findLastIndex(buf, newLineDashBoundary.slice(0, 1)); if (j >= 0 && bytes.hasPrefix(newLineDashBoundary, buf.slice(j))) { - return [j, null]; + return j; } - return [buf.length, state]; + + return buf.length === 0 && this.eof ? EOF : buf.length; } let i = 0; @@ -96,56 +101,54 @@ let i = 0; class PartReader implements Reader, Closer { n: number = 0; total: number = 0; - bufState: BufState = null; + eof: boolean = false; index = i++; constructor(private mr: MultipartReader, public readonly headers: Headers) {} async read(p: Uint8Array): Promise { 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) { + const returnResult = nread => (nread === 0 && this.eof ? EOF : nread); + if (this.n === 0 && !this.eof) { + const peek = await br.peek(br.buffered()); + if (peek === EOF) { + this.eof = true; + } else { + const n = scanUntilBoundary( + peek, + this.mr.dashBoundary, + this.mr.newLineDashBoundary, + this.total, + false + ); + if (n === EOF) throw new UnexpectedEOFError(); + this.n = n; + // 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 && + !this.eof && + (await br.peek(peek.length + 1)) === EOF + ) { + this.eof = true; + throw new UnexpectedEOFError(); } } } if (this.n === 0) { - return returnResult(0, this.bufState); + return returnResult(0); } - 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); + if (p.length < this.n) { + return returnResult(0); } - return returnResult(n, null); + const n = this.n; + const buf = p.subarray(0, n); + const r = await this.mr.bufReader.readFull(buf); + if (r === EOF) throw new UnexpectedEOFError(); + this.total += n; + this.n -= n; + return returnResult(n); } close(): void {} @@ -212,7 +215,7 @@ export class MultipartReader { readonly dashBoundary = encoder.encode(`--${this.boundary}`); readonly bufReader: BufReader; - constructor(private reader: Reader, private boundary: string) { + constructor(reader: Reader, private boundary: string) { this.bufReader = new BufReader(reader); } @@ -228,6 +231,7 @@ export class MultipartReader { const buf = new Buffer(new Uint8Array(maxValueBytes)); for (;;) { const p = await this.nextPart(); + console.error(p); if (!p) { break; } @@ -277,7 +281,7 @@ export class MultipartReader { filename: p.fileName, type: p.headers.get("content-type"), content: buf.bytes(), - size: buf.bytes().byteLength + size: buf.bytes().length }; maxMemory -= n; maxValueBytes -= n; @@ -291,6 +295,7 @@ export class MultipartReader { private partsRead: number; private async nextPart(): Promise { + console.error("PART\n"); if (this.currentPart) { this.currentPart.close(); } @@ -299,19 +304,20 @@ export class MultipartReader { } 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()}`); + const line = await this.bufReader.readSlice("\n".charCodeAt(0)); + if (line === EOF) { + if (this.isFinalBoundary(EMPTY_BUF)) { + break; + } else { + throw new UnexpectedEOFError(); + } } if (this.isBoundaryDelimiterLine(line)) { this.partsRead++; const r = new TextProtoReader(this.bufReader); - const [headers, state] = await r.readMIMEHeader(); - if (state) { - throw state; + const headers = await r.readMIMEHeader(); + if (headers === EOF) { + throw new UnexpectedEOFError(); } const np = new PartReader(this, headers); this.currentPart = np; @@ -330,7 +336,8 @@ export class MultipartReader { expectNewPart = true; continue; } - throw new Error(`unexpected line in next(): ${line}`); + let l2 = [...line].map(c => String.fromCharCode(c)).join(""); + throw new Error(`unexpected line in next(): ${l2}`); } } @@ -478,7 +485,7 @@ export class MultipartWriter { await copy(f, file); } - private flush(): Promise { + private flush(): Promise { return this.bufWriter.flush(); } diff --git a/mime/multipart_test.ts b/mime/multipart_test.ts index d7583cf23077..ed033ad9accd 100644 --- a/mime/multipart_test.ts +++ b/mime/multipart_test.ts @@ -7,7 +7,7 @@ import { assertThrows, assertThrowsAsync } from "../testing/asserts.ts"; -import { test } from "../testing/mod.ts"; +import { test, runIfMain } from "../testing/mod.ts"; import { matchAfterPrefix, MultipartReader, @@ -16,6 +16,7 @@ import { } from "./multipart.ts"; import * as path from "../fs/path.ts"; import { FormFile, isFormFile } from "../multipart/formfile.ts"; +import { EOF } from "../io/bufio.ts"; import { StringWriter } from "../io/writers.ts"; const e = new TextEncoder(); @@ -25,71 +26,67 @@ const nlDashBoundary = e.encode("\r\n--" + boundary); test(function multipartScanUntilBoundary1(): void { const data = `--${boundary}`; - const [n, err] = scanUntilBoundary( + const n = scanUntilBoundary( e.encode(data), dashBoundary, nlDashBoundary, 0, - "EOF" + true ); - assertEquals(n, 0); - assertEquals(err, "EOF"); + assertEquals(n, EOF); }); test(function multipartScanUntilBoundary2(): void { const data = `foo\r\n--${boundary}`; - const [n, err] = scanUntilBoundary( + const n = scanUntilBoundary( e.encode(data), dashBoundary, nlDashBoundary, 0, - "EOF" + true ); assertEquals(n, 3); - assertEquals(err, "EOF"); }); -test(function multipartScanUntilBoundary4(): void { - const data = `foo\r\n--`; - const [n, err] = scanUntilBoundary( +test(function multipartScanUntilBoundary3(): void { + const data = `foobar`; + const n = scanUntilBoundary( e.encode(data), dashBoundary, nlDashBoundary, 0, - null + false ); - assertEquals(n, 3); - assertEquals(err, null); + assertEquals(n, data.length); }); -test(function multipartScanUntilBoundary3(): void { - const data = `foobar`; - const [n, err] = scanUntilBoundary( +test(function multipartScanUntilBoundary4(): void { + const data = `foo\r\n--`; + const n = scanUntilBoundary( e.encode(data), dashBoundary, nlDashBoundary, 0, - null + false ); - assertEquals(n, data.length); - assertEquals(err, null); + assertEquals(n, 3); }); test(function multipartMatchAfterPrefix1(): void { const data = `${boundary}\r`; - const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null); + const v = matchAfterPrefix(e.encode(data), e.encode(boundary), false); assertEquals(v, 1); }); test(function multipartMatchAfterPrefix2(): void { const data = `${boundary}hoge`; - const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null); + const v = matchAfterPrefix(e.encode(data), e.encode(boundary), false); assertEquals(v, -1); }); test(function multipartMatchAfterPrefix3(): void { const data = `${boundary}`; - const v = matchAfterPrefix(e.encode(data), e.encode(boundary), null); + const v = matchAfterPrefix(e.encode(data), e.encode(boundary), false); assertEquals(v, 0); }); @@ -211,3 +208,5 @@ test(async function multipartMultipartReader2(): Promise { await remove(file.tempfile); } }); + +runIfMain(import.meta); diff --git a/test.ts b/test.ts index 209b93dd6369..5716cf98c68c 100755 --- a/test.ts +++ b/test.ts @@ -12,7 +12,7 @@ import "./http/test.ts"; import "./io/test.ts"; import "./log/test.ts"; import "./media_types/test.ts"; -//import "./mime/test.ts"; +import "./mime/test.ts"; import "./multipart/test.ts"; import "./prettier/test.ts"; import "./strings/test.ts";