summaryrefslogtreecommitdiff
path: root/multipart/multipart.ts
diff options
context:
space:
mode:
Diffstat (limited to 'multipart/multipart.ts')
-rw-r--r--multipart/multipart.ts380
1 files changed, 193 insertions, 187 deletions
diff --git a/multipart/multipart.ts b/multipart/multipart.ts
index 70874ed85..68785c7c6 100644
--- a/multipart/multipart.ts
+++ b/multipart/multipart.ts
@@ -17,7 +17,7 @@ import { TextProtoReader } from "../textproto/mod.ts";
import { encoder } from "../strings/strings.ts";
import * as path from "../fs/path.ts";
-function randomBoundary() {
+function randomBoundary(): string {
let boundary = "--------------------------";
for (let i = 0; i < 24; i++) {
boundary += Math.floor(Math.random() * 10).toString(16);
@@ -25,6 +25,185 @@ function randomBoundary() {
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 => v.trim())
+ .map(kv => {
+ 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");
@@ -125,7 +304,7 @@ export class MultipartReader {
break;
}
if (state) {
- throw new Error("aa" + state.toString());
+ throw new Error(`aa${state.toString()}`);
}
if (this.isBoundaryDelimiterLine(line)) {
this.partsRead++;
@@ -155,7 +334,7 @@ export class MultipartReader {
}
}
- private isFinalBoundary(line: Uint8Array) {
+ private isFinalBoundary(line: Uint8Array): boolean {
if (!bytesHasPrefix(line, this.dashBoundaryDash)) {
return false;
}
@@ -163,7 +342,7 @@ export class MultipartReader {
return rest.length === 0 || bytesEqual(skipLWSPChar(rest), this.newLine);
}
- private isBoundaryDelimiterLine(line: Uint8Array) {
+ private isBoundaryDelimiterLine(line: Uint8Array): boolean {
if (!bytesHasPrefix(line, this.dashBoundary)) {
return false;
}
@@ -172,183 +351,6 @@ export class MultipartReader {
}
}
-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);
-}
-
-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) {
- 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() {
- 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 => v.trim())
- .map(kv => {
- 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 "";
- }
-}
-
-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:
- 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];
-}
-
-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;
-}
-
class PartWriter implements Writer {
closed = false;
private readonly partHeader: string;
@@ -389,9 +391,9 @@ class PartWriter implements Writer {
}
}
-function checkBoundary(b: string) {
+function checkBoundary(b: string): string {
if (b.length < 1 || b.length > 70) {
- throw new Error("invalid boundary length: " + b.length);
+ throw new Error(`invalid boundary length: ${b.length}`);
}
const end = b.length - 1;
for (let i = 0; i < end; i++) {
@@ -407,7 +409,7 @@ function checkBoundary(b: string) {
export class MultipartWriter {
private readonly _boundary: string;
- get boundary() {
+ get boundary(): string {
return this._boundary;
}
@@ -462,12 +464,16 @@ export class MultipartWriter {
return this.createPart(h);
}
- async writeField(field: string, value: string) {
+ 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) {
+ async writeFile(
+ field: string,
+ filename: string,
+ file: Reader
+ ): Promise<void> {
const f = await this.createFormFile(field, filename);
await copy(f, file);
}
@@ -477,7 +483,7 @@ export class MultipartWriter {
}
/** Close writer. No additional data can be writen to stream */
- async close() {
+ async close(): Promise<void> {
if (this.isClosed) {
throw new Error("multipart: writer is closed");
}