import { primordials } from "ext:core/mod.js";
const {
ArrayPrototypePop,
ArrayPrototypePush,
ArrayPrototypeSlice,
MathMin,
SafeArrayIterator,
RegExpPrototypeTest,
SafeRegExp,
StringPrototypeCharCodeAt,
Symbol,
Uint8Array,
} = primordials;
import { setImmediate } from "node:timers";
import {
allMethods,
HTTPParser,
methods,
} from "ext:deno_node/internal_binding/http_parser.ts";
import { IncomingMessage, readStart, readStop } from "node:_http_incoming";
const kIncomingMessage = Symbol("IncomingMessage");
const kSkipPendingData = Symbol("SkipPendingData");
const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
const kOnHeaders = HTTPParser.kOnHeaders | 0;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const kOnBody = HTTPParser.kOnBody | 0;
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
const kOnExecute = HTTPParser.kOnExecute | 0;
const kOnTimeout = HTTPParser.kOnTimeout | 0;
const MAX_HEADER_PAIRS = 2000;
class FreeList {
constructor(name, max, ctor) {
this.name = name;
this.max = max;
this.ctor = ctor;
this.list = [];
}
alloc() {
return this.list.length > 0 ? ArrayPrototypePop(this.list) : this.ctor();
}
free(obj) {
if (this.list.length < this.max) {
ArrayPrototypePush(this.list, obj);
return true;
}
return false;
}
}
function parserOnHeaders(headers, url) {
const capacity = this.maxHeaderPairs - this._headers.length;
if (this.maxHeaderPairs <= 0 || capacity >= headers.length) {
ArrayPrototypePush(this._headers, ...new SafeArrayIterator(headers));
} else if (capacity > 0) {
ArrayPrototypePush(
this._headers,
...new SafeArrayIterator(ArrayPrototypeSlice(headers, 0, capacity)),
);
}
this._url += url;
}
const HTTP_VERSION_1_1 = "1.1";
function parserOnHeadersComplete(
versionMajor,
versionMinor,
headers,
method,
url,
statusCode,
statusMessage,
upgrade,
shouldKeepAlive,
) {
const parser = this;
const { socket } = parser;
if (headers === undefined) {
headers = parser._headers;
parser._headers = [];
}
if (url === undefined) {
url = parser._url;
parser._url = "";
}
const ParserIncomingMessage = (socket?.server?.[kIncomingMessage]) ||
IncomingMessage;
const incoming = parser.incoming = new ParserIncomingMessage(socket);
incoming.httpVersionMajor = versionMajor;
incoming.httpVersionMinor = versionMinor;
incoming.httpVersion = versionMajor === 1 && versionMinor === 1
? HTTP_VERSION_1_1
: `${versionMajor}.${versionMinor}`;
incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders ||
parser.joinDuplicateHeaders;
incoming.url = url;
incoming.upgrade = upgrade;
let n = headers.length;
if (parser.maxHeaderPairs > 0) {
n = MathMin(n, parser.maxHeaderPairs);
}
incoming._addHeaderLines(headers, n);
if (typeof method === "number") {
incoming.method = allMethods[method];
} else {
incoming.statusCode = statusCode;
incoming.statusMessage = statusMessage;
}
return parser.onIncoming(incoming, shouldKeepAlive);
}
function parserOnBody(b) {
const stream = this.incoming;
if (stream === null || stream[kSkipPendingData]) {
return;
}
if (!stream._dumped) {
stream._consuming = true;
const ret = stream.push(b);
if (!ret) {
readStop(this.socket);
}
}
}
function parserOnMessageComplete() {
const parser = this;
const stream = parser.incoming;
if (stream !== null && !stream[kSkipPendingData]) {
stream.complete = true;
const headers = parser._headers;
if (headers.length) {
stream._addHeaderLines(headers, headers.length);
parser._headers = [];
parser._url = "";
}
stream.push(null);
}
readStart(parser.socket);
}
const parsers = new FreeList("parsers", 1000, function parsersCb() {
const parser = new HTTPParser();
cleanParser(parser);
parser[kOnHeaders] = parserOnHeaders;
parser[kOnHeadersComplete] = parserOnHeadersComplete;
parser[kOnBody] = parserOnBody;
parser[kOnMessageComplete] = parserOnMessageComplete;
return parser;
});
function closeParserInstance(parser) {
parser.close();
}
function freeParser(parser, req, socket) {
if (parser) {
if (parser._consumed) {
parser.unconsume();
}
parser.remove();
cleanParser(parser);
if (parsers.free(parser) === false) {
setImmediate(closeParserInstance, parser);
} else {
parser.free();
}
}
if (req) {
req.parser = null;
}
if (socket) {
socket.parser = null;
}
}
const tokenRegExp = new SafeRegExp("^[\\^_`a-zA-Z\\-0-9!#$%&'*+.|~]+$");
const validTokenChars = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]);
function checkIsHttpToken(val) {
if (val.length >= 10) {
return RegExpPrototypeTest(tokenRegExp, val);
}
if (val.length === 0) return false;
for (let i = 0; i < val.length; i++) {
if (!validTokenChars[StringPrototypeCharCodeAt(val, i)]) {
return false;
}
}
return true;
}
const headerCharRegex = new SafeRegExp("[^\\t\\x20-\\x7e\\x80-\\xff]");
function checkInvalidHeaderChar(val) {
return RegExpPrototypeTest(headerCharRegex, val);
}
function cleanParser(parser) {
parser._headers = [];
parser._url = "";
parser.socket = null;
parser.incoming = null;
parser.outgoing = null;
parser.maxHeaderPairs = MAX_HEADER_PAIRS;
parser[kOnMessageBegin] = null;
parser[kOnExecute] = null;
parser[kOnTimeout] = null;
parser._consumed = false;
parser.onIncoming = null;
parser.joinDuplicateHeaders = null;
parser._asyncResource = null;
}
function prepareError(err, parser, rawPacket) {
err.rawPacket = rawPacket || parser.getCurrentBuffer();
if (typeof err.reason === "string") {
err.message = `Parse Error: ${err.reason}`;
}
}
function isLenient() {
return false;
}
export const CRLF = "\r\n";
export const chunkExpression = new SafeRegExp("(?:^|\\W)chunked(?:$|\\W)", "i");
export const continueExpression = new SafeRegExp(
"(?:^|\\W)100-continue(?:$|\\W)",
"i",
);
export {
checkInvalidHeaderChar as _checkInvalidHeaderChar,
checkIsHttpToken as _checkIsHttpToken,
freeParser,
HTTPParser,
IncomingMessage,
isLenient,
kIncomingMessage,
kOnBody,
kSkipPendingData,
methods,
parsers,
prepareError,
readStart,
readStop,
};
export default {
_checkInvalidHeaderChar: checkInvalidHeaderChar,
_checkIsHttpToken: checkIsHttpToken,
chunkExpression,
CRLF,
continueExpression,
freeParser,
HTTPParser,
IncomingMessage,
isLenient,
kIncomingMessage,
kSkipPendingData,
methods,
parsers,
prepareError,
readStart,
readStop,
};