From fb0b99408b1ce0c8061d654e9dae3fd8221efa6f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 8 Nov 2018 12:58:43 -0500 Subject: [PATCH] Add tests for TextProtoReader.readMIMEHeader() Original: https://github.com/denoland/deno_std/commit/36edda18ab75ea8287088478d46e89e5e8d6be0f --- headers.ts | 34 +++++++++++++++++++ textproto.ts | 25 +++++++------- textproto_test.ts | 84 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 headers.ts create mode 100644 textproto_test.ts diff --git a/headers.ts b/headers.ts new file mode 100644 index 0000000000..9fe2181950 --- /dev/null +++ b/headers.ts @@ -0,0 +1,34 @@ +// Fake headers to work around +// https://github.com/denoland/deno/issues/1173 + +function normalize(name: string, value?: string): [string, string] { + name = String(name).toLowerCase(); + value = String(value).trim(); + return [name, value]; +} + +export class Headers { + private map = new Map(); + + get(name: string): string | null { + let [name_] = normalize(name); + return this.map.get(name_); + } + + append(name: string, value: string): void { + [name, value] = normalize(name, value); + this.map.set(name, value); + } + + toString(): string { + let out = ""; + this.map.forEach((v, k) => { + out += `${k}: ${v}\n`; + }); + return out; + } + + [Symbol.iterator](): IterableIterator<[string, string]> { + return this.map[Symbol.iterator](); + } +} diff --git a/textproto.ts b/textproto.ts index b4336c90de..61ca45a8ad 100644 --- a/textproto.ts +++ b/textproto.ts @@ -5,6 +5,7 @@ import { BufReader, BufState } from "./bufio.ts"; import { charCode } from "./util.ts"; +import { Headers } from "./headers.ts"; const asciiDecoder = new TextDecoder("ascii"); function str(buf: Uint8Array): string { @@ -53,37 +54,38 @@ export class TextProtoReader { * "Long-Key": {"Even Longer Value"}, * } */ - /* - async readMIMEHeader(): Promise { + async readMIMEHeader(): Promise<[Headers, BufState]> { let m = new Headers(); let line: Uint8Array; // The first line cannot start with a leading space. let [buf, err] = await this.r.peek(1); - if (buf[0] == charCode(' ') || buf[0] == charCode('\t')) { + if (buf[0] == charCode(" ") || buf[0] == charCode("\t")) { [line, err] = await this.readLineSlice(); } [buf, err] = await this.r.peek(1); - if (err == null && (buf[0] == charCode(' ') || buf[0] == charCode('\t'))) { - throw new ProtocolError(`malformed MIME header initial line: ${str(line)}`) + if (err == null && (buf[0] == charCode(" ") || buf[0] == charCode("\t"))) { + throw new ProtocolError( + `malformed MIME header initial line: ${str(line)}` + ); } while (true) { let [kv, err] = await this.readLineSlice(); // readContinuedLineSlice if (kv.byteLength == 0) { - return m; + return [m, err]; } // Key ends at first colon; should not have trailing spaces // but they appear in the wild, violating specs, so we remove // them if present. - let i = kv.indexOf(charCode(':')); + let i = kv.indexOf(charCode(":")); if (i < 0) { throw new ProtocolError(`malformed MIME header line: ${str(kv)}`); } let endKey = i; - while (endKey > 0 && kv[endKey - 1] == charCode(' ')) { + while (endKey > 0 && kv[endKey - 1] == charCode(" ")) { endKey--; } @@ -99,8 +101,10 @@ export class TextProtoReader { // Skip initial spaces in value. i++; // skip colon - while (i < kv.byteLength && - (kv[i] == charCode(' ') || kv[i] == charCode('\t'))) { + while ( + i < kv.byteLength && + (kv[i] == charCode(" ") || kv[i] == charCode("\t")) + ) { i++; } let value = str(kv.subarray(i)); @@ -112,7 +116,6 @@ export class TextProtoReader { } } } - */ async readLineSlice(): Promise<[Uint8Array, BufState]> { // this.closeDot(); diff --git a/textproto_test.ts b/textproto_test.ts new file mode 100644 index 0000000000..32311a4688 --- /dev/null +++ b/textproto_test.ts @@ -0,0 +1,84 @@ +// Based on https://github.com/golang/go/blob/891682/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 { BufReader } from "./bufio.ts"; +import { TextProtoReader } from "./textproto.ts"; +import { stringsReader } from "./buffer.ts"; +import { + test, + assert, + assertEqual +} from "https://deno.land/x/testing/testing.ts"; + +function reader(s: string): TextProtoReader { + return new TextProtoReader(new BufReader(stringsReader(s))); +} + +test(async function textprotoReader() { + let r = reader("line1\nline2\n"); + let [s, err] = await r.readLine(); + assertEqual(s, "line1"); + assert(err == null); + + [s, err] = await r.readLine(); + assertEqual(s, "line2"); + assert(err == null); + + [s, err] = await r.readLine(); + assertEqual(s, ""); + assert(err == "EOF"); +}); + +/* +test(async function textprotoReadMIMEHeader() { + let r = reader("my-key: Value 1 \r\nLong-key: Even \n Longer Value\r\nmy-Key: Value 2\r\n\n"); + let [m, err] = await r.readMIMEHeader(); + + console.log("Got headers", m.toString()); + want := MIMEHeader{ + "My-Key": {"Value 1", "Value 2"}, + "Long-Key": {"Even Longer Value"}, + } + if !reflect.DeepEqual(m, want) || err != nil { + t.Fatalf("ReadMIMEHeader: %v, %v; want %v", m, err, want) + } +}); +*/ + +test(async function textprotoReadMIMEHeaderSingle() { + let r = reader("Foo: bar\n\n"); + let [m, err] = await r.readMIMEHeader(); + assertEqual(m.get("Foo"), "bar"); + assert(!err); +}); + +// Test that we read slightly-bogus MIME headers seen in the wild, +// with spaces before colons, and spaces in keys. +test(async function textprotoReadMIMEHeaderNonCompliant() { + // Invalid HTTP response header as sent by an Axis security + // camera: (this is handled by IE, Firefox, Chrome, curl, etc.) + let r = reader( + "Foo: bar\r\n" + + "Content-Language: en\r\n" + + "SID : 0\r\n" + + "Audio Mode : None\r\n" + + "Privilege : 127\r\n\r\n" + ); + let [m, err] = await r.readMIMEHeader(); + console.log(m.toString()); + assert(!err); + /* + let want = MIMEHeader{ + "Foo": {"bar"}, + "Content-Language": {"en"}, + "Sid": {"0"}, + "Audio Mode": {"None"}, + "Privilege": {"127"}, + } + if !reflect.DeepEqual(m, want) || err != nil { + t.Fatalf("ReadMIMEHeader =\n%v, %v; want:\n%v", m, err, want) + } + */ +});