swc/crates/swc_bundler/tests/.cache/untrusted/7edad670b878b9dce2251724b67c9c784e4f9c9c.ts
2021-11-09 20:42:49 +09:00

235 lines
6.0 KiB
TypeScript

// Loaded from https://raw.githubusercontent.com/nats-io/nats.deno/v1.0.0-rc4/nats-base-client/headers.ts
/*
* Copyright 2020-2021 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Heavily inspired by Golang's https://golang.org/src/net/http/header.go
import { ErrorCode, NatsError } from "./error.ts";
import { TD, TE } from "./encoders.ts";
export interface MsgHdrs extends Iterable<[string, string[]]> {
hasError: boolean;
status: string;
code?: number;
get(k: string): string;
set(k: string, v: string): void;
append(k: string, v: string): void;
has(k: string): boolean;
values(k: string): string[];
delete(k: string): void;
}
export function headers(): MsgHdrs {
return new MsgHdrsImpl();
}
const HEADER = "NATS/1.0";
export class MsgHdrsImpl implements MsgHdrs {
code?: number;
headers: Map<string, string[]>;
description: string;
constructor() {
this.headers = new Map();
this.description = "";
}
[Symbol.iterator]() {
return this.headers.entries();
}
size(): number {
let count = 0;
for (const [_, v] of this.headers.entries()) {
count += v.length;
}
return count;
}
equals(mh: MsgHdrsImpl) {
if (
mh && this.headers.size === mh.headers.size &&
this.code === mh.code
) {
for (const [k, v] of this.headers) {
const a = mh.values(k);
if (v.length !== a.length) {
return false;
}
const vv = [...v].sort();
const aa = [...a].sort();
for (let i = 0; i < vv.length; i++) {
if (vv[i] !== aa[i]) {
return false;
}
}
return true;
}
}
return false;
}
static decode(a: Uint8Array): MsgHdrsImpl {
const mh = new MsgHdrsImpl();
const s = TD.decode(a);
const lines = s.split("\r\n");
const h = lines[0];
if (h !== HEADER) {
let str = h.replace(HEADER, "");
mh.code = parseInt(str, 10);
const scode = mh.code.toString();
mh.set("Status", scode);
str = str.replace(scode, "");
mh.description = str.trim();
if (mh.description) {
mh.set("Description", mh.description);
}
} else {
lines.slice(1).map((s) => {
if (s) {
const idx = s.indexOf(": ");
const k = s.slice(0, idx);
const v = s.slice(idx + 2);
mh.append(k, v);
}
});
}
return mh;
}
toString(): string {
if (this.headers.size === 0) {
return "";
}
let s = HEADER;
for (const [k, v] of this.headers) {
for (let i = 0; i < v.length; i++) {
s = `${s}\r\n${k}: ${v[i]}`;
}
}
return `${s}\r\n\r\n`;
}
encode(): Uint8Array {
return TE.encode(this.toString());
}
// https://www.ietf.org/rfc/rfc822.txt
// 3.1.2. STRUCTURE OF HEADER FIELDS
//
// Once a field has been unfolded, it may be viewed as being com-
// posed of a field-name followed by a colon (":"), followed by a
// field-body, and terminated by a carriage-return/line-feed.
// The field-name must be composed of printable ASCII characters
// (i.e., characters that have values between 33. and 126.,
// decimal, except colon). The field-body may be composed of any
// ASCII characters, except CR or LF. (While CR and/or LF may be
// present in the actual text, they are removed by the action of
// unfolding the field.)
static canonicalMIMEHeaderKey(k: string): string {
const a = 97;
const A = 65;
const Z = 90;
const z = 122;
const dash = 45;
const colon = 58;
const start = 33;
const end = 126;
const toLower = a - A;
let upper = true;
const buf: number[] = new Array(k.length);
for (let i = 0; i < k.length; i++) {
let c = k.charCodeAt(i);
if (c === colon || c < start || c > end) {
throw new NatsError(
`'${k[i]}' is not a valid character for a header key`,
ErrorCode.BAD_HEADER,
);
}
if (upper && a <= c && c <= z) {
c -= toLower;
} else if (!upper && A <= c && c <= Z) {
c += toLower;
}
buf[i] = c;
upper = c == dash;
}
return String.fromCharCode(...buf);
}
static validHeaderValue(k: string): string {
const inv = /[\r\n]/;
if (inv.test(k)) {
throw new NatsError(
"invalid header value - \\r and \\n are not allowed.",
ErrorCode.BAD_HEADER,
);
}
return k.trim();
}
get(k: string): string {
const key = MsgHdrsImpl.canonicalMIMEHeaderKey(k);
const a = this.headers.get(key);
return a ? a[0] : "";
}
has(k: string): boolean {
return this.get(k) !== "";
}
set(k: string, v: string): void {
const key = MsgHdrsImpl.canonicalMIMEHeaderKey(k);
const value = MsgHdrsImpl.validHeaderValue(v);
this.headers.set(key, [value]);
}
append(k: string, v: string): void {
const key = MsgHdrsImpl.canonicalMIMEHeaderKey(k);
const value = MsgHdrsImpl.validHeaderValue(v);
let a = this.headers.get(key);
if (!a) {
a = [];
this.headers.set(key, a);
}
a.push(value);
}
values(k: string): string[] {
const key = MsgHdrsImpl.canonicalMIMEHeaderKey(k);
return this.headers.get(key) || [];
}
delete(k: string): void {
const key = MsgHdrsImpl.canonicalMIMEHeaderKey(k);
this.headers.delete(key);
}
get hasError() {
if (this.code) {
return this.code > 0 && (this.code < 200 || this.code >= 300);
}
return false;
}
get status(): string {
return `${this.code} ${this.description}`.trim();
}
}