Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
piscisaureus committed May 28, 2019
1 parent d2e3819 commit 9605729
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 93 deletions.
145 changes: 76 additions & 69 deletions mime/multipart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand All @@ -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;
Expand All @@ -52,100 +54,101 @@ 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;

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<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) {
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 {}
Expand Down Expand Up @@ -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);
}

Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -291,6 +295,7 @@ export class MultipartReader {
private partsRead: number;

private async nextPart(): Promise<PartReader> {
console.error("PART\n");
if (this.currentPart) {
this.currentPart.close();
}
Expand All @@ -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;
Expand All @@ -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}`);
}
}

Expand Down Expand Up @@ -478,7 +485,7 @@ export class MultipartWriter {
await copy(f, file);
}

private flush(): Promise<BufState> {
private flush(): Promise<void> {
return this.bufWriter.flush();
}

Expand Down
45 changes: 22 additions & 23 deletions mime/multipart_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
Expand All @@ -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);
});

Expand Down Expand Up @@ -211,3 +208,5 @@ test(async function multipartMultipartReader2(): Promise<void> {
await remove(file.tempfile);
}
});

runIfMain(import.meta);
2 changes: 1 addition & 1 deletion test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down

0 comments on commit 9605729

Please sign in to comment.