From 27b55eb78ee4b80b2e34f7f73f66941af32a9dad Mon Sep 17 00:00:00 2001 From: TheEmeraldStarr <46467239+Epicloudygamer@users.noreply.github.com> Date: Sun, 28 Feb 2021 12:01:47 -0800 Subject: [PATCH] Minor Edits --- app.js | 4 +- package.json | 2 +- src/alloyproxy/README.md | 156 +++++++++++++++ src/alloyproxy/examples/app.js | 29 +++ src/alloyproxy/index.js | 33 ++++ src/alloyproxy/libs/proxy.js | 275 +++++++++++++++++++++++++++ src/alloyproxy/libs/requests.js | 142 ++++++++++++++ src/alloyproxy/libs/rewriting.js | 187 ++++++++++++++++++ src/alloyproxy/libs/static/inject.js | 109 +++++++++++ src/alloyproxy/libs/websocket.js | 110 +++++++++++ src/alloyproxy/package.json | 56 ++++++ views/expr/surf.js | 4 +- 12 files changed, 1102 insertions(+), 5 deletions(-) create mode 100644 src/alloyproxy/README.md create mode 100644 src/alloyproxy/examples/app.js create mode 100644 src/alloyproxy/index.js create mode 100644 src/alloyproxy/libs/proxy.js create mode 100644 src/alloyproxy/libs/requests.js create mode 100644 src/alloyproxy/libs/rewriting.js create mode 100644 src/alloyproxy/libs/static/inject.js create mode 100644 src/alloyproxy/libs/websocket.js create mode 100644 src/alloyproxy/package.json diff --git a/app.js b/app.js index 50cfd7a2..c613ab21 100644 --- a/app.js +++ b/app.js @@ -2,7 +2,7 @@ /* Author : QuiteAFancyEmerald, YÖCTDÖNALD'S and SexyDuceDuce with help from Divide /* MIT license: http://opensource.org/licenses/MIT /* ----------------------------------------------- */ -const [express, alloy, http, fs, path, char_insert] = [require('express'), require('alloyproxy'), require('http'), require('fs'), require('path'), require('./src/charinsert.js')], [app, config] = [express(), JSON.parse(fs.readFileSync('./config.json', { encoding: 'utf8' }))], server = http.createServer(app), localprox = new alloy({ +const [express, alloy, http, fs, path, char_insert] = [require('express'), require('./src/alloyproxy'), require('http'), require('fs'), require('path'), require('./src/charinsert.js')], [app, config] = [express(), JSON.parse(fs.readFileSync('./config.json', { encoding: 'utf8' }))], server = http.createServer(app), localprox = new alloy({ prefix: '/fetch/', error: (proxy) => { proxy.res.send(fs.readFileSync('./views/error.html', { encoding: 'utf8' }).replace('%ERR%', proxy.error.info.message.replace(//gi, '>'))); }, // Doing replace functions on "<" and ">" to prevent XSS. request: [], @@ -237,4 +237,4 @@ atob = (str) => { return str; }; -*/ +*/ \ No newline at end of file diff --git a/package.json b/package.json index 392fba28..24ab713a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "author": "Titanium Network", "license": "MIT", "dependencies": { - "alloyproxy": "^1.1.0", + "ws": "^7.4.3", "cookie-parser": "^1.4.5", "express": "^4.17.1", "express-session": "^1.17.1", diff --git a/src/alloyproxy/README.md b/src/alloyproxy/README.md new file mode 100644 index 00000000..43c9f6a0 --- /dev/null +++ b/src/alloyproxy/README.md @@ -0,0 +1,156 @@ +# AlloyProxy +Module specialized in proxying websites to unblock the web. + +## Table of contents: + +- [Setup](#how-to-use) + - [Module Use](#module-use) + - [Sample Implementation](#sample-express-application) + - [Sample Implementation Extended](#sample-implementation) + - [Configurations](#configurations) + - [General Use](#general-use) + - [Extended Configuration Information](#extended-configuration-information) + - [Websocket Proxy Information](#websocket-proxy-information) + +### Module Use + +1. `npm install alloyproxy` + +2. Set all of your configs in the main file for the Node app. + +3. Start up your app and unblock a website at `/prefix/[BASE64 ENCODED WEBSITE ORIGIN]/`. The path of the website does not have to be B64 encoded. + +A good example of what code to use is here using the Express.js framework. + +### Sample Express Application +1. Navigate to the `/examples/` folder. + +2. Do the following commands: + +``` +cd examples/express + +npm install + +npm start +``` + +The demo application will run at `localhost:8080` by default however the port can be configured in `config.json`. + +The static folder provides you with the base site if you wish to go manual about this. + +### Sample Implementation +Add this to your server-side script ex. "app.js". +``` +// Note: make sure you use Alloy before any other Express middleware that sends responses to client or handles POST data. + +const Alloy = require('alloyproxy'), + http = require('http'), + express = require('express'), + app = express(); + +const server = http.createServer(app); + +const Unblocker = new Alloy({ + prefix: '/fetch/', + request: [], + response: [], + injection: true, +}); + +// The main part of the proxy. + +app.use(Unblocker.app); + +// WebSocket handler. + +Unblocker.ws(server); + +server.listen('8080') + +``` + +## Configurations +### General Use + +``` + prefix: '/prefix/', + blocklist: [], + // error: (proxy) => { return proxy.res.end('proxy.error.info.message') }, Custom error handling which is optional. + request: [], // Add custom functions before request is made or modify the request. + response: [], // Add custom functions after the request is made or modify the response. + injection: true, // Script injection which is helpful in rewriting window.fetch() and all kinds of client-side JS requests. + requestAgent: null, // Set a custom agent to use in the request. + // userAgent: Uses the clients "User-Agent" request header by default. More customizable using the "request" option in the configs. + localAddress: [] // Neat feature in basic http(s).request() to choose what IP to use to make the request. Will be randomized if there is multiple. +``` + +### Extended Configuration Information + +To use the "request" and "response" options in the config. You must make a function like this for example. + +``` +customFunction = (proxy) => { + + if (proxy.url.hostname == 'example.org' && proxy.response.headers['content-type'].startsWith('text/html')) { + + return proxy.sendResponse == proxy.sendResponse.toString().replace(/example/gi, 'cat :3'); + + }; + +}; + +new Alloy({ +prefix: '/prefix/', +blocklist: [], +// error: (proxy) => { return proxy.res.end('proxy.error.info.message') }, Custom error handling which is optional. +request: [], // Add custom functions before request is made or modify the request. +response: [ + + customFunction + +], // Add custom functions after the request is made or modify the response. +injection: true, // Script injection which is helpful in rewriting window.fetch() and all kinds of client-side JS requests. +requestAgent: null, // Set a custom agent to use in the request. +// userAgent: Uses the clients "User-Agent" request header by default. More customizable using the "request" option in the configs. +localAddress: [] // Neat feature in basic http(s).request() to choose what IP to use to make the request. Will be randomized if there is multiple. +}) +``` + +What this will do is when the hostname of a website being accessed is `example.org`. The console sends you "weee :3". If you want a preview of what options you have, heres a list. :) + +``` + +// Basic HTTP functions. + +proxy.req // This is the request option in HTTP servers. If Express.js is being used, you can use Express.js functions. +proxy.res // This is the response option in HTTP servers. If Express.js is being used, you can use Express.js functions. +proxy.next() // This is only avaliable in Express.js . If used in native HTTP, the app will display blank text as a filler. + +// Request + +proxy.request.headers // A modified version of the client's request headers used in sending the request. +proxy.request.method // The clients request method. +proxy.request.body // The POST body of a POST / PATCH request. + +// Response + +proxy.response // The entire response of the website. Contains headers, JSON / text response, and all Node.js http(s).request() response data. +proxy.response.headers // Response headers the website gave back. Is modified to filter out bad headers, and rewrite "Set-Cookie" header. +proxy.sendResponse // The modified response buffer the website gave back. You can modify it in anyway you desire. :) + +// Errors + +proxy.error.status // Outputs "true" when theres an error. +proxy.error.info // Gives information about an error. +proxy.error.info.code // Gives error code. Error codes such as "ENOTFOUND" mean a website could not be found. "BLOCKED" means a website is blocked. +proxy.error.info.message // Gives error message. +proxy.blocked.status // Outputs "true" when a filtered hostname is detected. + + +``` + +## Websocket Proxy Information + +Alloy does come with a built in websocket proxy. To use it, you must have an HTTP server already defined. The example of using Alloy as Express middleware already uses the websocket proxy. + diff --git a/src/alloyproxy/examples/app.js b/src/alloyproxy/examples/app.js new file mode 100644 index 00000000..6ce76327 --- /dev/null +++ b/src/alloyproxy/examples/app.js @@ -0,0 +1,29 @@ +// Note: make sure you use Alloy before any other Express middleware that sends responses to client or handles POST data. + +const Alloy = require('alloyproxy'), + http = require('http'), + express = require('express'), + app = express(); + +const server = http.createServer(app); + +const Unblocker = new Alloy({ + prefix: '/fetch/', + request: [], + response: [], + injection: true, +}); + +// The main part of the proxy. + +app.use(Unblocker.app); + + +// Do your stuff here! :3 + +// WebSocket handler. + +Unblocker.ws(server); + + +server.listen('8080') diff --git a/src/alloyproxy/index.js b/src/alloyproxy/index.js new file mode 100644 index 00000000..e6199853 --- /dev/null +++ b/src/alloyproxy/index.js @@ -0,0 +1,33 @@ +module.exports = class { + + constructor(config) { + + // If parts of the config are messing. The parts are filled with placeholders. + + if (!config) config = {}; + + if (!config.prefix) config.prefix = '/get/'; + + if (!config.prefix.startsWith('/')) config.prefix = '/' + config.prefix; + + if (!config.prefix.endsWith('/')) config.prefix = config.prefix + '/'; + + if (!config.blocklist) config.blocklist = []; + + if (!config.request) config.request = []; + + if (!config.response) config.response = []; + + this.config = config; + + // Main proxy. + + this.app = require('./libs/proxy.js')(config); + + // WebSocket Proxy. + + this.ws = (server) => require('./libs/websocket.js')(server, config); + + } + + } \ No newline at end of file diff --git a/src/alloyproxy/libs/proxy.js b/src/alloyproxy/libs/proxy.js new file mode 100644 index 00000000..911e9967 --- /dev/null +++ b/src/alloyproxy/libs/proxy.js @@ -0,0 +1,275 @@ +const sendRequest = require('./requests.js'), + rewrite = require('./rewriting.js'), + fs = require('fs'), + bodyParser = (req) => { + + return new Promise(resolve => { + + var body = ''; + + req.on('data', chunk => { + + body += chunk; + + }).on('end', () => { + + resolve(body); + + }); + + }); + + }; + + +module.exports = (config) => { + + return async (req, res, next) => { + + // To have compatibility with both native Node.js HTTP and Express.js. + + if (typeof next != 'function') next = () => res.end(''); + + if (typeof config.injection == 'undefined' || typeof config.injection == 'null') config.injection = true; + + if (req.url.startsWith(config.prefix) && config.injection == true) { + + // Setting up public directory for injection scripts. + + if (req.url.startsWith(`${config.prefix}static/`)) { + + path = req.url.toString().replace(`${config.prefix}static`, ''); + + if (path.includes('?')) path = path.split('?').splice(0, 1).join(''); + + if (path.includes('#')) path = path.split('#').splice(0, 1).join(''); + + try { + + return res.end(fs.readFileSync(__dirname + `/static${path}`, { + encoding: 'utf8' + })); + + } catch (err) { + return res.end('') + }; + + } + + // Setting configurations for errors, requests, urls, and blocklist. + + var proxy = { + + request: { + + // Defined later on. Uses clients request headers as the headers used when making the request to the server, although its slightly modifed to work. + + headers: {}, + + // Using the clients request method as the method used when making the request to the server.0 + + method: req.method, + + // When this is true, the response of websites with invalid SSL certs will not be given. + + rejectUnauthorized: false + + }, + + error: { + + status: false, + + info: null + + }, + + blocked: { + + status: false, + + }, + + prefix: config.prefix, + + // Can be used to create extra functions. + + req: req, + res: res, + next: next, + + }; + + try { proxy.url = new URL(rewrite.url(req.url.replace(config.prefix, ''), 'decode')); } catch(err) { + + proxy.error.status = true; + + // Using 404 error as a filler for this. + + proxy.error.info = { + + code: 'ENOTFOUND', + message: `Could not make ${req.method} request to "${rewrite.url(req.url.replace(config.prefix, ''), 'decode')}".` + + }; + + if (config.error) return config.error(proxy); + + return res.end(proxy.error.info.message.replace(//gi, '>')); + + }; + + proxy.injection = config.injection; + + Object.entries(req.headers).forEach(([header_name, header_value]) => proxy.request.headers[header_name] = header_value); + + delete proxy.request.headers['host']; + + // Rewriting "Referer" and "Origin" headers to be accurate as possible in the request. + + if (proxy.request.headers['referer']) proxy.request.headers['referer'] = rewrite.referer(proxy.request.headers['referer'], proxy) + + if (proxy.request.headers['origin']) proxy.request.headers['origin'] = rewrite.origin(proxy.request.headers['origin'], proxy) + + // Forcing character limit on Cookie header because too many cookies in the header can lead to a "Header Overflow" error. Will most likely be replaced in the future. + + + if (proxy.request.headers['cookie']) { + + var new_cookie = [], + cookie_array = proxy.request.headers['cookie'].split('; '); + + cookie_array.forEach(cookie => { + + cookie_name = cookie.split('=').splice(0, 1).join(); + + cookie_value = cookie.split('=').splice(1).join(); + + if (proxy.url.hostname.includes(cookie_name.split('@').splice(1).join())) new_cookie.push(cookie_name.split('@').splice(0, 1).join() + '=' + cookie_value); + + }); + + proxy.request.headers['cookie'] = new_cookie.join('; '); + + } + + // If theres a user agent in the config, use that user agent instead of using the browsers user agent by default. + + if (config.userAgent) proxy.request.headers['user-agent'] = config.useragent; + + if (config.requestAgent) proxy.request['agent'] = config.requestAgent; + + delete proxy.request.headers['accept-encoding']; + + // Getting data from our body parser for HTTP (POST / PATCH) requests. + + if (req.method == 'POST' || req.method == 'PATCH') proxy.request.body = await bodyParser(req); + + config.blocklist.forEach(hostname => { + + if (proxy.url.hostname == hostname) { + + proxy.error.status = true; + + proxy.blocked.status = true; + + proxy.error.info = { + + code: 'BLOCKED', + + message: `Could not make ${req.method} request to "${rewrite.url(req.url.replace(config.prefix, ''), 'decode')}".` + + } + + } + + }); + + // If URL hostname has been detected as blocked, the app blocks all further functions and sends a different response to the client. + + if (proxy.blocked.status == true) { + + if (config.error) return config.error(proxy); + + return res.end(proxy.error.info.message.replace(//gi, '>')); + + } + // In http.request(), there is a feature to choose what IP you want to use when making the request. This is useful when your server has additional IP's. + // When there is multiple IP's in the array, the proxy randomizes which one to use. This is useful to bypass Youtube's request checking. + + if (config.localAddress) proxy.request.localAddress = config.localAddress[Math.floor(Math.random() * config.localAddress.length)] + + // Allowing custom functions to be set before the request is made. + + config.request.forEach(customFunction => customFunction(proxy)); + + // The request is made to the website, and is awaiting response. + + // Error handling. + + if (!req.url.startsWith(config.prefix + btoa(proxy.url.origin) + '/')) { + res.writeHead(307, { + Location: config.prefix + btoa(proxy.url.origin) + '/' + }); + return res.end(); + }; + + proxy.response = await sendRequest(proxy.url.href, proxy.request).catch(err => { + + proxy.error.status = true; + + proxy.error.info = { + + code: err.code, + + message: `Could not make ${req.method} request to "${rewrite.url(req.url.replace(config.prefix, ''), 'decode')}".` + + }; + + }); + + + if (proxy.error.status == true) { + + if (config.error) return config.error(proxy); + + return res.end(proxy.error.info.message.replace(//gi, '>')); + + } + + proxy.sendResponse = await proxy.response.buffer; + + // Filtering out bad headers, setting redirect locations, and rewriting cookies. + + rewrite.headers(proxy); + + // Setting response status and headers. + + res.writeHead(proxy.response.statusCode, proxy.response.headers); + + // When rewriting, the "Content-Type" header is extracted, and if it matches ("text/html" | "text/css") the response body gets rewritten. But when the request is POST, "Content-Type" is undefined so we set the header to "text/html" which won't effect POST responses. + + if (typeof proxy.response.headers['content-type'] == 'null' || typeof proxy.response.headers['content-type'] == 'undefined') proxy.response.headers['content-type'] = 'text/html'; + + // Rewriting body based off of "Content-Type" header to not mess up any images or javascript types. + + if (proxy.response.headers['content-type'].startsWith('text/html') || proxy.response.headers['content-type'].startsWith('text/css')) { + + proxy.sendResponse = rewrite.body(proxy.sendResponse.toString(), proxy) + + }; + + + // Allowing custom functions to be set after response is made and rewritten. + + config.response.forEach(customFunction => customFunction(proxy)); + + // Sending response to the client. + + res.end(proxy.sendResponse); + + } else next(); + + }; + +} \ No newline at end of file diff --git a/src/alloyproxy/libs/requests.js b/src/alloyproxy/libs/requests.js new file mode 100644 index 00000000..2be1d448 --- /dev/null +++ b/src/alloyproxy/libs/requests.js @@ -0,0 +1,142 @@ +const http = require('http'), + https = require('https'), + zlib = require('zlib'); + + +module.exports = (url, options) => { + + + if (!options) options = {}; + + var request = {}; + + request.options = options; + + var protocol; + + if (url.startsWith('https://')) { + protocol = https + } else protocol = http; + + return new Promise((resolve, error) => { + + var req = protocol.request(url, request.options, res => { + + var response = res; + + response.json = new Promise(resolve => { + + var body = '', + json = ''; + + res.on('data', chunk => body += chunk); + + res.on('end', () => { + + try { + + json = JSON.parse(body); + + } catch (err) { + + json = {}; + + } + + resolve(json); + + }); + + }); + + response.text = new Promise(resolve => { + + var data = '', + text = ''; + + res.on('data', chunk => data = chunk.toString()); + + res.on('end', () => { + text = data; + resolve(text); + }); + + }); + + response.buffer = new Promise(resolve => { + + var buffer = []; + + res.on('data', binary => { + + buffer.push(binary) + + }).on('end', () => { + + buffer = Buffer.concat(buffer) + + switch (res.headers['content-encoding']) { + + case 'gzip': + case 'x-gzip': + + zlib.gunzip(buffer, (err, buffer) => { + + resolve(buffer); + + }); + + break; + + case 'deflate': + case 'x-deflate': + + zlib.inflate(buffer, (err, buffer) => { + + resolve(buffer); + + }); + + break; + + case 'br': + + zlib.BrotliDecompress(buffer, (err, buffer) => { + + resolve(buffer); + + }); + + break; + + default: + + resolve(buffer); + + break; + + }; + + }); + + }); + + resolve(response); + + }); + + req.on('error', err => { + + error(err); + + }); + + if (options.body) { + req.write(options.body); + req.end(); + } else req.end(); + + + }); + +} \ No newline at end of file diff --git a/src/alloyproxy/libs/rewriting.js b/src/alloyproxy/libs/rewriting.js new file mode 100644 index 00000000..44018661 --- /dev/null +++ b/src/alloyproxy/libs/rewriting.js @@ -0,0 +1,187 @@ +// Base64 encoding and decoding functions. +btoa = (str) => { + + str = new Buffer.from(str).toString('base64'); + + return str; + +}; + + +atob = (str) => { + + str = new Buffer.from(str, 'base64').toString('utf-8'); + + return str; + +}; + + +// Rewriting URL's. + +url = (url, type) => { + + if (url.startsWith('//')) url = 'http:' + url; + + var origin, path; + + switch (type) { + + case 'decode': + + origin = atob(url.split('/').splice(0, 1).join('/')); + + path = '/' + url.split('/').splice(1).join('/'); + + break; + + default: + + origin = btoa(url.split('/').splice(0, 3).join('/')); + + path = '/' + url.split('/').splice(3).join('/'); + + break; + + } + + return origin + path; + +} + + +// Rewriting response buffers to send to client. + +body = (buffer, config) => { + + + proxified_body = buffer.toString() + .replace(/integrity="(.*?)"/gi, '') + .replace(/nonce="(.*?)"/gi, '') + .replace(/(href|src|poster|data|action|srcset|data-src|data-href)="\/\/(.*?)"/gi, `$1` + `="http://` + `$2` + `"`) + .replace(/(href|src|poster|data|action|srcset|data-src|data-href)='\/\/(.*?)'/gi, `$1` + `='http://` + `$2` + `'`) + .replace(/(href|src|poster|data|action|srcset|data-src|data-href)="\/(.*?)"/gi, `$1` + `="${config.prefix}${btoa(config.url.origin)}/` + `$2` + `"`) + .replace(/(href|src|poster|data|action|srcset|data-src|data-href)='\/(.*?)'/gi, `$1` + `='${config.prefix}${btoa(config.url.origin)}/` + `$2` + `'`) + .replace(/(href|src|poster|data|action|srcset|data-src|data-href)="(https:\/\/|http:\/\/)(.*?)"/gi, str => { + + attribute = str.split('=').splice(0, 1).join(''); + + href = str.replace(`${attribute}=`, '').slice(1).slice(0, -1); + + return `${attribute}="${config.prefix}${url(href)}"`; + + }) + .replace(/(href|src|poster|data|action|srcset|data-src|data-href)='(https:\/\/|http:\/\/)(.*?)'/gi, str => { + + attribute = str.split('=').splice(0, 1).join(''); + + href = str.replace(`${attribute}=`, '').slice(1).slice(0, -1); + + return `${attribute}='${config.prefix}${url(href)}'`; + + }) + .replace(/(window|document).location.href/gi, `"${config.url.href}"`) + .replace(/(window|document).location.hostname/gi, `"${config.url.hostname}"`) + .replace(/(window|document).location.pathname/gi, `"${config.url.path}"`) + .replace(/location.href/gi, `"${config.url.href}"`) + .replace(/location.hostname/gi, `"${config.url.hostname}"`) + .replace(/location.pathname/gi, `"${config.url.path}"`) + .replace(/url\("\/\/(.*?)"\)/gi, `url("http://` + `$1` + `")`) + .replace(/url\('\/\/(.*?)'\)/gi, `url('http://` + `$1` + `')`) + .replace(/url\(\/\/(.*?)\)/gi, `url(http://` + `$1` + `)`) + .replace(/url\("\/(.*?)"\)/gi, `url("${config.prefix}${btoa(config.url.origin)}/` + `$1` + `")`) + .replace(/url\('\/(.*?)'\)/gi, `url('${config.prefix}${btoa(config.url.origin)}/` + `$1` + `')`) + .replace(/url\(\/(.*?)\)/gi, `url(${config.prefix}${btoa(config.url.origin)}/` + `$1` + `)`); + + if (config.injection == true) { + + proxified_body = proxified_body.replace(/
/gi, ` \n `); + + if (!proxified_body.match(//gi)) proxified_body = proxified_body.replace(//gi, ` \n `); + + }; + + return proxified_body; +}; + +// Rewriting the "Origin" request header. + +origin = (origin, config) => { + + origin = '/' + String(origin).split('/').splice(3).join('/'); + + origin = url(origin.replace(config.prefix, ''), 'decode'); + + if (origin.startsWith('https://') || origin.startsWith('http://')) { + + origin = origin.split('/').splice(0, 3).join('/'); + + } else origin = config.url.origin; + + return origin; +}; + +// Rewriting the "Referer" request header. + +referer = (referer, config) => { + + referer = '/' + String(referer).split('/').splice(3).join('/'); + + referer = url(referer.replace(config.prefix, ''), 'decode'); + + if (referer.startsWith('https://') || referer.startsWith('http://')) { + referer = referer; + + } else referer = config.url.href; + + return referer; + +}; + + +headers = (proxy) => { + + Object.entries(proxy.response.headers).forEach(([header_name, header_value]) => { + + if (header_name == 'location') { + proxy.response.statusCode = 308; + proxy.response.headers[header_name] = proxy.prefix + url(header_value); + }; + + if (header_name == 'set-cookie') { + + var array = []; + + header_value.forEach(cookie => { + + cookie = cookie.replace(/Domain=(.*?);/gi, `Domain=` + proxy.req.headers['host'] + ';').replace(/(.*?)=(.*?);/, '$1' + '@' + proxy.url.hostname + `=` + '$2' + ';'); + + array.push(cookie); + + }); + + proxy.response.headers[header_name] = array; + + }; + + 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') || header_name.startsWith('content-length')) { + + delete proxy.response.headers[header_name]; + + }; + + }); + +}; + +// Setting all our functions to be used in "proxy.js" + +module.exports = { + + url: url, + body: body, + origin: origin, + referer: referer, + headers: headers + +} diff --git a/src/alloyproxy/libs/static/inject.js b/src/alloyproxy/libs/static/inject.js new file mode 100644 index 00000000..f3c04d1b --- /dev/null +++ b/src/alloyproxy/libs/static/inject.js @@ -0,0 +1,109 @@ +var alloy_data = document.querySelector('#_alloy_data'); + +var prefix = alloy_data.getAttribute('prefix'); + +var url = new URL(atob(alloy_data.getAttribute('url'))) + +rewrite_url = (str) => { + proxied_url = ''; + if (str.startsWith(window.location.origin + '/') && !str.startsWith(window.location.origin + prefix)) { + str = '/' + str.split('/').splice(3).join('/'); + } + if (str.startsWith('//')) { + str = 'http:' + str; + } else if (str.startsWith('/') && !str.startsWith(prefix)) { + str = url.origin + str + } + if (str.startsWith('https://') || str.startsWith('http://')) { + path = "/" + str.split('/').splice(3).join('/'); + origin = btoa(str.split('/').splice(0, 3).join('/')); + return proxied_url = prefix + origin + path + } else { + proxied_url = str; + } + return proxied_url; + } + + +let fetch_rewrite = window.fetch; window.fetch = function(url, options) { + url = rewrite_url(url); + return fetch_rewrite.apply(this, arguments); +} + +let xml_rewrite = window.XMLHttpRequest.prototype.open;window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) { + url = rewrite_url(url); + return xml_rewrite.apply(this, arguments); + } + +let createelement_rewrite = document.createElement; document.createElement = function(tag) { + var element = createelement_rewrite.call(document, tag); + if (tag.toLowerCase() === 'script' || tag.toLowerCase() === 'iframe' || tag.toLowerCase() === 'embed') { + Object.defineProperty(element.__proto__, 'src', { + set: function(value) { + value = rewrite_url(value) + element.setAttribute('src', value) + } + }); + } else if (tag.toLowerCase() === 'link') { + Object.defineProperty(element.__proto__, 'href', { + set: function(value) { + value = rewrite_url(value) + element.setAttribute('href', value) + } + }); + } else if (tag.toLowerCase() === 'form') { + Object.defineProperty(element.__proto__, 'action', { + set: function(value) { + value = rewrite_url(value) + element.setAttribute('action', value) + } + }); + } + return element; +} + +let setattribute_rewrite = window.Element.prototype.setAttribute; window.Element.prototype.setAttribute = function(attribute, href) { + if (attribute == ('src') || attribute == ('href') || attribute == ('action')) { + href = rewrite_url(href) + } else href = href; + return setattribute_rewrite.apply(this, arguments) + } + +// Rewriting all incoming websocket request. + + WebSocket = new Proxy(WebSocket, { + + construct(target, args_array) { + + var protocol; + + if (location.protocol == 'https:') { protocol = 'wss://' } else { protocol = 'ws://' } + + args_array[0] = protocol + location.origin.split('/').splice(2).join('/') + prefix + 'ws/' + btoa(args_array[0]); + + return new target(args_array); + } + + }); + + // Rewriting incoming pushstate. + + history.pushState = new Proxy(history.pushState, { + + apply: (target, thisArg, args_array) => { + + args_array[2] = rewrite_url(args_array[2]) + + return target.apply(thisArg, args_array) + } + + }); + +var previousState = window.history.state; +setInterval(function() { + + if (!window.location.pathname.startsWith(`${prefix}${btoa(url.origin)}/`)) { + + history.replaceState('', '', `${prefix}${btoa(url.origin)}/${window.location.href.split('/').splice(3).join('/')}`); + } +}, 0.1); \ No newline at end of file diff --git a/src/alloyproxy/libs/websocket.js b/src/alloyproxy/libs/websocket.js new file mode 100644 index 00000000..6c86aaf1 --- /dev/null +++ b/src/alloyproxy/libs/websocket.js @@ -0,0 +1,110 @@ +const WebSocket = require('ws'); + +// Setting Base64 encoding and decoding functions. + +btoa = (str) => { + + str = new Buffer.from(str).toString('base64'); + + return str; + +}; + + +atob = (str) => { + + str = new Buffer.from(str, 'base64').toString('utf-8'); + + return str; + +}; + +module.exports = (server, config) => { + + // Using the HTTP server as the WS server. + + const wss = new WebSocket.Server({ + server: server + }); + + wss.on('connection', (cli, req) => { + + try { + + const svr = new WebSocket(atob(req.url.replace(config.prefix + 'ws/', ''))); + + svr.on('message', (data) => { + + try { + cli.send(data) + } catch (err) {} + + }); + + // Getting response from WS client to send to the user from the server. + + svr.on('open', () => { + + cli.on('message', (data) => { + + svr.send(data) + + }); + + }); + + + // Closes server when WS client closes. + + cli.on('close', (code) => { + + try { + svr.close(code); + } catch (err) { + svr.close(1006) + }; + + }); + + // Closes client when WS server closes. + + svr.on('close', (code) => { + + try { + cli.close(code); + } catch (err) { + cli.close(1006) + }; + + }); + + // Closes server when WS client errors. + + cli.on('error', (err) => { + + try { + svr.close(1001); + } catch (err) { + svr.close(1006) + }; + + }); + + // Closes client when WS server errors. + + svr.on('error', (err) => { + + try { + cli.close(1001); + } catch (err) { + cli.close(1006) + }; + + }); + + } catch (err) { + console.log(err); + cli.close(1001); + } + }); +} \ No newline at end of file diff --git a/src/alloyproxy/package.json b/src/alloyproxy/package.json new file mode 100644 index 00000000..aab38b7a --- /dev/null +++ b/src/alloyproxy/package.json @@ -0,0 +1,56 @@ +{ + "_from": "alloyproxy@^1.1.0", + "_id": "alloyproxy@1.1.0", + "_inBundle": false, + "_integrity": "sha512-AlLoWex3qx2dWRO+3HovFjUWoRx4Lq9hEeyH5soIvJ0Y18VP2kgd8nuGirLJgJiVkoZyZJ31qzYK5q+vRsAebA==", + "_location": "/alloyproxy", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": true, + "raw": "alloyproxy@^1.1.0", + "name": "alloyproxy", + "escapedName": "alloyproxy", + "rawSpec": "^1.1.0", + "saveSpec": null, + "fetchSpec": "^1.1.0" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/alloyproxy/-/alloyproxy-1.1.0.tgz", + "_shasum": "95d4064bd691a0f949e1a66de9bd966e1c0fff7f", + "_spec": "alloyproxy@^1.1.0", + "_where": "C:\\Users\\Not A Porxy\\Documents\\HolyUnblockerWorkspace\\HolyUB", + "author": { + "name": "Titanium Network" + }, + "bugs": { + "url": "https://github.com/titaniumnetwork-dev/alloyproxy/issues" + }, + "bundleDependencies": false, + "dependencies": { + "ws": "^7.4.0" + }, + "deprecated": false, + "description": "Proxy library to unblock the web!", + "homepage": "https://github.com/titaniumnetwork-dev/alloyproxy#readme", + "keywords": [ + "proxy", + "unblocker", + "web", + "proxy" + ], + "license": "ISC", + "main": "index.js", + "name": "alloyproxy", + "repository": { + "type": "git", + "url": "git+https://github.com/titaniumnetwork-dev/alloyproxy.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.1.0" +} diff --git a/views/expr/surf.js b/views/expr/surf.js index 937dc5d3..c6952d66 100644 --- a/views/expr/surf.js +++ b/views/expr/surf.js @@ -48,7 +48,7 @@ $('al').onclick = function() { url = btoa('http://' + url[0] + '/' + url.slice(1).join('/')); console.log(url); } else url = btoa(url) - frame.src = "https://" + domain + "/fetch/" + url; + frame.src = "https://" + domain + "/session/?url=" + url; frame.style['visibility'] = "visible"; frame.setAttribute('allow', 'fullscreen'); frame.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms'); @@ -65,7 +65,7 @@ $('albp').onclick = function() { url = btoa('http://' + url[0] + '/' + url.slice(1).join('/')); console.log(url); } else url = btoa(url) - window.location.href = "https://" + domain + "/fetch/" + url; + window.location.href = "https://" + domain + "/session/?url=" + url; document.cookie = 'oldsmobile=badcar; expires=' + (Date.now() + 259200) + '; SameSite=Lax; domain=.' + auth + '; path=/; Secure;'; return false; };