moving to new repo

This commit is contained in:
Russell2259
2024-01-25 20:52:54 -07:00
parent a7be044a02
commit 3fe2650a5e
25 changed files with 1 additions and 1934 deletions
-34
View File
@@ -1,34 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
import type { IncomingMessage, ServerResponse } from 'node:http';
import { Headers } from 'headers-polyfill';
import type { BareHeaders } from './requestUtil.js';
export interface RequestInit {
method: string;
path: string;
headers: Headers | BareHeaders;
}
/**
* Abstraction for the data read from IncomingMessage
*/
export declare class Request {
body: IncomingMessage;
method: string;
headers: Headers;
url: URL;
constructor(body: IncomingMessage, init: RequestInit);
}
export type ResponseBody = Buffer | IncomingMessage;
export interface ResponseInit {
headers?: Headers | BareHeaders;
status?: number;
statusText?: string;
}
export declare class Response {
body?: ResponseBody;
status: number;
statusText?: string;
headers: Headers;
constructor(body: ResponseBody | undefined, init?: ResponseInit);
}
export declare function writeResponse(response: Response, res: ServerResponse): boolean;
@@ -1,61 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeResponse = exports.Response = exports.Request = void 0;
const node_stream_1 = require("node:stream");
const headers_polyfill_1 = require("headers-polyfill");
/**
* Abstraction for the data read from IncomingMessage
*/
class Request {
body;
method;
headers;
url;
constructor(body, init) {
this.body = body;
this.method = init.method;
this.headers = new headers_polyfill_1.Headers(init.headers);
// Parse the URL pathname. Host doesn't matter.
this.url = new URL(init.path, 'http://bare-server-node');
}
}
exports.Request = Request;
class Response {
body;
status;
statusText;
headers;
constructor(body, init = {}) {
if (body) {
this.body = body instanceof node_stream_1.Stream ? body : Buffer.from(body);
}
if (typeof init.status === 'number') {
this.status = init.status;
}
else {
this.status = 200;
}
if (typeof init.statusText === 'string') {
this.statusText = init.statusText;
}
this.headers = new headers_polyfill_1.Headers(init.headers);
}
}
exports.Response = Response;
function writeResponse(response, res) {
for (const [header, value] of response.headers)
res.setHeader(header, value);
res.writeHead(response.status, response.statusText);
if (response.body instanceof node_stream_1.Stream) {
const { body } = response;
res.on('close', () => body.destroy());
body.pipe(res);
}
else if (response.body instanceof Buffer)
res.end(response.body);
else
res.end();
return true;
}
exports.writeResponse = writeResponse;
//# sourceMappingURL=AbstractMessage.js.map
-90
View File
@@ -1,90 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import type { LookupOneOptions } from 'node:dns';
import EventEmitter from 'node:events';
import type { Agent as HttpAgent, IncomingMessage, ServerResponse } from 'node:http';
import type { Agent as HttpsAgent } from 'node:https';
import type { Duplex } from 'node:stream';
import type WebSocket from 'ws';
import { Request, Response } from './AbstractMessage.js';
import type { JSONDatabaseAdapter } from './Meta.js';
export interface BareErrorBody {
code: string;
id: string;
message?: string;
stack?: string;
}
export declare class BareError extends Error {
status: number;
body: BareErrorBody;
constructor(status: number, body: BareErrorBody);
}
export declare const pkg: {
version: string;
};
export declare function json<T>(status: number, json: T): Response;
export type BareMaintainer = {
email?: string;
website?: string;
};
export type BareProject = {
name?: string;
description?: string;
email?: string;
website?: string;
repository?: string;
version?: string;
};
export type BareLanguage = 'NodeJS' | 'ServiceWorker' | 'Deno' | 'Java' | 'PHP' | 'Rust' | 'C' | 'C++' | 'C#' | 'Ruby' | 'Go' | 'Crystal' | 'Shell' | string;
export type BareManifest = {
maintainer?: BareMaintainer;
project?: BareProject;
versions: string[];
language: BareLanguage;
memoryUsage?: number;
};
export interface Options {
logErrors: boolean;
/**
* Callback for filtering the remote URL.
* @returns Nothing
* @throws An error if the remote is bad.
*/
filterRemote?: (remote: Readonly<URL>) => Promise<void> | void;
/**
* DNS lookup
* May not get called when remote.host is an IP
* Use in combination with filterRemote to block IPs
*/
lookup: (hostname: string, options: LookupOneOptions, callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void) => void;
localAddress?: string;
family?: number;
maintainer?: BareMaintainer;
httpAgent: HttpAgent;
httpsAgent: HttpsAgent;
database: JSONDatabaseAdapter;
wss: WebSocket.Server;
}
export type RouteCallback = (request: Request, response: ServerResponse<IncomingMessage>, options: Options) => Promise<Response> | Response;
export type SocketRouteCallback = (request: Request, socket: Duplex, head: Buffer, options: Options) => Promise<void> | void;
export default class Server extends EventEmitter {
routes: Map<string, RouteCallback>;
socketRoutes: Map<string, SocketRouteCallback>;
versions: string[];
private closed;
private directory;
private options;
/**
* Remove all timers and listeners
*/
close(): void;
shouldRoute(request: IncomingMessage): boolean;
get instanceInfo(): BareManifest;
routeUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): Promise<void>;
routeRequest(req: IncomingMessage, res: ServerResponse): Promise<void>;
}
-168
View File
@@ -1,168 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.json = exports.pkg = exports.BareError = void 0;
const node_events_1 = __importDefault(require("node:events"));
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const http_errors_1 = __importDefault(require("http-errors"));
const AbstractMessage_js_1 = require("./AbstractMessage.js");
class BareError extends Error {
status;
body;
constructor(status, body) {
super(body.message || body.code);
this.status = status;
this.body = body;
}
}
exports.BareError = BareError;
exports.pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(__dirname, '..', 'package.json'), 'utf-8'));
const project = {
name: 'bare-server-node',
description: 'TOMPHTTP NodeJS Bare Server',
repository: 'https://github.com/tomphttp/bare-server-node',
version: exports.pkg.version,
};
function json(status, json) {
const send = Buffer.from(JSON.stringify(json, null, '\t'));
return new AbstractMessage_js_1.Response(send, {
status,
headers: {
'content-type': 'application/json',
'content-length': send.byteLength.toString(),
},
});
}
exports.json = json;
class Server extends node_events_1.default {
routes = new Map();
socketRoutes = new Map();
versions = [];
closed = false;
directory;
options;
/**
* @internal
*/
constructor(directory, options) {
super();
this.directory = directory;
this.options = options;
}
/**
* Remove all timers and listeners
*/
close() {
this.closed = true;
this.emit('close');
}
shouldRoute(request) {
return (!this.closed &&
request.url !== undefined &&
request.url.startsWith(this.directory));
}
get instanceInfo() {
return {
versions: this.versions,
language: 'NodeJS',
memoryUsage: Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100,
maintainer: this.options.maintainer,
project,
};
}
async routeUpgrade(req, socket, head) {
const request = new AbstractMessage_js_1.Request(req, {
method: req.method,
path: req.url,
headers: req.headers,
});
const service = request.url.pathname.slice(this.directory.length - 1);
if (this.socketRoutes.has(service)) {
const call = this.socketRoutes.get(service);
try {
await call(request, socket, head, this.options);
}
catch (error) {
if (this.options.logErrors) {
console.error(error);
}
socket.end();
}
}
else {
socket.end();
}
}
async routeRequest(req, res) {
const request = new AbstractMessage_js_1.Request(req, {
method: req.method,
path: req.url,
headers: req.headers,
});
const service = request.url.pathname.slice(this.directory.length - 1);
let response;
try {
if (request.method === 'OPTIONS') {
response = new AbstractMessage_js_1.Response(undefined, { status: 200 });
}
else if (service === '/') {
response = json(200, this.instanceInfo);
}
else if (this.routes.has(service)) {
const call = this.routes.get(service);
response = await call(request, res, this.options);
}
else {
throw new http_errors_1.default.NotFound();
}
}
catch (error) {
if (this.options.logErrors)
console.error(error);
if (http_errors_1.default.isHttpError(error)) {
response = json(error.statusCode, {
code: 'UNKNOWN',
id: `error.${error.name}`,
message: error.message,
stack: error.stack,
});
}
else if (error instanceof Error) {
response = json(500, {
code: 'UNKNOWN',
id: `error.${error.name}`,
message: error.message,
stack: error.stack,
});
}
else {
response = json(500, {
code: 'UNKNOWN',
id: 'error.Exception',
message: error,
stack: new Error(error).stack,
});
}
if (!(response instanceof AbstractMessage_js_1.Response)) {
if (this.options.logErrors) {
console.error('Cannot', request.method, request.url.pathname, ': Route did not return a response.');
}
throw new http_errors_1.default.InternalServerError();
}
}
response.headers.set('x-robots-tag', 'noindex');
response.headers.set('access-control-allow-headers', '*');
response.headers.set('access-control-allow-origin', '*');
response.headers.set('access-control-allow-methods', '*');
response.headers.set('access-control-expose-headers', '*');
// don't fetch preflight on every request...
// instead, fetch preflight every 10 minutes
response.headers.set('access-control-max-age', '7200');
(0, AbstractMessage_js_1.writeResponse)(response, res);
}
}
exports.default = Server;
//# sourceMappingURL=BareServer.js.map
-33
View File
@@ -1,33 +0,0 @@
import type { BareHeaders } from './requestUtil';
export interface MetaV1 {
v: 1;
response?: {
headers: BareHeaders;
};
}
export interface MetaV2 {
v: 2;
response?: {
status: number;
statusText: string;
headers: BareHeaders;
};
sendHeaders: BareHeaders;
remote: string;
forwardHeaders: string[];
}
export default interface CommonMeta {
value: MetaV1 | MetaV2;
expires: number;
}
export interface Database {
get(key: string): string | undefined | PromiseLike<string | undefined>;
set(key: string, value: string): unknown;
has(key: string): boolean | PromiseLike<boolean>;
delete(key: string): boolean | PromiseLike<boolean>;
entries(): IterableIterator<[string, string]> | PromiseLike<IterableIterator<[string, string]>>;
}
/**
* Routine
*/
export declare function cleanupDatabase(database: Database): Promise<void>;
-43
View File
@@ -1,43 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanupDatabase = exports.JSONDatabaseAdapter = void 0;
/**
* @internal
*/
class JSONDatabaseAdapter {
impl;
constructor(impl) {
this.impl = impl;
}
async get(key) {
const res = await this.impl.get(key);
if (typeof res === 'string')
return JSON.parse(res);
}
async set(key, value) {
return await this.impl.set(key, JSON.stringify(value));
}
async has(key) {
return await this.impl.has(key);
}
async delete(key) {
return await this.impl.delete(key);
}
async *[Symbol.asyncIterator]() {
for (const [id, value] of await this.impl.entries()) {
yield [id, JSON.parse(value)];
}
}
}
exports.JSONDatabaseAdapter = JSONDatabaseAdapter;
/**
* Routine
*/
async function cleanupDatabase(database) {
const adapter = new JSONDatabaseAdapter(database);
for await (const [id, { expires }] of adapter)
if (expires < Date.now())
database.delete(id);
}
exports.cleanupDatabase = cleanupDatabase;
//# sourceMappingURL=Meta.js.map
-2
View File
@@ -1,2 +0,0 @@
import type Server from './BareServer.js';
export default function registerV1(server: Server): void;
-254
View File
@@ -1,254 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const headers_polyfill_1 = require("headers-polyfill");
const AbstractMessage_js_1 = require("./AbstractMessage.js");
const BareServer_js_1 = require("./BareServer.js");
const encodeProtocol_js_1 = require("./encodeProtocol.js");
const headerUtil_js_1 = require("./headerUtil.js");
const remoteUtil_js_1 = require("./remoteUtil.js");
const requestUtil_js_1 = require("./requestUtil.js");
const validProtocols = ['http:', 'https:', 'ws:', 'wss:'];
function loadForwardedHeaders(forward, target, request) {
for (const header of forward) {
const value = request.headers.get(header);
if (value !== null)
target[header] = value;
}
}
function readHeaders(request) {
const remote = Object.create(null);
const headers = Object.create(null);
for (const remoteProp of ['host', 'port', 'protocol', 'path']) {
const header = `x-bare-${remoteProp}`;
const value = request.headers.get(header);
if (value === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.${header}`,
message: `Header was not specified.`,
});
switch (remoteProp) {
case 'port':
if (isNaN(parseInt(value))) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `Header was not a valid integer.`,
});
}
break;
case 'protocol':
if (!validProtocols.includes(value)) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `Header was invalid`,
});
}
break;
}
remote[remoteProp] = value;
}
const xBareHeaders = request.headers.get('x-bare-headers');
if (xBareHeaders === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `Header was not specified.`,
});
try {
const json = JSON.parse(xBareHeaders);
for (const header in json) {
const value = json[header];
if (typeof value === 'string') {
headers[header] = value;
}
else if (Array.isArray(value)) {
const array = [];
for (const val of value) {
if (typeof val !== 'string') {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `Header was not a String.`,
});
}
array.push(val);
}
headers[header] = array;
}
else {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `Header was not a String.`,
});
}
}
}
catch (error) {
if (error instanceof SyntaxError) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `Header contained invalid JSON. (${error.message})`,
});
}
else {
throw error;
}
}
const xBareForwardHeaders = request.headers.get('x-bare-forward-headers');
if (xBareForwardHeaders === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `Header was not specified.`,
});
try {
loadForwardedHeaders(JSON.parse(xBareForwardHeaders), headers, request);
}
catch (error) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `Header contained invalid JSON. (${error instanceof Error ? error.message : error})`,
});
}
return { remote: (0, remoteUtil_js_1.remoteToURL)(remote), headers };
}
const tunnelRequest = async (request, res, options) => {
const abort = new AbortController();
request.body.on('close', () => {
if (!request.body.complete)
abort.abort();
});
res.on('close', () => {
abort.abort();
});
const { remote, headers } = readHeaders(request);
const response = await (0, requestUtil_js_1.fetch)(request, abort.signal, headers, remote, options);
const responseHeaders = new headers_polyfill_1.Headers();
for (const header in response.headers) {
if (header === 'content-encoding' || header === 'x-content-encoding')
responseHeaders.set('content-encoding', (0, headerUtil_js_1.flattenHeader)(response.headers[header]));
else if (header === 'content-length')
responseHeaders.set('content-length', (0, headerUtil_js_1.flattenHeader)(response.headers[header]));
}
responseHeaders.set('x-bare-headers', JSON.stringify((0, headerUtil_js_1.mapHeadersFromArray)((0, headerUtil_js_1.rawHeaderNames)(response.rawHeaders), {
...response.headers,
})));
responseHeaders.set('x-bare-status', response.statusCode.toString());
responseHeaders.set('x-bare-status-text', response.statusMessage);
return new AbstractMessage_js_1.Response(response, { status: 200, headers: responseHeaders });
};
const metaExpiration = 30e3;
const wsMeta = async (request, res, options) => {
if (request.method === 'OPTIONS') {
return new AbstractMessage_js_1.Response(undefined, { status: 200 });
}
const id = request.headers.get('x-bare-id');
if (id === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'Header was not specified',
});
const meta = await options.database.get(id);
// check if meta isn't undefined and if the version equals 1
if (meta?.value.v !== 1)
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'Unregistered ID',
});
await options.database.delete(id);
return (0, BareServer_js_1.json)(200, {
headers: meta.value.response?.headers,
});
};
const wsNewMeta = async (request, res, options) => {
const id = (0, requestUtil_js_1.randomHex)(16);
await options.database.set(id, {
value: { v: 1 },
expires: Date.now() + metaExpiration,
});
return new AbstractMessage_js_1.Response(Buffer.from(id));
};
const tunnelSocket = async (request, socket, head, options) => {
const abort = new AbortController();
request.body.on('close', () => {
if (!request.body.complete)
abort.abort();
});
socket.on('close', () => {
abort.abort();
});
if (!request.headers.has('sec-websocket-protocol')) {
socket.end();
return;
}
const [firstProtocol, data] = request.headers
.get('sec-websocket-protocol')
.split(/,\s*/g);
if (firstProtocol !== 'bare') {
socket.end();
return;
}
const { remote, headers, forward_headers: forwardHeaders, id, } = JSON.parse((0, encodeProtocol_js_1.decodeProtocol)(data));
loadForwardedHeaders(forwardHeaders, headers, request);
const [remoteResponse, remoteSocket] = await (0, requestUtil_js_1.upgradeFetch)(request, abort.signal, headers, (0, remoteUtil_js_1.remoteToURL)(remote), options);
remoteSocket.on('close', () => {
// console.log('Remote closed');
socket.end();
});
socket.on('close', () => {
// console.log('Serving closed');
remoteSocket.end();
});
remoteSocket.on('error', (error) => {
if (options.logErrors) {
console.error('Remote socket error:', error);
}
socket.end();
});
socket.on('error', (error) => {
if (options.logErrors) {
console.error('Serving socket error:', error);
}
remoteSocket.end();
});
if (typeof id === 'string') {
const meta = await options.database.get(id);
if (meta?.value.v === 1) {
meta.value.response = {
headers: (0, headerUtil_js_1.mapHeadersFromArray)((0, headerUtil_js_1.rawHeaderNames)(remoteResponse.rawHeaders), {
...remoteResponse.headers,
}),
};
await options.database.set(id, meta);
}
}
const responseHeaders = [
`HTTP/1.1 101 Switching Protocols`,
`Upgrade: websocket`,
`Connection: Upgrade`,
`Sec-WebSocket-Protocol: bare`,
`Sec-WebSocket-Accept: ${remoteResponse.headers['sec-websocket-accept']}`,
];
if ('sec-websocket-extensions' in remoteResponse.headers) {
responseHeaders.push(`Sec-WebSocket-Extensions: ${remoteResponse.headers['sec-websocket-extensions']}`);
}
socket.write(responseHeaders.concat('', '').join('\r\n'));
remoteSocket.pipe(socket);
socket.pipe(remoteSocket);
};
function registerV1(server) {
server.routes.set('/v1/', tunnelRequest);
server.routes.set('/v1/ws-new-meta', wsNewMeta);
server.routes.set('/v1/ws-meta', wsMeta);
server.socketRoutes.set('/v1/', tunnelSocket);
server.versions.push('v1');
}
exports.default = registerV1;
//# sourceMappingURL=V1.js.map
-2
View File
@@ -1,2 +0,0 @@
import type Server from './BareServer.js';
export default function registerV2(server: Server): void;
-364
View File
@@ -1,364 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const headers_polyfill_1 = require("headers-polyfill");
const AbstractMessage_js_1 = require("./AbstractMessage.js");
const BareServer_js_1 = require("./BareServer.js");
const headerUtil_js_1 = require("./headerUtil.js");
const remoteUtil_js_1 = require("./remoteUtil.js");
const requestUtil_js_1 = require("./requestUtil.js");
const splitHeaderUtil_js_1 = require("./splitHeaderUtil.js");
const validProtocols = ['http:', 'https:', 'ws:', 'wss:'];
const forbiddenForwardHeaders = [
'connection',
'transfer-encoding',
'host',
'connection',
'origin',
'referer',
];
const forbiddenPassHeaders = [
'vary',
'connection',
'transfer-encoding',
'access-control-allow-headers',
'access-control-allow-methods',
'access-control-expose-headers',
'access-control-max-age',
'access-control-request-headers',
'access-control-request-method',
];
// common defaults
const defaultForwardHeaders = [
'accept-encoding',
'accept-language',
'sec-websocket-extensions',
'sec-websocket-key',
'sec-websocket-version',
];
const defaultPassHeaders = [
'content-encoding',
'content-length',
'last-modified',
];
// defaults if the client provides a cache key
const defaultCacheForwardHeaders = [
'if-modified-since',
'if-none-match',
'cache-control',
];
const defaultCachePassHeaders = ['cache-control', 'etag'];
const cacheNotModified = 304;
function loadForwardedHeaders(forward, target, request) {
for (const header of forward) {
if (request.headers.has(header)) {
target[header] = request.headers.get(header);
}
}
}
const splitHeaderValue = /,\s*/g;
function readHeaders(request) {
const remote = Object.create(null);
const sendHeaders = Object.create(null);
const passHeaders = [...defaultPassHeaders];
const passStatus = [];
const forwardHeaders = [...defaultForwardHeaders];
// should be unique
const cache = request.url.searchParams.has('cache');
if (cache) {
passHeaders.push(...defaultCachePassHeaders);
passStatus.push(cacheNotModified);
forwardHeaders.push(...defaultCacheForwardHeaders);
}
const headers = (0, splitHeaderUtil_js_1.joinHeaders)(request.headers);
for (const remoteProp of ['host', 'port', 'protocol', 'path']) {
const header = `x-bare-${remoteProp}`;
const value = headers.get(header);
if (value === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.${header}`,
message: `Header was not specified.`,
});
switch (remoteProp) {
case 'port':
if (isNaN(parseInt(value))) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `Header was not a valid integer.`,
});
}
break;
case 'protocol':
if (!validProtocols.includes(value)) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `Header was invalid`,
});
}
break;
}
remote[remoteProp] = value;
}
const xBareHeaders = headers.get('x-bare-headers');
if (xBareHeaders === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `Header was not specified.`,
});
try {
const json = JSON.parse(xBareHeaders);
for (const header in json) {
const value = json[header];
if (typeof value === 'string') {
sendHeaders[header] = value;
}
else if (Array.isArray(value)) {
const array = [];
for (const val of value) {
if (typeof val !== 'string') {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `Header was not a String.`,
});
}
array.push(val);
}
sendHeaders[header] = array;
}
else
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `Header was not a String.`,
});
}
}
catch (error) {
if (error instanceof SyntaxError) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `Header contained invalid JSON. (${error.message})`,
});
}
else {
throw error;
}
}
if (headers.has('x-bare-pass-status')) {
const parsed = headers.get('x-bare-pass-status').split(splitHeaderValue);
for (const value of parsed) {
const number = parseInt(value);
if (isNaN(number)) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-pass-status`,
message: `Array contained non-number value.`,
});
}
else {
passStatus.push(number);
}
}
}
if (headers.has('x-bare-pass-headers')) {
const parsed = headers.get('x-bare-pass-headers').split(splitHeaderValue);
for (let header of parsed) {
header = header.toLowerCase();
if (forbiddenPassHeaders.includes(header)) {
throw new BareServer_js_1.BareError(400, {
code: 'FORBIDDEN_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `A forbidden header was passed.`,
});
}
else {
passHeaders.push(header);
}
}
}
if (headers.has('x-bare-forward-headers')) {
const parsed = headers
.get('x-bare-forward-headers')
.split(splitHeaderValue);
for (let header of parsed) {
header = header.toLowerCase();
if (forbiddenForwardHeaders.includes(header)) {
throw new BareServer_js_1.BareError(400, {
code: 'FORBIDDEN_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `A forbidden header was forwarded.`,
});
}
else {
forwardHeaders.push(header);
}
}
}
return {
remote: (0, remoteUtil_js_1.remoteToURL)(remote),
sendHeaders,
passHeaders,
passStatus,
forwardHeaders,
};
}
const tunnelRequest = async (request, res, options) => {
const abort = new AbortController();
request.body.on('close', () => {
if (!request.body.complete)
abort.abort();
});
res.on('close', () => {
abort.abort();
});
const { remote, sendHeaders, passHeaders, passStatus, forwardHeaders } = readHeaders(request);
loadForwardedHeaders(forwardHeaders, sendHeaders, request);
const response = await (0, requestUtil_js_1.fetch)(request, abort.signal, sendHeaders, remote, options);
const responseHeaders = new headers_polyfill_1.Headers();
for (const header of passHeaders) {
if (!(header in response.headers))
continue;
responseHeaders.set(header, (0, headerUtil_js_1.flattenHeader)(response.headers[header]));
}
const status = passStatus.includes(response.statusCode)
? response.statusCode
: 200;
if (status !== cacheNotModified) {
responseHeaders.set('x-bare-status', response.statusCode.toString());
responseHeaders.set('x-bare-status-text', response.statusMessage);
responseHeaders.set('x-bare-headers', JSON.stringify((0, headerUtil_js_1.mapHeadersFromArray)((0, headerUtil_js_1.rawHeaderNames)(response.rawHeaders), {
...response.headers,
})));
}
return new AbstractMessage_js_1.Response(response, {
status,
headers: (0, splitHeaderUtil_js_1.splitHeaders)(responseHeaders),
});
};
const metaExpiration = 30e3;
const getMeta = async (request, res, options) => {
if (request.method === 'OPTIONS') {
return new AbstractMessage_js_1.Response(undefined, { status: 200 });
}
const id = request.headers.get('x-bare-id');
if (id === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'Header was not specified',
});
const meta = await options.database.get(id);
if (meta?.value.v !== 2)
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'Unregistered ID',
});
if (!meta.value.response)
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'Meta not ready',
});
await options.database.delete(id);
const responseHeaders = new headers_polyfill_1.Headers();
responseHeaders.set('x-bare-status', meta.value.response.status.toString());
responseHeaders.set('x-bare-status-text', meta.value.response.statusText);
responseHeaders.set('x-bare-headers', JSON.stringify(meta.value.response.headers));
return new AbstractMessage_js_1.Response(undefined, {
status: 200,
headers: (0, splitHeaderUtil_js_1.splitHeaders)(responseHeaders),
});
};
const newMeta = async (request, res, options) => {
const { remote, sendHeaders, forwardHeaders } = readHeaders(request);
const id = (0, requestUtil_js_1.randomHex)(16);
await options.database.set(id, {
expires: Date.now() + metaExpiration,
value: {
v: 2,
remote: remote.toString(),
sendHeaders,
forwardHeaders,
},
});
return new AbstractMessage_js_1.Response(Buffer.from(id));
};
const tunnelSocket = async (request, socket, head, options) => {
const abort = new AbortController();
request.body.on('close', () => {
if (!request.body.complete)
abort.abort();
});
socket.on('close', () => {
abort.abort();
});
if (!request.headers.has('sec-websocket-protocol')) {
socket.end();
return;
}
const id = request.headers.get('sec-websocket-protocol');
const meta = await options.database.get(id);
if (meta?.value.v !== 2) {
socket.end();
return;
}
loadForwardedHeaders(meta.value.forwardHeaders, meta.value.sendHeaders, request);
const [remoteResponse, remoteSocket] = await (0, requestUtil_js_1.upgradeFetch)(request, abort.signal, meta.value.sendHeaders, new URL(meta.value.remote), options);
remoteSocket.on('close', () => {
socket.end();
});
socket.on('close', () => {
remoteSocket.end();
});
remoteSocket.on('error', (error) => {
if (options.logErrors) {
console.error('Remote socket error:', error);
}
socket.end();
});
socket.on('error', (error) => {
if (options.logErrors) {
console.error('Serving socket error:', error);
}
remoteSocket.end();
});
const remoteHeaders = new headers_polyfill_1.Headers(remoteResponse.headers);
meta.value.response = {
headers: (0, headerUtil_js_1.mapHeadersFromArray)((0, headerUtil_js_1.rawHeaderNames)(remoteResponse.rawHeaders), {
...remoteResponse.headers,
}),
status: remoteResponse.statusCode,
statusText: remoteResponse.statusMessage,
};
await options.database.set(id, meta);
const responseHeaders = [
`HTTP/1.1 101 Switching Protocols`,
`Upgrade: websocket`,
`Connection: Upgrade`,
`Sec-WebSocket-Protocol: ${id}`,
];
if (remoteHeaders.has('sec-websocket-extensions')) {
responseHeaders.push(`Sec-WebSocket-Extensions: ${remoteHeaders.get('sec-websocket-extensions')}`);
}
if (remoteHeaders.has('sec-websocket-accept')) {
responseHeaders.push(`Sec-WebSocket-Accept: ${remoteHeaders.get('sec-websocket-accept')}`);
}
socket.write(responseHeaders.concat('', '').join('\r\n'));
remoteSocket.pipe(socket);
socket.pipe(remoteSocket);
};
function registerV2(server) {
server.routes.set('/v2/', tunnelRequest);
server.routes.set('/v2/ws-new-meta', newMeta);
server.routes.set('/v2/ws-meta', getMeta);
server.socketRoutes.set('/v2/', tunnelSocket);
server.versions.push('v2');
}
exports.default = registerV2;
//# sourceMappingURL=V2.js.map
-2
View File
@@ -1,2 +0,0 @@
import type Server from './BareServer.js';
export default function registerV3(server: Server): void;
-305
View File
@@ -1,305 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const headers_polyfill_1 = require("headers-polyfill");
const AbstractMessage_js_1 = require("./AbstractMessage.js");
const BareServer_js_1 = require("./BareServer.js");
const headerUtil_js_1 = require("./headerUtil.js");
const remoteUtil_js_1 = require("./remoteUtil.js");
const requestUtil_js_1 = require("./requestUtil.js");
const splitHeaderUtil_js_1 = require("./splitHeaderUtil.js");
const forbiddenForwardHeaders = [
'connection',
'transfer-encoding',
'host',
'connection',
'origin',
'referer',
];
const forbiddenPassHeaders = [
'vary',
'connection',
'transfer-encoding',
'access-control-allow-headers',
'access-control-allow-methods',
'access-control-expose-headers',
'access-control-max-age',
'access-control-request-headers',
'access-control-request-method',
];
// common defaults
const defaultForwardHeaders = ['accept-encoding', 'accept-language'];
const defaultPassHeaders = [
'content-encoding',
'content-length',
'last-modified',
];
// defaults if the client provides a cache key
const defaultCacheForwardHeaders = [
'if-modified-since',
'if-none-match',
'cache-control',
];
const defaultCachePassHeaders = ['cache-control', 'etag'];
const cacheNotModified = 304;
function loadForwardedHeaders(forward, target, request) {
for (const header of forward) {
if (request.headers.has(header)) {
target[header] = request.headers.get(header);
}
}
}
const splitHeaderValue = /,\s*/g;
function readHeaders(request) {
const sendHeaders = Object.create(null);
const passHeaders = [...defaultPassHeaders];
const passStatus = [];
const forwardHeaders = [...defaultForwardHeaders];
// should be unique
const cache = request.url.searchParams.has('cache');
if (cache) {
passHeaders.push(...defaultCachePassHeaders);
passStatus.push(cacheNotModified);
forwardHeaders.push(...defaultCacheForwardHeaders);
}
const headers = (0, splitHeaderUtil_js_1.joinHeaders)(request.headers);
const xBareURL = headers.get('x-bare-url');
if (xBareURL === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-url`,
message: `Header was not specified.`,
});
const remote = (0, remoteUtil_js_1.urlToRemote)(new URL(xBareURL));
const xBareHeaders = headers.get('x-bare-headers');
if (xBareHeaders === null)
throw new BareServer_js_1.BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `Header was not specified.`,
});
try {
const json = JSON.parse(xBareHeaders);
for (const header in json) {
const value = json[header];
if (typeof value === 'string') {
sendHeaders[header] = value;
}
else if (Array.isArray(value)) {
const array = [];
for (const val of value) {
if (typeof val !== 'string') {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `Header was not a String.`,
});
}
array.push(val);
}
sendHeaders[header] = array;
}
else {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `Header was not a String.`,
});
}
}
}
catch (error) {
if (error instanceof SyntaxError) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `Header contained invalid JSON. (${error.message})`,
});
}
else {
throw error;
}
}
if (headers.has('x-bare-pass-status')) {
const parsed = headers.get('x-bare-pass-status').split(splitHeaderValue);
for (const value of parsed) {
const number = parseInt(value);
if (isNaN(number)) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-pass-status`,
message: `Array contained non-number value.`,
});
}
else {
passStatus.push(number);
}
}
}
if (headers.has('x-bare-pass-headers')) {
const parsed = headers.get('x-bare-pass-headers').split(splitHeaderValue);
for (let header of parsed) {
header = header.toLowerCase();
if (forbiddenPassHeaders.includes(header)) {
throw new BareServer_js_1.BareError(400, {
code: 'FORBIDDEN_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `A forbidden header was passed.`,
});
}
else {
passHeaders.push(header);
}
}
}
if (headers.has('x-bare-forward-headers')) {
const parsed = headers
.get('x-bare-forward-headers')
.split(splitHeaderValue);
for (let header of parsed) {
header = header.toLowerCase();
if (forbiddenForwardHeaders.includes(header)) {
throw new BareServer_js_1.BareError(400, {
code: 'FORBIDDEN_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `A forbidden header was forwarded.`,
});
}
else {
forwardHeaders.push(header);
}
}
}
return {
remote: (0, remoteUtil_js_1.remoteToURL)(remote),
sendHeaders,
passHeaders,
passStatus,
forwardHeaders,
};
}
const tunnelRequest = async (request, res, options) => {
const abort = new AbortController();
request.body.on('close', () => {
if (!request.body.complete)
abort.abort();
});
res.on('close', () => {
abort.abort();
});
const { remote, sendHeaders, passHeaders, passStatus, forwardHeaders } = readHeaders(request);
loadForwardedHeaders(forwardHeaders, sendHeaders, request);
const response = await (0, requestUtil_js_1.fetch)(request, abort.signal, sendHeaders, remote, options);
const responseHeaders = new headers_polyfill_1.Headers();
for (const header of passHeaders) {
if (!(header in response.headers))
continue;
responseHeaders.set(header, (0, headerUtil_js_1.flattenHeader)(response.headers[header]));
}
const status = passStatus.includes(response.statusCode)
? response.statusCode
: 200;
if (status !== cacheNotModified) {
responseHeaders.set('x-bare-status', response.statusCode.toString());
responseHeaders.set('x-bare-status-text', response.statusMessage);
responseHeaders.set('x-bare-headers', JSON.stringify((0, headerUtil_js_1.mapHeadersFromArray)((0, headerUtil_js_1.rawHeaderNames)(response.rawHeaders), {
...response.headers,
})));
}
return new AbstractMessage_js_1.Response(response, {
status,
headers: (0, splitHeaderUtil_js_1.splitHeaders)(responseHeaders),
});
};
function readSocket(socket) {
return new Promise((resolve, reject) => {
const messageListener = (event) => {
cleanup();
if (typeof event.data !== 'string')
return reject(new TypeError('the first websocket message was not a text frame'));
try {
resolve(JSON.parse(event.data));
}
catch (err) {
reject(err);
}
};
const closeListener = () => {
cleanup();
};
const cleanup = () => {
socket.removeEventListener('message', messageListener);
socket.removeEventListener('close', closeListener);
clearTimeout(timeout);
};
const timeout = setTimeout(() => {
cleanup();
reject(new Error('Timed out before metadata could be read'));
}, 10e3);
socket.addEventListener('message', messageListener);
socket.addEventListener('close', closeListener);
});
}
const tunnelSocket = async (request, socket, head, options) => options.wss.handleUpgrade(request.body, socket, head, async (client) => {
let _remoteSocket;
try {
const connectPacket = await readSocket(client);
if (connectPacket.type !== 'connect')
throw new Error('Client did not send open packet.');
loadForwardedHeaders(connectPacket.forwardHeaders, connectPacket.headers, request);
const [remoteReq, remoteSocket] = await (0, requestUtil_js_1.webSocketFetch)(request, connectPacket.headers, new URL(connectPacket.remote), connectPacket.protocols, options);
_remoteSocket = remoteSocket;
const setCookieHeader = remoteReq.headers['set-cookie'];
const setCookies = setCookieHeader !== undefined
? Array.isArray(setCookieHeader)
? setCookieHeader
: [setCookieHeader]
: [];
client.send(JSON.stringify({
type: 'open',
protocol: remoteSocket.protocol,
setCookies,
}),
// use callback to wait for this message to buffer and finally send before doing any piping
// otherwise the client will receive a random message from the remote before our open message
() => {
remoteSocket.addEventListener('message', (event) => {
client.send(event.data);
});
client.addEventListener('message', (event) => {
remoteSocket.send(event.data);
});
remoteSocket.addEventListener('close', () => {
client.close();
});
client.addEventListener('close', () => {
remoteSocket.close();
});
remoteSocket.addEventListener('error', (error) => {
if (options.logErrors) {
console.error('Remote socket error:', error);
}
client.close();
});
client.addEventListener('error', (error) => {
if (options.logErrors) {
console.error('Serving socket error:', error);
}
remoteSocket.close();
});
});
}
catch (err) {
if (options.logErrors)
console.error(err);
client.close();
if (_remoteSocket)
_remoteSocket.close();
}
});
function registerV3(server) {
server.routes.set('/v3/', tunnelRequest);
server.socketRoutes.set('/v3/', tunnelSocket);
server.versions.push('v3');
}
exports.default = registerV3;
//# sourceMappingURL=V3.js.map
-52
View File
@@ -1,52 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
import { Agent as HttpAgent } from 'node:http';
import { Agent as HttpsAgent } from 'node:https';
import BareServer from './BareServer.js';
import type { BareMaintainer, Options } from './BareServer.js';
import type { Database } from './Meta.js';
export declare const validIPFamily: number[];
export type IPFamily = 0 | 4 | 6;
export interface BareServerInit {
logErrors?: boolean;
localAddress?: string;
/**
* When set, the default logic for blocking local IP addresses is disabled.
*/
filterRemote?: Options['filterRemote'];
/**
* When set, the default logic for blocking local IP addresses is disabled.
*/
lookup?: Options['lookup'];
/**
* If local IP addresses/DNS records should be blocked.
* @default true
*/
blockLocal?: boolean;
/**
* IP address family to use when resolving `host` or `hostname`. Valid values are `0`, `4`, and `6`. When unspecified/0, both IP v4 and v6 will be used.
*/
family?: IPFamily | number;
maintainer?: BareMaintainer;
httpAgent?: HttpAgent;
httpsAgent?: HttpsAgent;
/**
* If legacy clients should be supported (v1 & v2). If this is set to false, the database can be safely ignored.
* @default true
*/
legacySupport?: boolean;
database?: Database;
}
export interface Address {
address: string;
family: number;
}
/**
* Converts the address and family of a DNS lookup callback into an array if it wasn't already
*/
export declare function toAddressArray(address: string | Address[], family?: number): Address[];
/**
* Create a Bare server.
* This will handle all lifecycles for unspecified options (httpAgent, httpsAgent, metaMap).
*/
export declare function createBareServer(directory: string, init?: BareServerInit): BareServer;
-99
View File
@@ -1,99 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createBareServer = exports.toAddressArray = exports.validIPFamily = void 0;
const node_dns_1 = require("node:dns");
const node_http_1 = require("node:http");
const node_https_1 = require("node:https");
const ipaddr_js_1 = require("ipaddr.js");
const ws_1 = require("ws");
const BareServer_js_1 = __importDefault(require("./BareServer.js"));
const Meta_js_1 = require("./Meta.js");
const V1_js_1 = __importDefault(require("./V1.js"));
const V2_js_1 = __importDefault(require("./V2.js"));
const V3_js_1 = __importDefault(require("./V3.js"));
exports.validIPFamily = [0, 4, 6];
/**
* Converts the address and family of a DNS lookup callback into an array if it wasn't already
*/
function toAddressArray(address, family) {
if (typeof address === 'string')
return [
{
address,
family,
},
];
else
return address;
}
exports.toAddressArray = toAddressArray;
/**
* Create a Bare server.
* This will handle all lifecycles for unspecified options (httpAgent, httpsAgent, metaMap).
*/
function createBareServer(directory, init = {}) {
if (typeof directory !== 'string')
throw new Error('Directory must be specified.');
if (!directory.startsWith('/') || !directory.endsWith('/'))
throw new RangeError('Directory must start and end with /');
init.logErrors ??= false;
const cleanup = [];
if (typeof init.family === 'number' && !exports.validIPFamily.includes(init.family))
throw new RangeError('init.family must be one of: 0, 4, 6');
if (init.blockLocal ?? true) {
init.filterRemote ??= (url) => {
// if the remote is an IP then it didn't go through the init.lookup hook
// isValid determines if this is so
if ((0, ipaddr_js_1.isValid)(url.hostname) && (0, ipaddr_js_1.parse)(url.hostname).range() !== 'unicast')
throw new RangeError('Forbidden IP');
};
init.lookup ??= (hostname, options, callback) => (0, node_dns_1.lookup)(hostname, options, (err, address, family) => {
if (address &&
toAddressArray(address, family).some(({ address }) => (0, ipaddr_js_1.parse)(address).range() !== 'unicast'))
callback(new RangeError('Forbidden IP'), '', -1);
else
callback(err, address, family);
});
}
if (!init.httpAgent) {
const httpAgent = new node_http_1.Agent({
keepAlive: true,
});
init.httpAgent = httpAgent;
cleanup.push(() => httpAgent.destroy());
}
if (!init.httpsAgent) {
const httpsAgent = new node_https_1.Agent({
keepAlive: true,
});
init.httpsAgent = httpsAgent;
cleanup.push(() => httpsAgent.destroy());
}
if (!init.database) {
const database = new Map();
const interval = setInterval(() => (0, Meta_js_1.cleanupDatabase)(database), 1000);
init.database = database;
cleanup.push(() => clearInterval(interval));
}
const server = new BareServer_js_1.default(directory, {
...init,
database: new Meta_js_1.JSONDatabaseAdapter(init.database),
wss: new ws_1.WebSocketServer({ noServer: true }),
});
init.legacySupport ??= true;
if (init.legacySupport) {
(0, V1_js_1.default)(server);
(0, V2_js_1.default)(server);
}
(0, V3_js_1.default)(server);
server.once('close', () => {
for (const cb of cleanup)
cb();
});
return server;
}
exports.createBareServer = createBareServer;
//# sourceMappingURL=createServer.js.map
-3
View File
@@ -1,3 +0,0 @@
export declare function validProtocol(protocol: string): boolean;
export declare function encodeProtocol(protocol: string): string;
export declare function decodeProtocol(protocol: string): string;
@@ -1,48 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeProtocol = exports.encodeProtocol = exports.validProtocol = void 0;
const validChars = "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
const reserveChar = '%';
function validProtocol(protocol) {
for (let i = 0; i < protocol.length; i++) {
const char = protocol[i];
if (!validChars.includes(char)) {
return false;
}
}
return true;
}
exports.validProtocol = validProtocol;
function encodeProtocol(protocol) {
let result = '';
for (let i = 0; i < protocol.length; i++) {
const char = protocol[i];
if (validChars.includes(char) && char !== reserveChar) {
result += char;
}
else {
const code = char.charCodeAt(0);
result += reserveChar + code.toString(16).padStart(2, '0');
}
}
return result;
}
exports.encodeProtocol = encodeProtocol;
function decodeProtocol(protocol) {
let result = '';
for (let i = 0; i < protocol.length; i++) {
const char = protocol[i];
if (char === reserveChar) {
const code = parseInt(protocol.slice(i + 1, i + 3), 16);
const decoded = String.fromCharCode(code);
result += decoded;
i += 2;
}
else {
result += char;
}
}
return result;
}
exports.decodeProtocol = decodeProtocol;
//# sourceMappingURL=encodeProtocol.js.map
-8
View File
@@ -1,8 +0,0 @@
import type { BareHeaders } from './requestUtil.js';
export declare function objectFromRawHeaders(raw: string[]): BareHeaders;
export declare function rawHeaderNames(raw: string[]): string[];
export declare function mapHeadersFromArray(from: string[], to: BareHeaders): BareHeaders;
/**
* Converts a header into an HTTP-ready comma joined header.
*/
export declare function flattenHeader(value: string | string[]): string;
-48
View File
@@ -1,48 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.flattenHeader = exports.mapHeadersFromArray = exports.rawHeaderNames = exports.objectFromRawHeaders = void 0;
function objectFromRawHeaders(raw) {
const result = Object.create(null);
for (let i = 0; i < raw.length; i += 2) {
const [header, value] = raw.slice(i, i + 2);
if (header in result) {
const v = result[header];
if (Array.isArray(v))
v.push(value);
else
result[header] = [v, value];
}
else
result[header] = value;
}
return result;
}
exports.objectFromRawHeaders = objectFromRawHeaders;
function rawHeaderNames(raw) {
const result = [];
for (let i = 0; i < raw.length; i += 2) {
if (!result.includes(raw[i]))
result.push(raw[i]);
}
return result;
}
exports.rawHeaderNames = rawHeaderNames;
function mapHeadersFromArray(from, to) {
for (const header of from) {
if (header.toLowerCase() in to) {
const value = to[header.toLowerCase()];
delete to[header.toLowerCase()];
to[header] = value;
}
}
return to;
}
exports.mapHeadersFromArray = mapHeadersFromArray;
/**
* Converts a header into an HTTP-ready comma joined header.
*/
function flattenHeader(value) {
return Array.isArray(value) ? value.join(', ') : value;
}
exports.flattenHeader = flattenHeader;
//# sourceMappingURL=headerUtil.js.map
-9
View File
@@ -1,9 +0,0 @@
export interface BareRemote {
host: string;
port: number | string;
path: string;
protocol: string;
}
export declare function remoteToURL(remote: BareRemote): URL;
export declare function resolvePort(url: URL): number;
export declare function urlToRemote(url: URL): BareRemote;
-36
View File
@@ -1,36 +0,0 @@
"use strict";
/*
* Utilities for converting remotes to URLs
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.urlToRemote = exports.resolvePort = exports.remoteToURL = void 0;
function remoteToURL(remote) {
return new URL(`${remote.protocol}${remote.host}:${remote.port}${remote.path}`);
}
exports.remoteToURL = remoteToURL;
function resolvePort(url) {
if (url.port)
return Number(url.port);
switch (url.protocol) {
case 'ws:':
case 'http:':
return 80;
case 'wss:':
case 'https:':
return 443;
default:
// maybe blob
return 0;
}
}
exports.resolvePort = resolvePort;
function urlToRemote(url) {
return {
protocol: url.protocol,
host: url.hostname,
port: resolvePort(url),
path: url.pathname + url.search,
};
}
exports.urlToRemote = urlToRemote;
//# sourceMappingURL=remoteUtil.js.map
-13
View File
@@ -1,13 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import type { IncomingMessage } from 'node:http';
import type { Duplex } from 'node:stream';
import WebSocket from 'ws';
import type { Request } from './AbstractMessage.js';
import type { Options } from './BareServer.js';
export type BareHeaders = Record<string, string | string[]>;
export declare function randomHex(byteLength: number): string;
export declare function fetch(request: Request, signal: AbortSignal, requestHeaders: BareHeaders, remote: URL, options: Options): Promise<IncomingMessage>;
export declare function upgradeFetch(request: Request, signal: AbortSignal, requestHeaders: BareHeaders, remote: URL, options: Options): Promise<[res: IncomingMessage, socket: Duplex, head: Buffer]>;
export declare function webSocketFetch(request: Request, requestHeaders: BareHeaders, remote: URL, protocols: string[], options: Options): Promise<[req: IncomingMessage, socket: WebSocket]>;
-185
View File
@@ -1,185 +0,0 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.webSocketFetch = exports.upgradeFetch = exports.fetch = exports.randomHex = void 0;
const node_crypto_1 = require("node:crypto");
const node_http_1 = require("node:http");
const node_https_1 = require("node:https");
const ws_1 = __importDefault(require("ws"));
const BareServer_js_1 = require("./BareServer.js");
function randomHex(byteLength) {
const bytes = new Uint8Array(byteLength);
(0, node_crypto_1.getRandomValues)(bytes);
let hex = '';
for (const byte of bytes)
hex += byte.toString(16).padStart(2, '0');
return hex;
}
exports.randomHex = randomHex;
function outgoingError(error) {
if (error instanceof Error) {
switch (error.code) {
case 'ENOTFOUND':
return new BareServer_js_1.BareError(500, {
code: 'HOST_NOT_FOUND',
id: 'request',
message: 'The specified host could not be resolved.',
});
case 'ECONNREFUSED':
return new BareServer_js_1.BareError(500, {
code: 'CONNECTION_REFUSED',
id: 'response',
message: 'The remote rejected the request.',
});
case 'ECONNRESET':
return new BareServer_js_1.BareError(500, {
code: 'CONNECTION_RESET',
id: 'response',
message: 'The request was forcibly closed.',
});
case 'ETIMEOUT':
return new BareServer_js_1.BareError(500, {
code: 'CONNECTION_TIMEOUT',
id: 'response',
message: 'The response timed out.',
});
}
}
return error;
}
async function fetch(request, signal, requestHeaders, remote, options) {
if (options.filterRemote)
await options.filterRemote(remote);
const req = {
method: request.method,
headers: requestHeaders,
setHost: false,
signal,
localAddress: options.localAddress,
family: options.family,
lookup: options.lookup,
};
let outgoing;
// NodeJS will convert the URL into HTTP options automatically
// see https://github.com/nodejs/node/blob/e30e71665cab94118833cc536a43750703b19633/lib/internal/url.js#L1277
if (remote.protocol === 'https:')
outgoing = (0, node_https_1.request)(remote, {
...req,
agent: options.httpsAgent,
});
else if (remote.protocol === 'http:')
outgoing = (0, node_http_1.request)(remote, {
...req,
agent: options.httpAgent,
});
else
throw new RangeError(`Unsupported protocol: '${remote.protocol}'`);
request.body.pipe(outgoing);
return await new Promise((resolve, reject) => {
outgoing.on('response', (response) => {
resolve(response);
});
outgoing.on('upgrade', (req, socket) => {
reject('Remote did not send a response');
socket.destroy();
});
outgoing.on('error', (error) => {
reject(outgoingError(error));
});
});
}
exports.fetch = fetch;
async function upgradeFetch(request, signal, requestHeaders, remote, options) {
if (options.filterRemote)
await options.filterRemote(remote);
const req = {
headers: requestHeaders,
method: request.method,
timeout: 12e3,
setHost: false,
signal,
localAddress: options.localAddress,
family: options.family,
lookup: options.lookup,
};
let outgoing;
// NodeJS will convert the URL into HTTP options automatically
// see https://github.com/nodejs/node/blob/e30e71665cab94118833cc536a43750703b19633/lib/internal/url.js#L1277
// calling .replace on remote may look like it replaces other occurrences of wss:, but it only replaces the first which is remote.protocol
if (remote.protocol === 'wss:')
outgoing = (0, node_https_1.request)(remote.toString().replace('wss:', 'https:'), {
...req,
agent: options.httpsAgent,
});
else if (remote.protocol === 'ws:')
outgoing = (0, node_http_1.request)(remote.toString().replace('ws:', 'http:'), {
...req,
agent: options.httpAgent,
});
else
throw new RangeError(`Unsupported protocol: '${remote.protocol}'`);
outgoing.end();
return await new Promise((resolve, reject) => {
outgoing.on('response', (res) => {
reject(new Error('Remote did not upgrade the WebSocket'));
res.destroy();
});
outgoing.on('upgrade', (res, socket, head) => {
resolve([res, socket, head]);
});
outgoing.on('error', (error) => {
reject(outgoingError(error));
});
});
}
exports.upgradeFetch = upgradeFetch;
async function webSocketFetch(request, requestHeaders, remote, protocols, options) {
if (options.filterRemote)
await options.filterRemote(remote);
const req = {
headers: requestHeaders,
method: request.method,
timeout: 12e3,
setHost: false,
localAddress: options.localAddress,
family: options.family,
lookup: options.lookup,
};
let outgoing;
if (remote.protocol === 'wss:')
outgoing = new ws_1.default(remote, protocols, {
...req,
agent: options.httpsAgent,
});
else if (remote.protocol === 'ws:')
outgoing = new ws_1.default(remote, protocols, {
...req,
agent: options.httpAgent,
});
else
throw new RangeError(`Unsupported protocol: '${remote.protocol}'`);
return await new Promise((resolve, reject) => {
let request;
const cleanup = () => {
outgoing.removeEventListener('open', openListener);
outgoing.removeEventListener('open', openListener);
};
outgoing.on('upgrade', (req) => {
request = req;
});
const openListener = () => {
cleanup();
resolve([request, outgoing]);
};
const errorListener = (event) => {
cleanup();
reject(outgoingError(event.error));
};
outgoing.addEventListener('open', openListener);
outgoing.addEventListener('error', errorListener);
});
}
exports.webSocketFetch = webSocketFetch;
//# sourceMappingURL=requestUtil.js.map
-14
View File
@@ -1,14 +0,0 @@
import { Headers } from 'headers-polyfill';
/**
*
* Splits headers according to spec
* @param headers
* @returns Split headers
*/
export declare function splitHeaders(headers: Headers): Headers;
/**
* Joins headers according to spec
* @param headers
* @returns Joined headers
*/
export declare function joinHeaders(headers: Headers): Headers;
@@ -1,60 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.joinHeaders = exports.splitHeaders = void 0;
const headers_polyfill_1 = require("headers-polyfill");
const BareServer_js_1 = require("./BareServer.js");
const MAX_HEADER_VALUE = 3072;
/**
*
* Splits headers according to spec
* @param headers
* @returns Split headers
*/
function splitHeaders(headers) {
const output = new headers_polyfill_1.Headers(headers);
if (headers.has('x-bare-headers')) {
const value = headers.get('x-bare-headers');
if (value.length > MAX_HEADER_VALUE) {
output.delete('x-bare-headers');
let split = 0;
for (let i = 0; i < value.length; i += MAX_HEADER_VALUE) {
const part = value.slice(i, i + MAX_HEADER_VALUE);
const id = split++;
output.set(`x-bare-headers-${id}`, `;${part}`);
}
}
}
return output;
}
exports.splitHeaders = splitHeaders;
/**
* Joins headers according to spec
* @param headers
* @returns Joined headers
*/
function joinHeaders(headers) {
const output = new headers_polyfill_1.Headers(headers);
const prefix = 'x-bare-headers';
if (headers.has(`${prefix}-0`)) {
const join = [];
for (const [header, value] of headers) {
if (!header.startsWith(prefix)) {
continue;
}
if (!value.startsWith(';')) {
throw new BareServer_js_1.BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `Value didn't begin with semi-colon.`,
});
}
const id = parseInt(header.slice(prefix.length + 1));
join[id] = value.slice(1);
output.delete(header);
}
output.set(prefix, join.join(''));
}
return output;
}
exports.joinHeaders = joinHeaders;
//# sourceMappingURL=splitHeaderUtil.js.map
+1 -1
View File
@@ -1,4 +1,4 @@
import { createBareServer } from '@tomphttp/bare-server-node';
import { createBareServer } from '../lib/bare-server-modified/createServer.cjs';
import express from 'express';
import mime from 'mime';
import cors from 'cors';