Added Corrosion Fix for Heroku

Credits: https://github.com/BinBashBanana/Corrosion-Heroku (very epic person)
This commit is contained in:
TheEmeraldStarr 2021-11-29 23:40:38 -08:00
parent 8d88daf1c2
commit b0c48df26c
25 changed files with 13 additions and 10 deletions

22
lib/server/decompress.js Normal file
View file

@ -0,0 +1,22 @@
const zlib = require('zlib');
function decompress(ctx) {
if (!ctx.body || !ctx.remoteResponse) return;
try {
switch(ctx.headers['content-encoding']) {
case 'br':
ctx.body = zlib.brotliDecompressSync(ctx.body);
break;
case 'gzip':
ctx.body = zlib.gunzipSync(ctx.body);
break;
case 'deflate':
ctx.body = zlib.inflateRawSync(ctx.body);
break;
};
} catch(err) {};
delete ctx.headers['content-encoding'];
return true;
};
module.exports = decompress;

21
lib/server/gateway.js Normal file
View file

@ -0,0 +1,21 @@
function createGateway(ctx) {
return function gateway(clientRequest, clientResponse) {
const chunks = [];
clientRequest.on('data', chunk =>
chunks.push(chunk)
).on('end', () => {
const body = chunks.length ? Buffer.concat(chunks) : '';
const query = clientRequest.method == 'POST' ? new URLSearchParams((body || '').toString()) : new URLSearchParams((clientRequest.url.split('?')[1] || ''));
if (!query.has('url')) return clientResponse.end();
const url = query.get('url');
if (/https?:\/\/([a-zA-Z0-9\-\_])|([a-zA-Z0-9\-\_])\.([a-zA-Z])/.test(url)) {
clientResponse.writeHead(301, { Location: ctx.url.wrap(/https?:\/\//.test(url) ? url : 'http://' + url) });
clientResponse.end();
} else {
clientResponse.writeHead(301, { Location: ctx.url.wrap('https://www.google.com/search?q=' + url) });
clientResponse.end();
};
});
};
};
module.exports = createGateway;

45
lib/server/headers.js Normal file
View file

@ -0,0 +1,45 @@
function requestHeaders(ctx) {
if (ctx.headers.cookie && ctx.rewrite.config.cookie) ctx.headers.cookie = ctx.rewrite.cookies.decode(ctx.headers.cookie, { url: ctx.url, });
else delete ctx.headers.cookie;
if (ctx.headers.origin) {
if (ctx.clientSocket) {
const params = new URLSearchParams((ctx.clientRequest.url.split('?')[1] || ''));
delete ctx.headers.origin;
delete ctx.headers.host;
ctx.headers.Origin = params.get('origin') || ctx.url.origin;
ctx.headers.Host = ctx.url.host;
// Some websocket servers oddly only accept Host and Origin headers if the first character of the header is uppercase.
} else {
ctx.headers.origin = ctx.url.origin;
};
};
if (ctx.headers.referer) {
try {
ctx.headers.referer = new URL(ctx.rewrite.url.unwrap(ctx.headers.referer, { origin: ctx.origin, })).href;
} catch(err) {
ctx.headers.referer = ctx.url.href;
};
};
for (let header in ctx.headers) {
if (header.startsWith('cf-') || header.startsWith('x-forwarded') || header == 'cdn-loop') delete ctx.headers[header];
};
ctx.headers.host = ctx.url.host;
return true;
};
function responseHeaders(ctx) {
if (ctx.headers.location) ctx.headers.location = ctx.rewrite.url.wrap(ctx.headers.location, { base: ctx.url, origin: ctx.origin, });
if (ctx.headers['set-cookie']) ctx.headers['set-cookie'] = ctx.rewrite.cookies.encode(ctx.headers['set-cookie'], { domain: ctx.clientRequest.headers.host, url: ctx.url, });
[
'content-length',
'content-security-policy',
'content-security-policy-report-only',
'strict-transport-security',
'x-frame-options'
].forEach(name => delete ctx.headers[name]);
return true;
};
exports.requestHeaders = requestHeaders;
exports.responseHeaders = responseHeaders;

61
lib/server/index.js Normal file
View file

@ -0,0 +1,61 @@
const webpack = require('webpack');
const createWebSocketProxy = require('./upgrade');
const createRequestProxy = require('./request');
const createGateway = require('./gateway');
const middleware = {
...require('./headers'),
...require('./middleware'),
decompress: require('./decompress'),
rewriteBody: require('./rewrite-body'),
};
const path = require('path');
const fs = require('fs');
const defaultConfig = {
prefix: '/service/',
codec: 'plain',
forceHttps: false,
ws: true,
cookie: true,
title: 'Service',
requestMiddleware: [],
responseMiddleware: [],
standardMiddleware: true,
};
class Corrosion extends require('../rewrite') {
constructor(config = defaultConfig) {
super(Object.assign(defaultConfig, config));
if (this.config.standardMiddleware) {
this.config.requestMiddleware.unshift(
middleware.requestHeaders,
);
this.config.responseMiddleware.unshift(
middleware.responseHeaders,
middleware.decompress,
middleware.rewriteBody,
);
};
this.gateway = createGateway(this);
this.upgrade = createWebSocketProxy(this);
this.request = createRequestProxy(this);
if (!fs.existsSync(path.join(__dirname, 'bundle.js'))) this.bundleScripts();
};
bundleScripts() {
webpack({
mode: 'none',
entry: path.join(__dirname, '../../lib/browser/index.js'),
output: {
path: __dirname,
filename: 'bundle.js',
}
}, err =>
console.log(err || 'Bundled scripts')
);
};
get script() {
return fs.existsSync(path.join(__dirname, 'bundle.js')) ? fs.readFileSync(path.join(__dirname, 'bundle.js')) : 'Client script is still compiling or has crashed.'
};
};
Corrosion.middleware = middleware;
module.exports = Corrosion;

14
lib/server/middleware.js Normal file
View file

@ -0,0 +1,14 @@
function address(arr = []) {
return function (ctx) {
ctx.address = arr[Math.floor(Math.random() * arr.length)];
};
};
function blacklist(arr = [], page = '') {
return function (ctx) {
if (arr.includes(ctx.url.hostname)) ctx.clientResponse.end(page);
};
};
exports.address = address;
exports.blacklist = blacklist;

83
lib/server/request.js Normal file
View file

@ -0,0 +1,83 @@
const http = require('http');
const https = require('https');
function createRequestProxy(ctx) {
return async function onRequest(clientRequest, clientResponse) {
try {
if (new RegExp(`^${ctx.prefix}gateway/?`).test(clientRequest.url)) return ctx.gateway(clientRequest, clientResponse);
if (clientRequest.url.startsWith(`${ctx.prefix}index.js`)) {
clientResponse.setHeader('Content-Type', 'application/javascript');
return clientResponse.end(ctx.script);
};
const urlData = ctx.url.unwrap(clientRequest.url, { flags: true, leftovers: true, });
urlData.value = new URL(urlData.value);
const requestContext = {
url: urlData.value,
flags: urlData.flags,
origin: ((clientRequest.socket.encrypted || ctx.config.forceHttps) ? 'https://' : 'http://') + clientRequest.headers.host,
body: await getChunks(clientRequest),
headers: { ...clientRequest.headers },
method: clientRequest.method,
rewrite: ctx,
agent: new ((urlData.value.protocol == 'https:' || ctx.config.forceHttps) ? https : http).Agent({
rejectUnauthorized: false,
}),
address: null,
clientRequest,
clientResponse,
};
for (let i in ctx.config.requestMiddleware) ctx.config.requestMiddleware[i](requestContext);
if (clientResponse.writableEnded) return;
((requestContext.url.protocol == 'https:' || ctx.config.forceHttps) ? https : http).request({
headers: requestContext.headers,
method: requestContext.method,
hostname: requestContext.url.hostname,
port: requestContext.url.port,
path: requestContext.url.pathname + requestContext.url.search,
agent: requestContext.agent,
localAddress: requestContext.address,
rejectUnauthorized: false,
}, async remoteResponse => {
const responseContext = {
url: requestContext.url,
flags: requestContext.flags,
origin: requestContext.origin,
body: await getChunks(remoteResponse),
headers: { ...remoteResponse.headers },
statusCode: remoteResponse.statusCode,
agent: requestContext.agent,
address: requestContext.address,
method: requestContext.method,
rewrite: ctx,
clientRequest,
clientResponse,
remoteResponse,
};
for (let i in ctx.config.responseMiddleware) ctx.config.responseMiddleware[i](responseContext);
if (clientResponse.writableEnded) return;
clientResponse.writeHead(responseContext.statusCode, responseContext.headers);
clientResponse.end((responseContext.body || ''));
}).on('error', err => {
if (clientResponse.writableEnded) return;
clientResponse.setHeader('Content-Type', 'text/plain');
clientResponse.end(err.toString())
}).end(requestContext.body);
} catch(err) {
if (clientResponse.writableEnded) return;
clientResponse.setHeader('Content-Type', 'text/plain');
clientResponse.end(err.toString());
};
};
};
function getChunks(stream) {
const chunks = [];
return new Promise(resolve =>
stream.on('data', chunk =>
chunks.push(chunk)
).on('end', () =>
chunks.length ? resolve(Buffer.concat(chunks)) : resolve(null)
)
);
};
module.exports = createRequestProxy;

View file

@ -0,0 +1,36 @@
const route = [
{
types: ['text/html'],
handler: 'html',
},
{
types: ['text/css'],
handler: 'css',
},
{
types: ['application/javascript', 'application/x-javascript', 'text/javascript', 'text/x-javascript'],
handler: 'js',
},
]
function rewriteBody(ctx) {
if (!ctx.body || !ctx.remoteResponse || ctx.flags.includes('xhr')) return;
const meta = {
base: ctx.url,
origin: ctx.origin,
};
const data = route.find(entry => ctx.flags == entry.handler) || route.find(entry => entry.types.includes((ctx.headers['content-type'] || '').split(';')[0])) || {};
switch(data.handler) {
case 'html':
ctx.body = ctx.rewrite.html.process(ctx.body.toString(), { ...meta, document: true });
break;
case 'css':
ctx.body = ctx.rewrite.css.process(ctx.body.toString(), meta);
break;
case 'js':
ctx.body = ctx.rewrite.js.process(ctx.body.toString(), ctx.url);
break;
};
};
module.exports = rewriteBody;

56
lib/server/upgrade.js Normal file
View file

@ -0,0 +1,56 @@
const http = require('http');
const https = require('https');
function createWebSocketProxy(ctx) {
return function onUpgrade(clientRequest, clientSocket, clientHead) {
try {
const urlData = ctx.url.unwrap(clientRequest.url, { flags: true, });
urlData.value = new URL(urlData.value);
const requestContext = {
url: urlData.value,
flags: urlData.flags,
body: null,
headers: { ...clientRequest.headers },
method: clientRequest.method,
rewrite: ctx,
agent: new ((urlData.value.protocol == 'https:' || ctx.config.forceHttps) ? https : http).Agent({
rejectUnauthorized: false,
}),
address: null,
clientRequest,
clientSocket,
clientHead,
};
ctx.config.requestMiddleware.forEach(fn => fn(requestContext));
((requestContext.url.protocol == 'https:' || ctx.config.forceHttps) ? https : http).request({
headers: requestContext.headers,
method: requestContext.method,
hostname: requestContext.url.hostname,
port: requestContext.url.port,
path: requestContext.url.pathname + requestContext.url.search,
agent: requestContext.agent,
localAddress: requestContext.address,
}).on('upgrade', (remoteResponse, remoteSocket, remoteHead) => {
let handshake = 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n';
for (let key in remoteResponse.headers) {
handshake += `${key}: ${remoteResponse.headers[key]}\r\n`;
};
handshake += '\r\n';
clientSocket.write(handshake);
clientSocket.write(remoteHead);
remoteSocket.on('close', () => clientSocket.end());
clientSocket.on('close', () => remoteSocket.end());
remoteSocket.on('error', () => clientSocket.end());
clientSocket.on('error', () => remoteSocket.end());
remoteSocket.pipe(clientSocket);
clientSocket.pipe(remoteSocket);
}).on('error', () => {
clientSocket.end()
}).end();
} catch(err) {
clientSocket.end();
};
};
};
module.exports = createWebSocketProxy;