diff options
Diffstat (limited to 'multipart/multipart.ts')
| -rw-r--r-- | multipart/multipart.ts | 380 |
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"); } |
