diff options
Diffstat (limited to 'std/textproto')
-rw-r--r-- | std/textproto/mod.ts | 163 | ||||
-rw-r--r-- | std/textproto/test.ts | 191 |
2 files changed, 0 insertions, 354 deletions
diff --git a/std/textproto/mod.ts b/std/textproto/mod.ts deleted file mode 100644 index 816275662..000000000 --- a/std/textproto/mod.ts +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -// Based on https://github.com/golang/go/tree/master/src/net/textproto -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -import type { BufReader } from "../io/bufio.ts"; -import { concat } from "../bytes/mod.ts"; -import { decode } from "../encoding/utf8.ts"; - -// FROM https://github.com/denoland/deno/blob/b34628a26ab0187a827aa4ebe256e23178e25d39/cli/js/web/headers.ts#L9 -const invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/g; - -function str(buf: Uint8Array | null | undefined): string { - if (buf == null) { - return ""; - } else { - return decode(buf); - } -} - -function charCode(s: string): number { - return s.charCodeAt(0); -} - -export class TextProtoReader { - constructor(readonly r: BufReader) {} - - /** readLine() reads a single line from the TextProtoReader, - * eliding the final \n or \r\n from the returned string. - */ - async readLine(): Promise<string | null> { - const s = await this.readLineSlice(); - if (s === null) return null; - return str(s); - } - - /** ReadMIMEHeader reads a MIME-style header from r. - * The header is a sequence of possibly continued Key: Value lines - * ending in a blank line. - * The returned map m maps CanonicalMIMEHeaderKey(key) to a - * sequence of values in the same order encountered in the input. - * - * For example, consider this input: - * - * My-Key: Value 1 - * Long-Key: Even - * Longer Value - * My-Key: Value 2 - * - * Given that input, ReadMIMEHeader returns the map: - * - * map[string][]string{ - * "My-Key": {"Value 1", "Value 2"}, - * "Long-Key": {"Even Longer Value"}, - * } - */ - async readMIMEHeader(): Promise<Headers | null> { - const m = new Headers(); - let line: Uint8Array | undefined; - - // The first line cannot start with a leading space. - let buf = await this.r.peek(1); - if (buf === null) { - return null; - } else if (buf[0] == charCode(" ") || buf[0] == charCode("\t")) { - line = (await this.readLineSlice()) as Uint8Array; - } - - buf = await this.r.peek(1); - if (buf === null) { - throw new Deno.errors.UnexpectedEof(); - } else if (buf[0] == charCode(" ") || buf[0] == charCode("\t")) { - throw new Deno.errors.InvalidData( - `malformed MIME header initial line: ${str(line)}`, - ); - } - - while (true) { - const kv = await this.readLineSlice(); // readContinuedLineSlice - if (kv === null) throw new Deno.errors.UnexpectedEof(); - if (kv.byteLength === 0) return m; - - // Key ends at first colon - let i = kv.indexOf(charCode(":")); - if (i < 0) { - throw new Deno.errors.InvalidData( - `malformed MIME header line: ${str(kv)}`, - ); - } - - //let key = canonicalMIMEHeaderKey(kv.subarray(0, endKey)); - const key = str(kv.subarray(0, i)); - - // As per RFC 7230 field-name is a token, - // tokens consist of one or more chars. - // We could throw `Deno.errors.InvalidData` here, - // but better to be liberal in what we - // accept, so if we get an empty key, skip it. - if (key == "") { - continue; - } - - // Skip initial spaces in value. - i++; // skip colon - while ( - i < kv.byteLength && - (kv[i] == charCode(" ") || kv[i] == charCode("\t")) - ) { - i++; - } - const value = str(kv.subarray(i)).replace( - invalidHeaderCharRegex, - encodeURI, - ); - - // In case of invalid header we swallow the error - // example: "Audio Mode" => invalid due to space in the key - try { - m.append(key, value); - } catch { - // Pass - } - } - } - - async readLineSlice(): Promise<Uint8Array | null> { - // this.closeDot(); - let line: Uint8Array | undefined; - while (true) { - const r = await this.r.readLine(); - if (r === null) return null; - const { line: l, more } = r; - - // Avoid the copy if the first call produced a full line. - if (!line && !more) { - // TODO(ry): - // This skipSpace() is definitely misplaced, but I don't know where it - // comes from nor how to fix it. - if (this.skipSpace(l) === 0) { - return new Uint8Array(0); - } - return l; - } - line = line ? concat(line, l) : l; - if (!more) { - break; - } - } - return line; - } - - skipSpace(l: Uint8Array): number { - let n = 0; - for (let i = 0; i < l.length; i++) { - if (l[i] === charCode(" ") || l[i] === charCode("\t")) { - continue; - } - n++; - } - return n; - } -} diff --git a/std/textproto/test.ts b/std/textproto/test.ts deleted file mode 100644 index ca7a17ce4..000000000 --- a/std/textproto/test.ts +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -// Based on https://github.com/golang/go/blob/master/src/net/textproto/reader_test.go -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -import { BufReader } from "../io/bufio.ts"; -import { TextProtoReader } from "./mod.ts"; -import { StringReader } from "../io/readers.ts"; -import { assert, assertEquals, assertThrows } from "../testing/asserts.ts"; - -function reader(s: string): TextProtoReader { - return new TextProtoReader(new BufReader(new StringReader(s))); -} - -Deno.test({ - ignore: true, - name: "[textproto] Reader : DotBytes", - fn(): Promise<void> { - const _input = - "dotlines\r\n.foo\r\n..bar\n...baz\nquux\r\n\r\n.\r\nanot.her\r\n"; - return Promise.resolve(); - }, -}); - -Deno.test("[textproto] ReadEmpty", async () => { - const r = reader(""); - const m = await r.readMIMEHeader(); - assertEquals(m, null); -}); - -Deno.test("[textproto] Reader", async () => { - const r = reader("line1\nline2\n"); - let s = await r.readLine(); - assertEquals(s, "line1"); - - s = await r.readLine(); - assertEquals(s, "line2"); - - s = await r.readLine(); - assert(s === null); -}); - -Deno.test({ - name: "[textproto] Reader : MIME Header", - async fn(): Promise<void> { - const input = - "my-key: Value 1 \r\nLong-key: Even Longer Value\r\nmy-Key: " + - "Value 2\r\n\n"; - const r = reader(input); - const m = await r.readMIMEHeader(); - assert(m !== null); - assertEquals(m.get("My-Key"), "Value 1, Value 2"); - assertEquals(m.get("Long-key"), "Even Longer Value"); - }, -}); - -Deno.test({ - name: "[textproto] Reader : MIME Header Single", - async fn(): Promise<void> { - const input = "Foo: bar\n\n"; - const r = reader(input); - const m = await r.readMIMEHeader(); - assert(m !== null); - assertEquals(m.get("Foo"), "bar"); - }, -}); - -Deno.test({ - name: "[textproto] Reader : MIME Header No Key", - async fn(): Promise<void> { - const input = ": bar\ntest-1: 1\n\n"; - const r = reader(input); - const m = await r.readMIMEHeader(); - assert(m !== null); - assertEquals(m.get("Test-1"), "1"); - }, -}); - -Deno.test({ - name: "[textproto] Reader : Large MIME Header", - async fn(): Promise<void> { - const data: string[] = []; - // Go test is 16*1024. But seems it can't handle more - for (let i = 0; i < 1024; i++) { - data.push("x"); - } - const sdata = data.join(""); - const r = reader(`Cookie: ${sdata}\r\n\r\n`); - const m = await r.readMIMEHeader(); - assert(m !== null); - assertEquals(m.get("Cookie"), sdata); - }, -}); - -// Test that we don't read MIME headers seen in the wild, -// with spaces before colons, and spaces in keys. -Deno.test({ - name: "[textproto] Reader : MIME Header Non compliant", - async fn(): Promise<void> { - const input = "Foo: bar\r\n" + - "Content-Language: en\r\n" + - "SID : 0\r\n" + - "Audio Mode : None\r\n" + - "Privilege : 127\r\n\r\n"; - const r = reader(input); - const m = await r.readMIMEHeader(); - assert(m !== null); - assertEquals(m.get("Foo"), "bar"); - assertEquals(m.get("Content-Language"), "en"); - // Make sure we drop headers with trailing whitespace - assertEquals(m.get("SID"), null); - assertEquals(m.get("Privilege"), null); - // Not legal http header - assertThrows((): void => { - assertEquals(m.get("Audio Mode"), "None"); - }); - }, -}); - -Deno.test({ - name: "[textproto] Reader : MIME Header Malformed", - async fn(): Promise<void> { - const input = [ - "No colon first line\r\nFoo: foo\r\n\r\n", - " No colon first line with leading space\r\nFoo: foo\r\n\r\n", - "\tNo colon first line with leading tab\r\nFoo: foo\r\n\r\n", - " First: line with leading space\r\nFoo: foo\r\n\r\n", - "\tFirst: line with leading tab\r\nFoo: foo\r\n\r\n", - "Foo: foo\r\nNo colon second line\r\n\r\n", - ]; - const r = reader(input.join("")); - - let err; - try { - await r.readMIMEHeader(); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.InvalidData); - }, -}); - -Deno.test({ - name: "[textproto] Reader : MIME Header Trim Continued", - async fn(): Promise<void> { - const input = "a:\n" + - " 0 \r\n" + - "b:1 \t\r\n" + - "c: 2\r\n" + - " 3\t\n" + - " \t 4 \r\n\n"; - const r = reader(input); - let err; - try { - await r.readMIMEHeader(); - } catch (e) { - err = e; - } - assert(err instanceof Deno.errors.InvalidData); - }, -}); - -Deno.test({ - name: "[textproto] #409 issue : multipart form boundary", - async fn(): Promise<void> { - const input = [ - "Accept: */*\r\n", - 'Content-Disposition: form-data; name="test"\r\n', - " \r\n", - "------WebKitFormBoundaryimeZ2Le9LjohiUiG--\r\n\n", - ]; - const r = reader(input.join("")); - const m = await r.readMIMEHeader(); - assert(m !== null); - assertEquals(m.get("Accept"), "*/*"); - assertEquals(m.get("Content-Disposition"), 'form-data; name="test"'); - }, -}); - -Deno.test({ - name: "[textproto] #4521 issue", - async fn() { - const input = "abcdefghijklmnopqrstuvwxyz"; - const bufSize = 25; - const tp = new TextProtoReader( - new BufReader(new StringReader(input), bufSize), - ); - const line = await tp.readLine(); - assertEquals(line, input); - }, -}); |