bhttp-js
Version:
A BHTTP (Binary Representation of HTTP Messages) Encoder and Decoder
394 lines (393 loc) • 14.9 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./consts.js", "./errors.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BHttpDecoder = void 0;
const consts = __importStar(require("./consts.js"));
const errors = __importStar(require("./errors.js"));
class InformationalResponse {
status;
headers;
constructor(status) {
this.status = status;
this.headers = new Headers();
}
}
class DecoderContext {
buf;
p = 0;
framingIndicator = 0;
headers;
content;
trailers;
constructor(buf) {
this.buf = buf;
this.headers = new Headers();
this.content = new Uint8Array(0);
this.trailers = new Headers();
}
}
class RequestDecoderContext extends DecoderContext {
method = "";
scheme = "";
authority = "";
path = "";
constructor(buf) {
super(buf);
}
createRequest() {
const input = this.scheme + "://" + this.authority + this.path;
let req;
if (this.method === "GET" || this.method === "HEAD") {
req = new Request(input, {
method: this.method,
});
}
else {
req = new Request(input, {
method: this.method,
body: this.content,
});
}
this.headers.forEach((value, key) => {
req.headers.set(key, value);
});
return req;
}
}
class ResponseDecoderContext extends DecoderContext {
status = 0;
informationalResponses;
constructor(buf) {
super(buf);
this.informationalResponses = new Array(0);
}
createResponse() {
return new Response(this.content, {
status: this.status,
headers: this.headers,
});
}
}
class BHttpDecoder {
_td;
constructor() {
this._td = new TextDecoder();
}
decodeRequest(src) {
if (src instanceof ArrayBuffer) {
src = new Uint8Array(src);
}
const ctx = new RequestDecoderContext(src);
ctx.framingIndicator = this.decodeVli(ctx);
switch (ctx.framingIndicator) {
case 0:
return this.decodeKnownLengthRequest(ctx);
case 2:
return this.decodeIndeterminateLengthRequest(ctx);
default:
throw new errors.InvalidMessageError("Invalid framing indicator.");
}
}
decodeResponse(src) {
if (src instanceof ArrayBuffer) {
src = new Uint8Array(src);
}
const ctx = new ResponseDecoderContext(src);
ctx.framingIndicator = this.decodeVli(ctx);
switch (ctx.framingIndicator) {
case 1:
return this.decodeKnownLengthResponse(ctx);
case 3:
return this.decodeIndeterminateLengthResponse(ctx);
default:
throw new errors.InvalidMessageError("Invalid framing indicator.");
}
}
decodeKnownLengthRequest(ctx) {
this.decodeRequestControlData(ctx);
this.decodeKnownLengthRequestHeaders(ctx);
this.decodeKnownLengthContent(ctx);
this.decodeKnownLengthTrailers(ctx);
this.checkPadding(ctx);
return ctx.createRequest();
}
decodeIndeterminateLengthRequest(ctx) {
this.decodeRequestControlData(ctx);
this.decodeIndeterminateLengthRequestHeaders(ctx);
this.decodeIndeterminateLengthContent(ctx);
this.decodeIndeterminateLengthTrailers(ctx);
this.checkPadding(ctx);
return ctx.createRequest();
}
decodeKnownLengthResponse(ctx) {
this.decodeKnownLengthInformationalResponsesAndHeaders(ctx);
this.decodeKnownLengthContent(ctx);
this.decodeKnownLengthTrailers(ctx);
this.checkPadding(ctx);
return ctx.createResponse();
}
decodeIndeterminateLengthResponse(ctx) {
this.decodeIndeterminateLengthInformationalResponsesAndHeaders(ctx);
this.decodeIndeterminateLengthContent(ctx);
this.decodeIndeterminateLengthTrailers(ctx);
this.checkPadding(ctx);
return ctx.createResponse();
}
decodeRequestControlData(ctx) {
ctx.method = this.decodeVliAndValue(ctx);
ctx.scheme = this.decodeVliAndValue(ctx);
ctx.authority = this.decodeVliAndValue(ctx);
ctx.path = this.decodeVliAndValue(ctx);
return;
}
decodeKnownLengthInformationalResponsesAndHeaders(ctx) {
let status = this.decodeVli(ctx);
while (status >= 100 && status < 200) {
this.decodeKnownLengthInformationalResponse(ctx, status);
status = this.decodeVli(ctx);
}
if (status < 100 && status >= 600) {
throw new errors.InvalidMessageError("Invalid status code.");
}
ctx.status = status;
this.decodeKnownLengthResponseHeaders(ctx);
return;
}
decodeIndeterminateLengthInformationalResponsesAndHeaders(ctx) {
let status = this.decodeVli(ctx);
while (status >= 100 && status < 200) {
this.decodeIndeterminateLengthInformationalResponse(ctx, status);
status = this.decodeVli(ctx);
}
if (status < 100 && status >= 600) {
throw new errors.InvalidMessageError("Invalid status code.");
}
ctx.status = status;
this.decodeIndeterminateLengthResponseHeaders(ctx);
return;
}
decodeKnownLengthInformationalResponse(ctx, status) {
const ir = new InformationalResponse(status);
const len = this.decodeVli(ctx);
let name = "";
let value = "";
const base = ctx.p;
while (ctx.p < base + len) {
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
ir.headers.set(name, value);
}
ctx.informationalResponses.push(ir);
return;
}
decodeIndeterminateLengthInformationalResponse(ctx, status) {
const ir = new InformationalResponse(status);
let name = "";
let value = "";
let terminator = this.decodeVli(ctx);
while (terminator !== 0) {
ctx.p--;
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
ir.headers.set(name, value);
terminator = this.decodeVli(ctx);
}
ctx.informationalResponses.push(ir);
return;
}
decodeKnownLengthRequestHeaders(ctx) {
let name = "";
let value = "";
const len = this.decodeVli(ctx);
const base = ctx.p;
while (ctx.p < base + len) {
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
if (name.localeCompare("host", undefined, { sensitivity: "accent" }) ===
0 && ctx.authority === "") {
ctx.authority = value;
}
ctx.headers.set(name, value);
}
return;
}
decodeKnownLengthResponseHeaders(ctx) {
let name = "";
let value = "";
const base = ctx.p;
const len = this.decodeVli(ctx);
while (ctx.p < base + len) {
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
ctx.headers.set(name, value);
}
return;
}
decodeIndeterminateLengthRequestHeaders(ctx) {
let name = "";
let value = "";
let terminator = this.decodeVli(ctx);
while (terminator !== 0) {
ctx.p--;
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
if (name.localeCompare("host", undefined, { sensitivity: "accent" }) ===
0 && ctx.authority === "") {
ctx.authority = value;
}
ctx.headers.set(name, value);
terminator = this.decodeVli(ctx);
}
return;
}
decodeIndeterminateLengthResponseHeaders(ctx) {
let name = "";
let value = "";
let terminator = this.decodeVli(ctx);
while (terminator !== 0) {
ctx.p--;
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
ctx.headers.set(name, value);
terminator = this.decodeVli(ctx);
}
return;
}
decodeKnownLengthContent(ctx) {
const len = this.decodeVli(ctx);
// ctx.content = new Uint8Array(ctx.buf, ctx.p, len);
ctx.content = ctx.buf.slice(ctx.p, ctx.p + len);
ctx.p += len;
return;
}
decodeIndeterminateLengthContent(ctx) {
let len = 0;
const p = ctx.p;
let terminator = this.decodeVli(ctx);
while (terminator !== 0) {
len += terminator;
ctx.p += terminator;
terminator = this.decodeVli(ctx);
}
if (len === 0) {
return;
}
ctx.p = p;
ctx.content = new Uint8Array(len);
len = 0;
terminator = this.decodeVli(ctx);
while (terminator !== 0) {
// ctx.content.set(new Uint8Array(ctx.buf, ctx.p, terminator), len);
ctx.content.set(ctx.buf.slice(ctx.p, ctx.p + terminator), len);
len += terminator;
ctx.p += terminator;
terminator = this.decodeVli(ctx);
}
return;
}
decodeKnownLengthTrailers(ctx) {
const len = this.decodeVli(ctx);
let name = "";
let value = "";
const base = ctx.p;
while (ctx.p < base + len) {
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
ctx.trailers.set(name, value);
}
return;
}
decodeIndeterminateLengthTrailers(ctx) {
let name = "";
let value = "";
let terminator = this.decodeVli(ctx);
while (terminator != 0) {
ctx.p--;
name = this.decodeVliAndValue(ctx);
value = this.decodeVliAndValue(ctx);
ctx.trailers.set(name, value);
terminator = this.decodeVli(ctx);
}
return;
}
checkPadding(ctx) {
while (ctx.p < ctx.buf.byteLength) {
if (ctx.buf[ctx.p++] !== 0x00) {
throw new errors.InvalidMessageError("Invalid padding data.");
}
}
return;
}
decodeVliAndValue(ctx) {
const len = this.decodeVli(ctx);
// const res = this._td.decode(new Uint8Array(ctx.buf, ctx.p, len));
const res = this._td.decode(ctx.buf.slice(ctx.p, ctx.p + len));
ctx.p += len;
return res;
}
decodeVli(ctx) {
let res = 0;
switch (ctx.buf[ctx.p] & consts.VLI_MASK_VALUE) {
case consts.VLI_LEN_1:
return ctx.buf[ctx.p++] & consts.VLI_MASK_HEADER;
case consts.VLI_LEN_2:
res = (ctx.buf[ctx.p++] & consts.VLI_MASK_HEADER) << 8;
res += ctx.buf[ctx.p++];
return res;
case consts.VLI_LEN_4:
res = (ctx.buf[ctx.p++] & consts.VLI_MASK_HEADER) << 24;
res += ctx.buf[ctx.p++] << 16;
res += ctx.buf[ctx.p++] << 8;
res += ctx.buf[ctx.p++];
return res;
default:
// consts.VLI_LEN_8
// res = (ctx.buf[ctx.p++] & consts.VLI_MASK_HEADER) << 56;
res = 0;
if (ctx.buf[++ctx.p] > 15) {
throw new errors.NotSupportedError("Over MAX_SAFE_INTEGER-length value is not supported.");
}
res += ctx.buf[ctx.p++] << 48;
res += ctx.buf[ctx.p++] << 40;
res += ctx.buf[ctx.p++] << 32;
res += ctx.buf[ctx.p++] << 24;
res += ctx.buf[ctx.p++] << 16;
res += ctx.buf[ctx.p++] << 8;
res += ctx.buf[ctx.p++];
return res;
}
}
}
exports.BHttpDecoder = BHttpDecoder;
});