const { checkServerIdentity } = require('tls'); const express = require('express'), app = express(), http = require('http'), https = require('https'), fs = require('fs'), querystring = require('querystring'), session = require('express-session'), sanitizer = require('sanitizer'), websocket = require('./ws-proxy.js'), fetch = require('node-fetch'), path = require("path"); const config = JSON.parse(fs.readFileSync('./config.json', { encoding: 'utf8' })); if (!config.prefix.startsWith('/')) { config.prefix = `/${config.prefix}`; } if (!config.prefix.endsWith('/')) { config.prefix = `${config.prefix}/`; } let server; let server_protocol; const server_options = { key: fs.readFileSync('./ssl/default.key'), cert: fs.readFileSync('./ssl/default.crt') } if (config.ssl == true) { server = https.createServer(server_options, app); server_protocol = 'https://'; } else { server = http.createServer(app); server_protocol = 'http://'; }; // WebSocket Proxying websocket(server); console.log(`Alloy Proxy now running on ${server_protocol}0.0.0.0:${config.port}! Proxy prefix is "${config.prefix}"!`); server.listen(process.env.PORT || config.port); btoa = (str) => { str = new Buffer.from(str).toString('base64'); return str; }; atob = (str) => { str = new Buffer.from(str, 'base64').toString('utf-8'); return str; }; rewrite_url = (dataURL, option) => { var websiteURL; var websitePath; if (option == 'decode') { websiteURL = atob(dataURL.split('/').splice(0, 1).join('/')); websitePath = '/' + dataURL.split('/').splice(1).join('/'); } else { websiteURL = btoa(dataURL.split('/').splice(0, 3).join('/')); websitePath = '/' + dataURL.split('/').splice(3).join('/'); } if (websitePath == '/') { return `${websiteURL}`; } else return `${websiteURL}${websitePath}`; }; app.use(session({ secret: 'alloy', saveUninitialized: true, resave: true, /*cookieName: '__alloy_cookie_auth=yes', duration: 30 * 60 * 1000, activeDuration: 5 * 60 * 1000 */ })); // We made our own version of body-parser instead, due to issues. app.use((req, res, next) => { if (req.method == 'POST') { req.raw_body = ''; req.on('data', chunk => { req.raw_body += chunk.toString(); // convert buffer to string }); req.on('end', () => { req.str_body = req.raw_body; try { req.body = JSON.parse(req.raw_body); } catch (err) { req.body = {} } next(); }); } else return next(); }); app.use(`${config.prefix}utils/`, async (req, res, next) => { if (req.url.startsWith('/assets/')) { res.sendFile(__dirname + '/utils' + req.url); } if (req.query.url) { let url = atob(req.query.url); if (url.startsWith('https://') || url.startsWith('http://')) { url = url; } else if (url.startsWith('//')) { url = 'http:' + url; } else { url = 'http://' + url; } return res.redirect(307, config.prefix + rewrite_url(url)); } }); /* //Cookie Auth app.use(checkAuth); app.use(auth); function auth(req, res, next) { let user = new User({ cookieName: '__alloy_cookie_auth=yes' }); if (!req.signedCookies.user) { var authHeader = req.headers.authorization; if (!authHeader) { var err = new Error('You are not authenticated!'); err.status = 401; next(err); return; } var auth = new Buffer(authHeader.split(' ')[1], 'base64').toString().split(':'); var pass = auth[1]; if (user == '__alloy_cookie_auth=yes') { res.cookie('user', 'admin', { signed: true }); next(); // authorized } else { var err = new Error('You are not authenticated!'); err.status = 401; next(err); } } else { if (req.signedCookies.user === 'admin') { next(); } else { var err = new Error('You are not authenticated!'); err.status = 401; next(err); } } }; // Check the auth of the routes => middleware functions function checkAuth(req, res, next) { console.log('checkAuth ' + req.url); // don 't serve /secure to those not logged in => /secure if for those who are logged in // you should add to this list, for each and every secure url if (req.url.indexOf(`${config.prefix}session/`) === 0 && (!req.session || !req.session.authenticated)) { res.render(fs.readFileSync('./utils/error/error.html', 'utf8').toString().replace('%ERROR%', `Error 401: The website '${sanitizer.sanitize(proxy.url.hostname)}' is not permitted!`), { status: 403 }); return; } xt(); } */ app.post(`${config.prefix}session/`, async (req, res, next) => { let url = querystring.parse(req.raw_body).url; if (url.startsWith('//')) { url = 'http:' + url; } else if (url.startsWith('https://') || url.startsWith('http://')) { url = url } else { url = 'http://' + url }; /* let cookies = {}; if (request.headers.cookie !== undefined) { cookies = cookie.parse(request.headers.cookie); } console.log(cookies); response.writeHead(200, { 'SET-Cookie': ['__alloy_cookie_auth=yes', `Permanent=Cookies; Max-Age=${60*60*24*30}`, 'Secure=Secure; Secure', 'HttpOnly=HttpOnly; HttpOnly', 'Path=Path; Path=/cookie' ] }) response.end('Coookie!!'); */ req.session.authenticated = true; }); app.use(config.prefix, async (req, res, next) => { var proxy = {}; proxy.url = rewrite_url(req.url.slice(1), 'decode'); proxy.url = { href: proxy.url, hostname: proxy.url.split('/').splice(2).splice(0, 1).join('/'), origin: proxy.url.split('/').splice(0, 3).join('/'), encoded_origin: btoa(proxy.url.split('/').splice(0, 3).join('/')), path: '/' + proxy.url.split('/').splice(3).join('/'), protocol: proxy.url.split('\:').splice(0, 1).join(''), } proxy.url.encoded_origin = btoa(proxy.url.origin); proxy.requestHeaders = req.headers; proxy.requestHeaders['host'] = proxy.url.hostname; if (proxy.requestHeaders['referer']) { let referer = '/' + String(proxy.requestHeaders['referer']).split('/').splice(3).join('/'); referer = rewrite_url(referer.replace(config.prefix, ''), 'decode'); if (referer.startsWith('https://') || referer.startsWith('http://')) { referer = referer; } else referer = proxy.url.href; proxy.requestHeaders['referer'] = referer; } if (proxy.requestHeaders['origin']) { let origin = '/' + String(proxy.requestHeaders['origin']).split('/').splice(3).join('/'); origin = rewrite_url(origin.replace(config.prefix, ''), 'decode'); if (origin.startsWith('https://') || origin.startsWith('http://')) { origin = origin.split('/').splice(0, 3).join('/'); } else origin = proxy.url.origin; proxy.requestHeaders['origin'] = origin; } if (proxy.requestHeaders.cookie) { delete proxy.requestHeaders.cookie; } const httpAgent = new http.Agent({ keepAlive: true }); const httpsAgent = new https.Agent({ rejectUnauthorized: false, keepAlive: true }); proxy.options = { method: req.method, headers: proxy.requestHeaders, redirect: 'manual', agent: function(_parsedURL) { if (_parsedURL.protocol == 'http:') { return httpAgent; } else { return httpsAgent; } } }; if (req.method == 'POST') { proxy.options.body = req.str_body; } if (proxy.url.hostname == 'discord.com' && proxy.url.path == '/') { return res.redirect(307, config.prefix + rewrite_url('https://discord.com/login')); }; if (proxy.url.hostname == 'www.reddit.com') { return res.redirect(307, config.prefix + rewrite_url('https://old.reddit.com')); }; if (!req.url.slice(1).startsWith(`${proxy.url.encoded_origin}/`)) { return res.redirect(307, config.prefix + proxy.url.encoded_origin + '/'); }; const blocklist = JSON.parse(fs.readFileSync('./blocklist.json', { encoding: 'utf8' })); let is_blocked = false; Array.from(blocklist).forEach(blocked_hostname => { if (proxy.url.hostname == blocked_hostname) { is_blocked = true; } }); if (is_blocked == true) { return res.send(fs.readFileSync('./utils/error/error.html', 'utf8').toString().replace('%ERROR%', `Error 401: The website '${sanitizer.sanitize(proxy.url.hostname)}' is not permitted!`)) } proxy.response = await fetch(proxy.url.href, proxy.options).catch(err => res.send(fs.readFileSync('./utils/error/error.html', 'utf8').toString().replace('%ERROR%', `Error 400: Could not make request to '${sanitizer.sanitize(proxy.url.href)}'!`))); if (typeof proxy.response.buffer != 'function') return; proxy.buffer = await proxy.response.buffer(); proxy.content_type = 'text/plain'; proxy.response.headers.forEach((e, i, a) => { if (i == 'content-type') proxy.content_type = e; }); if (proxy.content_type == null || typeof proxy.content_type == 'undefined') proxy.content_type = 'text/html'; proxy.sendResponse = proxy.buffer; // Parsing the headers from the response to remove square brackets so we can set them as the response headers. proxy.headers = Object.fromEntries( Object.entries(JSON.parse(JSON.stringify(proxy.response.headers.raw()))) .map(([key, val]) => [key, val[0]]) ); // Parsing all the headers to remove all of the bad headers that could affect proxies performance. Object.entries(proxy.headers).forEach(([header_name, header_value]) => { if (header_name.startsWith('content-encoding') || header_name.startsWith('x-') || header_name.startsWith('cf-') || header_name.startsWith('strict-transport-security') || header_name.startsWith('content-security-policy')) { delete proxy.headers[header_name]; } }); // If theres a location for a redirect in the response, then the proxy will get the response location then redirect you to the proxied version of the url. if (proxy.response.headers.get('location')) { return res.redirect(307, config.prefix + rewrite_url(String(proxy.response.headers.get('location')))); } res.status(proxy.response.status); res.set(proxy.headers); res.contentType(proxy.content_type); if (proxy.content_type.startsWith('text/html')) { req.session.url = proxy.url.origin; proxy.sendResponse = proxy.sendResponse.toString() .replace(/integrity="(.*?)"/gi, '') .replace(/nonce="(.*?)"/gi, '') .replace(/(href|src|poster|data|action|srcset)="\/\/(.*?)"/gi, `$1` + `="http://` + `$2` + `"`) .replace(/(href|src|poster|data|action|srcset)='\/\/(.*?)'/gi, `$1` + `='http://` + `$2` + `'`) .replace(/(href|src|poster|data|action|srcset)="\/(.*?)"/gi, `$1` + `="${config.prefix}${proxy.url.encoded_origin}/` + `$2` + `"`) .replace(/(href|src|poster|data|action|srcset)='\/(.*?)'/gi, `$1` + `='${config.prefix}${proxy.url.encoded_origin}/` + `$2` + `'`) .replace(/'(https:\/\/|http:\/\/)(.*?)'/gi, function(str) { str = str.split(`'`).slice(1).slice(0, -1).join(``); return `'${config.prefix}${rewrite_url(str)}'` }) .replace(/"(https:\/\/|http:\/\/)(.*?)"/gi, function(str) { str = str.split(`"`).slice(1).slice(0, -1).join(``); return `"${config.prefix}${rewrite_url(str)}"` }) .replace(/(window|document).location.href/gi, `"${proxy.url.href}"`) .replace(/(window|document).location.hostname/gi, `"${proxy.url.hostname}"`) .replace(/(window|document).location.pathname/gi, `"${proxy.url.path}"`) .replace(/location.href/gi, `"${proxy.url.href}"`) .replace(/location.hostname/gi, `"${proxy.url.hostname}"`) .replace(/location.pathname/gi, `"${proxy.url.path}"`) .replace(//gi, ``); // Temp hotfix for Youtube search bar until my script injection can fix it. if (proxy.url.hostname == 'www.youtube.com') { proxy.sendResponse = proxy.sendResponse.replace(/\/results/gi, `${config.prefix}${proxy.url.encoded_origin}/results`); }; } else if (proxy.content_type.startsWith('text/css')) { proxy.sendResponse = proxy.sendResponse.toString() .replace(/url\("\/\/(.*?)"\)/gi, `url("http://` + `$1` + `")`) .replace(/url\('\/\/(.*?)'\)/gi, `url('http://` + `$1` + `')`) .replace(/url\(\/\/(.*?)\)/gi, `url(http://` + `$1` + `)`) .replace(/url\("\/(.*?)"\)/gi, `url("${config.prefix}${proxy.url.encoded_origin}/` + `$1` + `")`) .replace(/url\('\/(.*?)'\)/gi, `url('${config.prefix}${proxy.url.encoded_origin}/` + `$1` + `')`) .replace(/url\(\/(.*?)\)/gi, `url(${config.prefix}${proxy.url.encoded_origin}/` + `$1` + `)`) .replace(/"(https:\/\/|http:\/\/)(.*?)"/gi, function(str) { str = str.split(`"`).slice(1).slice(0, -1).join(``); return `"${config.prefix}${rewrite_url(str)}"` }) .replace(/'(https:\/\/|http:\/\/)(.*?)'/gi, function(str) { str = str.split(`'`).slice(1).slice(0, -1).join(``); return `'${config.prefix}${rewrite_url(str)}'` }) .replace(/\((https:\/\/|http:\/\/)(.*?)\)/gi, function(str) { str = str.split(`(`).slice(1).join(``).split(')').slice(0, -1).join(''); return `(${config.prefix}${rewrite_url(str)})` }); }; // We send the response from the server rewritten. res.send(proxy.sendResponse); }); //Querystrings app.get('/', async (req, res) => { /* const path = require("path"); //Use this for path. fs.readFileSync( path, options ); Use this for improved navigation. Massive help from MikeLime and Duce. if (req.url == '/?querystringhere') { return res.send(fs.readFileSync(path.resolve() + 'filepath', { encoding: 'utf8' })); } */ /* var hbsites = {}; && hostname == hbsites */ switch (req.url) { case '/': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8')); } switch (req.url) { case '/?z': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'surf.html'), 'utf8')); } switch (req.url) { case '/?a': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'proxnav', 'alloy.html'), 'utf8')); } switch (req.url) { case '/?dd': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'redirects', 'discordprox.html'), 'utf8')); } switch (req.url) { case '/?b': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'proxnav', 'node.html'), 'utf8')); } switch (req.url) { case '/?y': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'proxnav', 'youtube.html'), 'utf8')); } switch (req.url) { case '/?e': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'proxnav', 'pydodge.html'), 'utf8')); } switch (req.url) { case '/?d': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'proxnav', 'discordhub.html'), 'utf8')); } switch (req.url) { case '/?c': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'credits.html'), 'utf8')); } switch (req.url) { case '/?f': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'flash.html'), 'utf8')); } switch (req.url) { case '/?g': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'gtools.html'), 'utf8')); } switch (req.url) { case '/?h': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'games5.html'), 'utf8')); } switch (req.url) { case '/?i': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'icons.html'), 'utf8')); } switch (req.url) { case '/?in': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'info.html'), 'utf8')); } switch (req.url == '/?k') { case '/?k': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'frames', 'krunker.html'), 'utf8')); } switch (req.url) { case '/?m': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'gba.html'), 'utf8')); } switch (req.url) { case '/?n': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'redirects', 'chatbox.html'), 'utf8')); } switch (req.url) { case '/?p': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'proxnav', 'pmprox.html'), 'utf8')); } switch (req.url) { case '/?t': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'terms.html'), 'utf8')); } switch (req.url) { case '/?x': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'nav', 'bookmarklets.html'), 'utf8')); } switch (req.url) { case '/?yh': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'redirects', 'ythub.html'), 'utf8')); } switch (req.url) { case '/?ym': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'pages', 'redirects', 'ytmobile.html'), 'utf8')); } switch (req.url) { case '/?fg': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'archive', 'f.html'), 'utf8')); } switch (req.url) { case '/?rr': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'archive', 'run.html'), 'utf8')); } // Frames Page switch (req.url) { case '/?j': return res.send(fs.readFileSync(path.join(__dirname, 'public', 'hidden.html'), 'utf8')) } }); app.use('/', express.static('public')); app.use(async (req, res, next) => { if (req.headers['referer']) { let referer = '/' + String(req.headers['referer']).split('/').splice(3).join('/'); referer = rewrite_url(referer.replace(config.prefix, ''), 'decode').split('/').splice(0, 3).join('/'); if (referer.startsWith('https://') || referer.startsWith('http://')) { res.redirect(307, config.prefix + btoa(referer) + req.url) } else { if (req.session.url) { res.redirect(307, config.prefix + btoa(req.session.url) + req.url) } else return next(); } } else if (req.session.url) { res.redirect(307, config.prefix + btoa(req.session.url) + req.url) } else return next(); });