moving to new repo
This commit is contained in:
-34
@@ -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
@@ -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>;
|
||||
}
|
||||
@@ -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
|
||||
Vendored
-33
@@ -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>;
|
||||
@@ -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
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import type Server from './BareServer.js';
|
||||
export default function registerV1(server: Server): void;
|
||||
@@ -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
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import type Server from './BareServer.js';
|
||||
export default function registerV2(server: Server): void;
|
||||
@@ -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
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
import type Server from './BareServer.js';
|
||||
export default function registerV3(server: Server): void;
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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]>;
|
||||
@@ -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
@@ -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
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user