├── README.md ├── app.json ├── config.json ├── lib ├── dom.js ├── error.html ├── index.js └── window.js ├── node_modules └── ws │ ├── LICENSE │ ├── README.md │ ├── browser.js │ ├── index.js │ ├── lib │ ├── buffer-util.js │ ├── constants.js │ ├── event-target.js │ ├── extension.js │ ├── limiter.js │ ├── permessage-deflate.js │ ├── receiver.js │ ├── sender.js │ ├── stream.js │ ├── validation.js │ ├── websocket-server.js │ └── websocket.js │ └── package.json ├── package.json ├── public ├── assets │ ├── fonts │ │ ├── all.css │ │ ├── fa-brands-400.woff │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.woff │ │ ├── fa-regular-400.woff2 │ │ ├── fa-solid-900.woff │ │ ├── fa-solid-900.woff2 │ │ └── fa.css │ ├── main.css │ └── main.js └── index.html ├── server.js └── ssl ├── default.crt └── default.key /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated! 2 | This project is deprecated! Use our other proxy [Corrosion](https://github.com/titaniumnetwork-dev/corrosion) instead! 3 | 4 | # Alloy Proxy 5 | A web proxy for use in combating web filters. 6 | 7 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/titaniumnetwork-dev/alloy/tree/master) 8 | 9 | ## Running locally 10 | 11 | ```sh 12 | git clone https://github.com/titaniumnetwork-dev/alloyproxy.git 13 | cd alloyproxy 14 | node server.js 15 | ``` 16 | 17 | 18 | ## Options in config.json 19 | ```json 20 | { 21 | "port": "8080", 22 | "ssl": false, 23 | "prefix": "/web/", 24 | "localAddresses": [], 25 | "blockedHostnames": [] 26 | } 27 | ``` 28 | 29 | `"port": "8080"` = Sets HTTP server port of web proxy. 30 | 31 | `"ssl": "false"` = Sets HTTP server SSL. 32 | 33 | `"prefix": "/web/"` = Sets the overall prefix of the web proxy. 34 | 35 | `"localAddresses": [ "0.0.0.0" ]` = Allows you to choose which IP to make the request from. If there are multiple IP's then the IP chosen will be randomized. 36 | 37 | `"blockedHostnames": [ "example.org", "example.com" ]` = If the hostname of the proxy URL matches any of the URL hostnames listed in the array, the request to the server will be cancelled. 38 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Alloy", 3 | "description": "A Titanium Network web proxy.", 4 | "repository": "https://github.com/titaniumnetwork-dev/alloy/", 5 | "logo": "https://avatars.githubusercontent.com/u/47227492?s=200&v=4", 6 | "keywords": ["alloy", "proxy", "web-unblocker", "internet", "alloyproxy"] 7 | } 8 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": "8080", 3 | "ssl": false, 4 | "prefix": "/web/", 5 | "localAddresses": [], 6 | "blockedHostnames": [] 7 | } -------------------------------------------------------------------------------- /lib/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 26 | 27 | 28 | 29 | 30 |
31 |

%ERR%

32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const http = require('http'), 2 | https = require('https'), 3 | fs = require('fs'), 4 | zlib = require('zlib'), 5 | querystring = require('querystring'), 6 | WebSocket = require('ws'), 7 | btoa = str => new Buffer.from(str).toString('base64'), 8 | atob = str => new Buffer.from(str, 'base64').toString('utf-8'); 9 | 10 | module.exports = class { 11 | // Constructor function. 12 | constructor(prefix = "/web/", config = {}) { 13 | this.prefix = prefix; 14 | this.config = config; 15 | this.proxifyRequestURL = (url, type) => type ? atob(url.split('_').slice(1).splice(0, 1).join()) + url.split('_').slice(2).join('_') : `_${btoa(url.split('/').splice(0, 3).join('/'))}_/${url.split('/').splice(3).join('/')}` 16 | 17 | if (!prefix.startsWith('/')) this.prefix = '/' + prefix; 18 | if (!prefix.endsWith('/')) this.prefix = prefix + '/'; 19 | }; 20 | // HTTP(S) proxy. 21 | http(req, res, next = () => res.end('')) { 22 | 23 | if (!req.url.startsWith(this.prefix)) return next(); 24 | 25 | // Defining alternatives to `req.url` that don't contain web proxy prefix (req.path) and with the additional prop (req.pathname) not containing any hash or query params. 26 | req.path = req.url.replace(this.prefix.slice(1), ''); 27 | req.pathname = req.path.split('#')[0].split('?')[0]; 28 | 29 | if (req.pathname == '/client_hook' || req.pathname == '/client_hook/') return res.end(fs.readFileSync(__dirname + '/window.js', 'utf-8')); 30 | 31 | try {new URL(this.proxifyRequestURL(req.path, true))} catch {return res.end('URL Parse Error')}; 32 | 33 | var proxyURL = { 34 | href: this.proxifyRequestURL(req.path, true), 35 | origin: this.proxifyRequestURL(req.path, true).split('/').splice(0, 3).join('/'), 36 | hostname: this.proxifyRequestURL(req.path, true).split('/').splice(0, 3).slice(2).join('/') 37 | }, 38 | proxify = {}, 39 | isBlocked = false, 40 | protocol = proxyURL.href.startsWith('https://') ? https : http, 41 | proxyOptions = { 42 | headers: Object.assign({}, req.headers), 43 | method: req.method, 44 | rejectUnauthorized: false 45 | }; 46 | 47 | if (proxyURL.href.startsWith('https://') || proxyURL.href.startsWith('http://')); else return res.end('URL Parse Error'); 48 | 49 | delete proxyOptions.headers['host']; 50 | 51 | // URL hostname blocklist. 52 | if (typeof this.config.blacklist == 'object' && this.config.blacklist.length != 0) this.config.blacklist.forEach(blacklisted => proxyURL.hostname == blacklisted ? isBlocked = true : isBlocked = false); 53 | if (isBlocked) return res.end('The URL you are trying to access is not permitted for use.') 54 | 55 | if (!req.path.startsWith(`/_${btoa(proxyURL.origin)}_/`)) return (res.writeHead(308, { location: this.prefix + `_${btoa(proxyURL.origin)}_/`}), res.end('')); 56 | 57 | // Proxifying "Origin" request header. Vital since some websites might have a failsafe for their API involving the "Origin" request header. 58 | if (proxyOptions.headers['origin']) { 59 | var proxified_header = this.proxifyRequestURL(`/${proxyOptions.headers['origin'].split('/').splice(3).join('/')}`.replace(this.prefix, ''), true); 60 | if (proxified_header.startsWith('https://') || proxified_header.startsWith('http://')) proxified_header = proxified_header.split('/').splice(0, 3).join('/'); 61 | else proxified_header = proxyURL.origin; 62 | proxyOptions.headers['origin'] = proxified_header; 63 | } 64 | 65 | // Proxifying "Referer" request header. Vital since some websites might have a failsafe for their API involving the "Referer" request header. 66 | if (proxyOptions.headers['referer']) { 67 | 68 | var proxified_header = this.proxifyRequestURL('/' + proxyOptions.headers['referer'].split('/').splice(3).join('/').replace(this.prefix, ''), true); 69 | if (proxified_header.startsWith('https://') || proxified_header.startsWith('http://')) proxified_header = proxified_header; 70 | else proxified_header = proxyURL.href; 71 | 72 | proxyOptions.headers['referer'] = proxified_header; 73 | 74 | } 75 | 76 | 77 | if (proxyOptions.headers['cookie']) { 78 | var new_cookie = [], 79 | cookie_array = proxyOptions.headers['cookie'].split('; '); 80 | 81 | cookie_array.forEach(cookie => { 82 | 83 | const cookie_name = cookie.split('=').splice(0, 1).join(), 84 | cookie_value = cookie.split('=').splice(1).join(); 85 | 86 | if (proxyURL.hostname.includes(cookie_name.split('@').splice(1).join())) new_cookie.push(cookie_name.split('@').splice(0, 1).join() + '=' + cookie_value); 87 | 88 | }); 89 | 90 | proxyOptions.headers['cookie'] = new_cookie.join('; '); 91 | }; 92 | 93 | if (typeof this.config.localAddress == 'object' && this.config.localAddress.length != 0) proxyOptions.localAddress = this.config.localAddress[Math.floor(Math.random() * this.config.localAddress.length)]; 94 | 95 | var makeRequest = protocol.request(proxyURL.href, proxyOptions, proxyResponse => { 96 | 97 | var rawData = [], 98 | sendData = ''; 99 | 100 | proxyResponse.on('data', data => rawData.push(data)).on('end', () => { 101 | 102 | const inject_config = { 103 | prefix: this.prefix, 104 | url: proxyURL.href 105 | } 106 | 107 | // General URL proxifer. 108 | proxify.url = url => { 109 | 110 | if (url.match(/^(#|about:|data:|blob:|mailto:|javascript:|{|\*)/)) return url; 111 | 112 | if (url.startsWith('//')) url = new URL('http:' + url); 113 | else if (url.startsWith('/')) url = new URL(proxyURL.origin + url); 114 | else if (url.startsWith('https://') || url.startsWith('http://')) url = new URL(url); 115 | else url = new URL(proxyURL.href.split('/').slice(0, -1).join('/') + '/' + url); 116 | 117 | if (url.protocol == 'https:' || url.protocol == 'http:') return this.prefix + this.proxifyRequestURL(url.href); 118 | else return url.href; 119 | 120 | }; 121 | 122 | // Javascript "location" object proxifier. Will be replaced in the future with a more efficient one. 123 | proxify.js = buffer => buffer.toString().replace(/(,| |=|\()document.location(,| |=|\)|\.)/gi, str => { return str.replace('.location', `.alloyLocation`); }) 124 | .replace(/(,| |=|\()window.location(,| |=|\)|\.)/gi, str => { return str.replace('.location', `.alloyLocation`); }) 125 | .replace(/(,| |=|\()location(,| |=|\)|\.)/gi, str => { return str.replace('location', `alloyLocation`); }); 126 | 127 | 128 | // CSS proxifier. 129 | proxify.css = buffer => { 130 | return buffer.replace(/url\("(.*?)"\)/gi, str => { 131 | var url = str.replace(/url\("(.*?)"\)/gi, '$1'); 132 | return `url("${proxify.url(url)}")`; 133 | }).replace(/url\('(.*?)'\)/gi, str => { 134 | var url = str.replace(/url\('(.*?)'\)/gi, '$1'); 135 | return `url('${proxify.url(url)}')`; 136 | }).replace(/url\((.*?)\)/gi, str => { 137 | var url = str.replace(/url\((.*?)\)/gi, '$1'); 138 | 139 | if (url.startsWith(`"`) || url.startsWith(`'`)) return str; 140 | 141 | return `url("${proxify.url(url)}")`; 142 | }).replace(/@import (.*?)"(.*?)";/gi, str => { 143 | var url = str.replace(/@import (.*?)"(.*?)";/, '$2'); 144 | return `@import "${proxify.url(url)}";` 145 | }).replace(/@import (.*?)'(.*?)';/gi, str => { 146 | var url = str.replace(/@import (.*?)'(.*?)';/, '$2'); 147 | return `@import '${proxify.url(url)}';` 148 | }) 149 | }; 150 | 151 | // DOM based HTML proxifier. 152 | proxify.html = body => { 153 | 154 | const html = new (require('./dom')).JSDOM(body, {contentType: 'text/html'}), document = html.window.document; 155 | 156 | var base_tag = false; 157 | 158 | if (document.querySelector('head base')) base_tag = document.querySelector('head base').getAttribute('href'); 159 | 160 | // Sloppy due to having to release this fast. 161 | if (base_tag) { 162 | 163 | if (base_tag.includes('#') || base_tag.includes('?')) base_tag = base_tag.split('#')[0].split('?')[0]; 164 | 165 | if (base_tag.startsWith('//')) base_tag = 'http:' + base_tag; 166 | 167 | if (base_tag.startsWith('https://') || base_tag.startsWith('http://')) base_tag = new URL(base_tag).href; 168 | else if (base_tag.startsWith('/')) base_tag = new URL(proxyURL.origin + base_tag).href; 169 | else base_tag = new URL(proxyURL.href.split('/').slice(0, -1).join('/') + '/' + base_tag).href; 170 | 171 | inject_config.baseURL = base_tag; 172 | 173 | }; 174 | 175 | proxify.attribute = attribute => { 176 | if (attribute.startsWith('https://') || attribute.startsWith('http://') || attribute.startsWith('//')) return proxify.url(attribute); 177 | else if (base_tag) { 178 | if (attribute.startsWith('/')) return attribute = proxify.url(base_tag.split('/').splice(0, 3).join('/') + attribute); 179 | else return attribute = proxify.url(base_tag.split('/').slice(0, -1).join('/') + '/' + attribute); 180 | } else return proxify.url(attribute); 181 | }; 182 | 183 | // Removing all "nonce" and "integrity" attributes. 184 | document.querySelectorAll('*').forEach(node => { 185 | if (node.getAttribute('nonce')) node.removeAttribute('nonce'); 186 | if (node.getAttribute('integrity')) node.removeAttribute('integrity'); 187 | if (node.getAttribute('style')) node.setAttribute('style', proxify.css(node.getAttribute('style'))); 188 | }); 189 | 190 | // Rewriting "src" attributes on elements. 191 | document.querySelectorAll("script, embed, iframe, audio, video, img, input, source, track").forEach(node => { 192 | if (node.src) node.src = proxify.attribute(node.src); 193 | if (node.tagName.toLowerCase() == 'script' && node.innerHTML != '') node.innerHTML = proxify.js(node.innerHTML); 194 | }); 195 | 196 | document.querySelectorAll("img[srcset], source[srcset]").forEach(node => { 197 | var arr = []; 198 | 199 | node.srcset.split(',').forEach(url => { 200 | url = url.trimStart().split(' '); 201 | url[0] = proxify.attribute(url[0]); 202 | arr.push(url.join(' ')); 203 | }); 204 | 205 | node.srcset = arr.join(', ') 206 | }); 207 | 208 | // Rewriting "href" attributes on elements. 209 | document.querySelectorAll("a, link, area").forEach(node => { 210 | if (node.href) node.href = proxify.attribute(node.href); 211 | }); 212 | 213 | document.querySelectorAll('base').forEach(node => node.href = proxify.attribute(node.href)); 214 | 215 | // Rewriting "action" attribute for forms. 216 | document.querySelectorAll('form').forEach(node => { 217 | if (node.action) node.action = proxify.attribute(node.action); 218 | }); 219 | 220 | document.querySelectorAll('style').forEach(node => { 221 | node.textContent = proxify.css(node.textContent); 222 | }); 223 | 224 | // Creating injection script element. 225 | const inject_script = document.createElement('script'); 226 | 227 | // Setting injection script attributes. 228 | inject_script.src = this.prefix + 'client_hook'; 229 | inject_script.setAttribute('data-config', btoa(JSON.stringify(inject_config))); 230 | 231 | // Putting "script" element for injection in the beginning of "head" element. 232 | document.querySelector('head').insertBefore(inject_script, document.querySelector('head').childNodes[0]) 233 | 234 | return html.serialize(); 235 | 236 | }; 237 | 238 | // Handling response body Content-Encoding. 239 | if (rawData.length != 0) switch(proxyResponse.headers['content-encoding']) { 240 | case 'gzip': 241 | sendData = zlib.gunzipSync(Buffer.concat(rawData)); 242 | break; 243 | case 'deflate': 244 | sendData = zlib.inflateSync(Buffer.concat(rawData)); 245 | break; 246 | case 'br': 247 | sendData = zlib.brotliDecompressSync(Buffer.concat(rawData)); 248 | break; 249 | default: sendData = Buffer.concat(rawData); break; 250 | }; 251 | 252 | // Handling response headers. 253 | Object.entries(proxyResponse.headers).forEach(([header_name, header_value]) => { 254 | if (header_name == 'set-cookie') { 255 | const cookie_array = []; 256 | header_value.forEach(cookie => cookie_array.push(cookie.replace(/Domain=(.*?);/gi, `Domain=` + req.headers['host'] + ';').replace(/(.*?)=(.*?);/, '$1' + '@' + proxyURL.hostname + `=` + '$2' + ';'))); 257 | proxyResponse.headers[header_name] = cookie_array; 258 | 259 | }; 260 | 261 | 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 proxyResponse.headers[header_name]; 262 | 263 | if (header_name == 'location') proxyResponse.headers[header_name] = proxify.url(header_value); 264 | }); 265 | 266 | // Rewriting the response body based off of the Content-Type response header. 267 | if (proxyResponse.headers['content-type'] && proxyResponse.headers['content-type'].startsWith('text/html')) sendData = proxify.html(sendData.toString()); 268 | else if (proxyResponse.headers['content-type'] && (proxyResponse.headers['content-type'].startsWith('application/javascript') || proxyResponse.headers['content-type'].startsWith('text/javascript'))) sendData = proxify.js(sendData.toString()); 269 | else if (proxyResponse.headers['content-type'] && proxyResponse.headers['content-type'].startsWith('text/css')) sendData = proxify.css(sendData.toString()); 270 | 271 | // Sending proxy response with processed headers and body. 272 | res.writeHead(proxyResponse.statusCode, proxyResponse.headers); 273 | res.end(sendData); 274 | 275 | }); 276 | 277 | }); 278 | 279 | makeRequest.on('error', err => res.end(err.toString())) 280 | 281 | if (!res.writableEnded) req.on('data', data => makeRequest.write(data)).on('end', () => makeRequest.end()); 282 | 283 | }; 284 | // Websocket Proxy 285 | ws(server) { 286 | new WebSocket.Server({server: server}).on('connection', (cli, req) => { 287 | 288 | var queryParams = querystring.parse(req.url.split('?').splice(1).join('?')), proxyURL, options = { 289 | headers: {}, 290 | followRedirects: true 291 | }, protocol = []; 292 | 293 | if (!queryParams.ws) return cli.close(); 294 | 295 | proxyURL = atob(queryParams.ws); 296 | 297 | try { new URL(proxyURL) } catch{ return cli.close() }; 298 | 299 | Object.entries(req.headers).forEach(([header_name, header_value]) => { 300 | if (header_name == 'sec-websocket-protocol') header_value.split(', ').forEach(proto => protocol.push(proto)); 301 | if (header_name.startsWith('cf-') || header_name.startsWith('cdn-loop')); 302 | else if (!header_name.startsWith('sec-websocket')) options.headers[header_name] = header_value; 303 | }) 304 | 305 | if (queryParams.origin) (options.origin = atob(queryParams.origin), options.headers.origin = atob(queryParams.origin)); 306 | 307 | delete options.headers['host']; 308 | delete options.headers['cookie']; 309 | 310 | if (typeof this.config.localAddress == 'object' && this.config.localAddress.length != 0) options.localAddress = this.config.localAddress[Math.floor(Math.random() * this.config.localAddress.length)]; 311 | 312 | const proxy = new WebSocket(proxyURL, protocol, options), 313 | before_open = []; 314 | 315 | if (proxy.readyState == 0) cli.on('message', data => before_open.push(data)); 316 | 317 | cli.on('close', () => proxy.close()); 318 | proxy.on('close', () => cli.close()); 319 | cli.on('error', () => proxy.terminate()) 320 | proxy.on('error', () => cli.terminate()); 321 | 322 | proxy.on('open', () => { 323 | 324 | if (before_open.length != 0) before_open.forEach(data => proxy.send(data)) 325 | 326 | cli.on('message', data => proxy.send(data)); 327 | proxy.on('message', data => cli.send(data)); 328 | 329 | 330 | }); 331 | 332 | }); 333 | }; 334 | }; 335 | -------------------------------------------------------------------------------- /lib/window.js: -------------------------------------------------------------------------------- 1 | // Alloy Proxy javascript object rewriter. 2 | // Rewrites functions that makes HTTP or Websocket requests, and DOM element selectors & creators. 3 | 4 | // Alloy configurations retrieved from attribute. 5 | 6 | var alloy = JSON.parse(atob(document.currentScript.getAttribute('data-config'))); 7 | alloy.url = new URL(alloy.url) 8 | 9 | // "document.location" and "window.location" are rewritten server-side and replaced with this object. 10 | window.alloyLocation = new Proxy({}, { 11 | set(obj, prop, value) { 12 | 13 | if (prop == 'assign' || prop == 'reload' || prop == 'replace' || prop == 'toString') return; 14 | 15 | console.log(proxify.url(alloy.url.href.replace(alloy.url[prop], value))); 16 | 17 | console.log((alloy.url.href.replace(alloy.url[prop], value))); 18 | 19 | 20 | return location[prop] = proxify.url(alloy.url.href.replace(alloy.url[prop], value)); 21 | }, 22 | get(obj, prop) { 23 | // Had to be done in order to fix Discord. 24 | if (alloy.url.origin == atob('aHR0cHM6Ly9kaXNjb3JkLmNvbQ==') && alloy.url.pathname == '/app') return window.location[prop]; 25 | 26 | if (prop == 'assign' || prop == 'reload' || prop == 'replace' || prop == 'toString') return { 27 | assign: arg => window.location.assign(proxify.url(arg)), 28 | replace: arg => window.location.replace(proxify.url(arg)), 29 | reload: () => window.location.reload(), 30 | toString: () => { return alloy.url.href } 31 | }[prop]; 32 | else return alloy.url[prop]; 33 | } 34 | }); 35 | 36 | window.document.alloyLocation = window.alloyLocation; 37 | 38 | Object.defineProperty(document, 'domain', { 39 | get() { 40 | return alloy.url.hostname; 41 | }, 42 | set(value) { 43 | return value; 44 | } 45 | }); 46 | 47 | // Any alloy function that rewrites request URLs go under this object. 48 | var proxify = { 49 | url: (url, type) => { 50 | 51 | if (!url) return; 52 | 53 | var proxified; 54 | // If type equals "true" then the function will decode "/prefix/_aHR0cHM6Ly9kaXNjb3JkLmNvbQ==_/" to "https://discord.com/". 55 | //By default, the function will proxify the URL with the proxy prefix and base64 encoded URL origin. 56 | switch(type) { 57 | case true: 58 | proxified = atob(url.replace(alloy.prefix, '').split('_').slice(1).splice(0, 1).join()) + url.split('_').slice(2).join('_'); 59 | break; 60 | 61 | default: 62 | 63 | if (url.match(/^(#|about:|data:|blob:|mailto:|javascript:|{|\*)/) || url.startsWith(alloy.prefix) || url.startsWith(window.location.origin + alloy.prefix)) return url; 64 | 65 | if (url.startsWith(window.location.origin + '/') && !url.startsWith(window.location.origin + alloy.prefix)) url = '/' + url.split('/').splice(3).join('/'); 66 | 67 | if (url.startsWith('//')) url = 'http:' + url; 68 | if (url.startsWith('/') && !url.startsWith(alloy.prefix)) url = alloy.url.origin + url; 69 | 70 | if (url.startsWith('https://') || url.startsWith('http://')) url = new URL(url); 71 | else url = new URL(alloy.url.href.split('/').slice(0, -1).join('/') + '/' + url); 72 | 73 | proxified = alloy.prefix + '_' + btoa(url.href.split('/').splice(0, 3).join('/')) + '_' + "/" + url.href.split('/').splice(3).join('/'); 74 | 75 | break; 76 | } 77 | return proxified; 78 | } 79 | }; 80 | 81 | // Customized URL proxifier for any DOM element or HTTP request function that can get morphed by element. 82 | proxify.url_http = url => { 83 | 84 | if (url.match(/^(#|about:|data:|blob:|mailto:|javascript:|{|\*)/) || url.startsWith(alloy.prefix) || url.startsWith(window.location.origin + alloy.prefix)) return url; 85 | 86 | // Rewriting based on element href needs to be developed more. 87 | if (url.startsWith('https://') || url.startsWith('http://') || url.startsWith('//')) return proxify.url(url); 88 | else if (alloy.baseURL) { 89 | if (url.startsWith('/')) return url = proxify.url(alloy.baseURL.split('/').splice(0, 3).join('/') + url); 90 | else return url = proxify.url(alloy.baseURL.split('/').slice(0, -1).join('/') + '/' + url); 91 | } else return proxify.url(url); 92 | }; 93 | 94 | 95 | let originalFetch = window.fetch, 96 | originalXMLOpen = window.XMLHttpRequest.prototype.open, 97 | originalOpen = window.open, 98 | originalPostMessage = window.postMessage, 99 | originalSendBeacon = window.Navigator.prototype.sendBeacon; 100 | 101 | // HTTP request function proxifying. 102 | 103 | window.fetch = function(url, options) { 104 | 105 | if (url) (url.replace(location.hostname, alloy.url.hostname), url = proxify.url_http(url)); 106 | return originalFetch.apply(this, arguments); 107 | }; 108 | window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) { 109 | if (url) (url.replace(location.hostname, alloy.url.hostname), url = proxify.url_http(url)); 110 | return originalXMLOpen.apply(this, arguments); 111 | }; 112 | window.open = function(url, windowName, windowFeatures) { 113 | if (url) url = proxify.url(url); 114 | return originalOpen.apply(this, arguments); 115 | }; 116 | window.postMessage = function(msg, origin, transfer) { 117 | if (origin) origin = location.origin; 118 | return originalPostMessage.apply(this, arguments); 119 | }; 120 | window.Navigator.prototype.sendBeacon = function(url, data) { 121 | if (url) url = proxify.url(url); 122 | return originalSendBeacon.apply(this, arguments); 123 | }; 124 | 125 | // Websocket function proxifying. 126 | window.WebSocket = new Proxy(window.WebSocket, { 127 | construct(target, args) { 128 | var protocol; 129 | if (location.protocol == 'https:') protocol = 'wss://'; else protocol = 'ws://'; 130 | 131 | args[0] = protocol + location.origin.split('/').splice(2).join('/') + alloy.prefix + '?ws=' + btoa(args[0]) + '&origin=' + btoa(alloy.url.origin); 132 | 133 | return Reflect.construct(target, args); 134 | } 135 | }); 136 | 137 | // DOM element proxifying. 138 | 139 | // Element.innerHTML & Element.outerHTML proxifying. 140 | proxify.elementHTML = element_array => { 141 | element_array.forEach(element => { 142 | Object.defineProperty(element.prototype, 'innerHTML', { 143 | set(value) { 144 | const elem = new DOMParser().parseFromString(Object.getOwnPropertyDescriptor(window.Element.prototype, "outerHTML").get.call(this), 'text/html').body.querySelectorAll('*')[0]; 145 | Object.getOwnPropertyDescriptor(window.Element.prototype, "innerHTML").set.call(elem, value); 146 | elem.querySelectorAll("script[src], iframe[src], embed[src], audio[src], img[src], input[src], source[src], track[src], video[src]").forEach(node => node.setAttribute('src', node.getAttribute('src'))); 147 | elem.querySelectorAll("object[data]").forEach(node => node.setAttribute('data', node.getAttribute('data'))); 148 | elem.querySelectorAll("a[href], link[href], area[href").forEach(node => node.setAttribute('href', node.getAttribute('href'))); 149 | return Object.getOwnPropertyDescriptor(window.Element.prototype, "innerHTML").set.call(this, elem.innerHTML); 150 | }, 151 | get() { 152 | return Object.getOwnPropertyDescriptor(window.Element.prototype, "innerHTML").get.call(this); 153 | } 154 | }); 155 | Object.defineProperty(element.prototype, 'outerHTML', { 156 | set(value) { 157 | const elem = new DOMParser().parseFromString(Object.getOwnPropertyDescriptor(window.Element.prototype, "outerHTML").get.call(this), 'text/html').body; 158 | Object.getOwnPropertyDescriptor(window.Element.prototype, "outerHTML").set.call(elem.querySelectorAll('*')[0], value); 159 | elem.querySelectorAll("script[src], iframe[src], embed[src], audio[src], img[src], input[src], source[src], track[src], video[src]").forEach(node => node.setAttribute('src', node.getAttribute('src'))); 160 | elem.querySelectorAll("object[data]").forEach(node => node.setAttribute('data', node.getAttribute('data'))); 161 | elem.querySelectorAll("a[href], link[href], area[href").forEach(node => node.setAttribute('href', node.getAttribute('href'))); 162 | return Object.getOwnPropertyDescriptor(window.Element.prototype, "outerHTML").set.call(this, elem.innerHTML); 163 | }, 164 | get() { 165 | return Object.getOwnPropertyDescriptor(window.Element.prototype, "outerHTML").get.call(this); 166 | } 167 | }); 168 | }); 169 | }; 170 | 171 | // Element.attribute 172 | proxify.elementAttribute = (element_array, attribute_array) => { 173 | element_array.forEach(element => { 174 | 175 | // If the element being rewritten is "script". Prevent "integrity" and "nonce" attributes from being created. 176 | if (element == window.HTMLScriptElement) { 177 | Object.defineProperty(element.prototype, 'integrity', { 178 | set(value) { 179 | return this.removeAttribute('integrity') 180 | }, 181 | get() { 182 | return this.getAttribute('integrity'); 183 | } 184 | }); 185 | Object.defineProperty(element.prototype, 'nonce', { 186 | set(value) { 187 | return this.removeAttribute('nonce') 188 | }, 189 | get() { 190 | return this.getAttribute('nonce'); 191 | } 192 | }); 193 | } 194 | 195 | element.prototype.setAttribute = new Proxy(element.prototype.setAttribute, { 196 | apply(target, thisArg, [ element_attribute, value ]) { 197 | attribute_array.forEach(array_attribute => { 198 | 199 | // Customized "srcset" rewriting. 200 | if (array_attribute == 'srcset' && element_attribute.toLowerCase() == array_attribute) { 201 | var arr = []; 202 | 203 | value.split(',').forEach(url => { 204 | url = url.trimStart().split(' '); 205 | url[0] = proxify.url_http(url[0]); 206 | arr.push(url.join(' ')); 207 | }); 208 | 209 | return Reflect.apply(target, thisArg, [ element_attribute, arr.join(', ') ]); 210 | }; 211 | 212 | // General attribute rewriting. 213 | if (element_attribute.toLowerCase() == array_attribute) value = proxify.url_http(value); 214 | }); 215 | return Reflect.apply(target, thisArg, [ element_attribute, value ]); 216 | } 217 | }); 218 | 219 | // No need to rewrite values here because of Element.setAttribute already being proxified. 220 | attribute_array.forEach(attribute => { 221 | 222 | Object.defineProperty(element.prototype, attribute, { 223 | set(value) { 224 | return this.setAttribute(attribute, value); 225 | }, 226 | get() { 227 | return this.getAttribute(attribute); 228 | } 229 | }); 230 | 231 | }); 232 | 233 | }); 234 | }; 235 | 236 | 237 | document.write = new Proxy(document.write, { 238 | apply(target, thisArg, args) { 239 | var processedHTML = new DOMParser().parseFromString(args[0], 'text/html'); 240 | 241 | processedHTML.querySelectorAll("script[src], iframe[src], embed[src], audio[src], img[src], input[src], source[src], track[src], video[src]").forEach(node => node.setAttribute('src', node.getAttribute('src'))); 242 | processedHTML.querySelectorAll("object[data]").forEach(node => node.setAttribute('data', node.getAttribute('data'))); 243 | processedHTML.querySelectorAll("a[href], link[href], area[href").forEach(node => node.setAttribute('href', node.getAttribute('href'))); 244 | 245 | return Reflect.apply(target, thisArg, [ processedHTML.querySelector('html').outerHTML ]); 246 | 247 | } 248 | }); 249 | 250 | 251 | // Proxifying DOM elements here. 252 | proxify.elementHTML([ window.HTMLDivElement ]); 253 | // Proxifying "href" attribute elements (Except for element at this time). 254 | proxify.elementAttribute([ window.HTMLAnchorElement, window.HTMLLinkElement, window.HTMLAreaElement ], [ 'href' ]); 255 | // Proxifying "src" attribute elements ( and elements proxified separately). 256 | proxify.elementAttribute([ window.HTMLScriptElement, window.HTMLIFrameElement, window.HTMLEmbedElement, window.HTMLAudioElement, window.HTMLInputElement, window.HTMLTrackElement, window.HTMLVideoElement ], [ 'src' ]); 257 | // Proxifying and elements for "src" and "srcset" attributes. 258 | proxify.elementAttribute([ window.HTMLImageElement, HTMLSourceElement ], [ 'src', 'srcset' ]); 259 | // Proxifying "data" attribute elements. 260 | proxify.elementAttribute([ window.HTMLObjectElement ], [ 'data' ]); 261 | // Proxifying "action" attribute elements. 262 | proxify.elementAttribute([ window.HTMLFormElement ], [ 'action' ]); 263 | 264 | 265 | // History method proxifying. 266 | window.History.prototype.pushState = new Proxy(window.History.prototype.pushState, { 267 | apply(target, thisArg, args) { 268 | 269 | // Discord support 270 | if (alloy.url.origin == atob('aHR0cHM6Ly9kaXNjb3JkLmNvbQ==') && args[2] == '/app') { 271 | args[2] = proxify.url(args[2]) 272 | Reflect.apply(target, thisArg, args); 273 | return window.location.reload(); 274 | } 275 | 276 | args[2] = proxify.url(args[2]) 277 | return Reflect.apply(target, thisArg, args) 278 | } 279 | }); 280 | 281 | window.History.prototype.replaceState = new Proxy(window.History.prototype.replaceState, { 282 | apply(target, thisArg, args) { 283 | args[2] = proxify.url(args[2]) 284 | return Reflect.apply(target, thisArg, args) 285 | } 286 | }); 287 | 288 | window.Worker = new Proxy(window.Worker, { 289 | construct(target, args) { 290 | args[0] = proxify.url(args[0]); 291 | return Reflect.construct(target, args); 292 | } 293 | }); 294 | 295 | Object.defineProperty(document, 'cookie', { 296 | get() { 297 | var cookie = Object.getOwnPropertyDescriptor(window.Document.prototype, 'cookie').get.call(this), 298 | new_cookie = [], 299 | cookie_array = cookie.split('; '); 300 | 301 | cookie_array.forEach(cookie => { 302 | 303 | const cookie_name = cookie.split('=').splice(0, 1).join(), 304 | cookie_value = cookie.split('=').splice(1).join(); 305 | 306 | if (alloy.url.hostname.includes(cookie_name.split('@').splice(1).join())) new_cookie.push(cookie_name.split('@').splice(0, 1).join() + '=' + cookie_value); 307 | 308 | }); 309 | return new_cookie.join('; ');; 310 | }, 311 | set(value) { 312 | return Object.getOwnPropertyDescriptor(window.Document.prototype, 'cookie').set.call(this, value); 313 | } 314 | }); 315 | 316 | 317 | // Doing this so the 23 | 24 | 25 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'), 2 | https = require('https'), 3 | fs = require('fs'), 4 | config = require('./config.json'), 5 | proxy = new (require('./lib/index'))(config.prefix, { 6 | localAddress: config.localAddresses ? config.localAddresses : false, 7 | blacklist: config.blockedHostnames ? config.blockedHostnames : false 8 | }), 9 | index_file = 'index.html', 10 | atob = str => new Buffer.from(str, 'base64').toString('utf-8'), 11 | app = (req, res) => { 12 | 13 | // HTTP(S) proxy. 14 | if (req.url.startsWith(config.prefix)) return proxy.http(req, res); 15 | 16 | req.pathname = req.url.split('#')[0].split('?')[0]; 17 | req.query = {}; 18 | req.url.split('#')[0].split('?').slice(1).join('?').split('&').forEach(query => req.query[query.split('=')[0]] = query.split('=').slice(1).join('=')); 19 | 20 | if (req.query.url && (req.pathname == '/prox' || req.pathname == '/prox/' || req.pathname == '/session' || req.pathname == '/session/')) { 21 | var url = atob(req.query.url); 22 | 23 | if (url.startsWith('https://') || url.startsWith('http://')) url = url; 24 | else if (url.startsWith('//')) url = 'http:' + url; 25 | else url = 'http://' + url; 26 | 27 | return (res.writeHead(301, { location: config.prefix + proxy.proxifyRequestURL(url) }), res.end('')); 28 | } 29 | 30 | 31 | // General file server. 32 | const publicPath = __dirname + '/public' + req.pathname; 33 | 34 | const error = () => (res.statusCode = 404, res.end(fs.readFileSync(__dirname + '/lib/error.html', 'utf-8').replace('%ERR%', `Cannot ${req.method} ${req.pathname}`))) 35 | 36 | fs.lstat(publicPath, (err, stats) => { 37 | 38 | if (err) return error(); 39 | 40 | if (stats.isDirectory()) fs.existsSync(publicPath + index_file) ? fs.createReadStream(publicPath + index_file).pipe(res) : error(); 41 | else if (stats.isFile()) !publicPath.endsWith('/') ? fs.createReadStream(publicPath).pipe(res) : error(); 42 | else error(); 43 | 44 | }); 45 | 46 | }, 47 | server = config.ssl ? https.createServer({key: fs.readFileSync('./ssl/default.key'), cert: fs.readFileSync('./ssl/default.crt')}, app) : http.createServer(app); 48 | 49 | // Websocket proxy. 50 | proxy.ws(server); 51 | 52 | server.listen(process.env.PORT || config.port, () => console.log(`${config.ssl ? 'https://' : 'http://'}0.0.0.0:${config.port}`)) 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ssl/default.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDqzCCApOgAwIBAgIJAJnCkScWtmL0MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRgwFgYDVQQKDA9UaXRhbml1bU5l 4 | dHdvcmsxDjAMBgNVBAsMBWdhbWVyMR4wHAYDVQQDDBUqLnRpdGFuaXVtbmV0d29y 5 | ay5vcmcwHhcNMjAwNjEzMTg0OTU2WhcNMjEwNjEzMTg0OTU2WjBsMQswCQYDVQQG 6 | EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEYMBYGA1UECgwPVGl0YW5pdW1OZXR3 7 | b3JrMQ4wDAYDVQQLDAVnYW1lcjEeMBwGA1UEAwwVKi50aXRhbml1bW5ldHdvcmsu 8 | b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPL69+RE6r8RrFh4 9 | njzC8ZRnLB+yNtuGw14C0dvNb5JwgdLl5g9/wK/s0V5NGlqwxlQlxQ/gUSuYEcUR 10 | 6MYjcnaUmZZe/gaKVV0fkfkuigOWhLnI5AQxx7rhkzx1ujuyJ9D2pkDtZpSvv0yn 11 | 2yrvWhJMtjuxGYip8jaLuRpbXoafvR7nrlDaNcE/GwIjnCCxsRnY2bGbxYK840mN 12 | fuMfF2nz+fXKPuQ/9PT48e3wOo9vM5s7yKhiHYwrogqzGN4cH4sSr1FE8C7flFyT 13 | Yw101u7fUaopfeGCo9Pg6IrfzyzE5Qb7OlqlVk2IkvXx7pPqVc6lZCJEhOX/qF9o 14 | n3mFqwIDAQABo1AwTjAdBgNVHQ4EFgQUC561ob2kGtFQ4az6y64b98+Fy+IwHwYD 15 | VR0jBBgwFoAUC561ob2kGtFQ4az6y64b98+Fy+IwDAYDVR0TBAUwAwEB/zANBgkq 16 | hkiG9w0BAQsFAAOCAQEAotvUsSLSzFyxQz329tEPyH6Tmi19FQoA5ZbLg6EqeTI9 17 | 08qOByDGkSYJi0npaIlPO1I557NxRzdO0PxK3ybol6lnzuSlqCJP5nb1dr0z2Eax 18 | wgKht9P+ap/yozU5ye05ah2nkpcaeDPnwnnWFmfsnYNfgu62EshOS+5FETWEKVUb 19 | LXQhGInOdJq8KZvhoLZWJoUhyAqxBfW4oVvaqs+Ff96A2NNKrvbiAVYX30rVa+x0 20 | KIl0/DoVvDx2Q6TiL396cAXdKUW7edRQcSsGFcxwIrU5lePm0V05aN+oCoEBvXBG 21 | ArPN+a5kpGjJwfcpcBVf9cJ6IsvptGS9de3eTHoTyw== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /ssl/default.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDA8vr35ETqvxGs 3 | WHiePMLxlGcsH7I224bDXgLR281vknCB0uXmD3/Ar+zRXk0aWrDGVCXFD+BRK5gR 4 | xRHoxiNydpSZll7+BopVXR+R+S6KA5aEucjkBDHHuuGTPHW6O7In0PamQO1mlK+/ 5 | TKfbKu9aEky2O7EZiKnyNou5Gltehp+9HueuUNo1wT8bAiOcILGxGdjZsZvFgrzj 6 | SY1+4x8XafP59co+5D/09Pjx7fA6j28zmzvIqGIdjCuiCrMY3hwfixKvUUTwLt+U 7 | XJNjDXTW7t9Rqil94YKj0+Doit/PLMTlBvs6WqVWTYiS9fHuk+pVzqVkIkSE5f+o 8 | X2ifeYWrAgMBAAECggEAbihK8Ev6rKr5RBQeiPjXs2SuoppV/MvIXLHHmliLKS/J 9 | 29S0PGyM202VPtM/4dP1KMXR6nft8WmaIEsKtoKoqijZHfajtRO21pWb+JLy5wi1 10 | XoFTGBrs8MLZFl5mODTsuZ6rsq9O2kn5LJZvHsmcbSgVc9UQfytvG0HY840ArS3g 11 | kSDtUFb1xRui6wtCBKzHVvCT+FXhSBbwkHalmbqP6BefhJ3lW2VonkOcHDrdXPfW 12 | CEN18IJ2v8QYgXqZP6VUlAweNXLJ33ZOl+jXGdygcOG24MFqdw0VtP0XFGk0jnSS 13 | W6dX67BZKeZ71EKaTy02jw5LpQNXA70ismPJHQ2uQQKBgQDuROawnBIW1fC3xOle 14 | m+JmP0eMe0eIQycxRsMXsXhYAA0wV3qYZSLZrNK2eRhmSNt+ODSmZ2Vt11dwOv5u 15 | bo8WONrRlM097SmitS2S+8o7ASem2VKQzyRE72Y9517Q+aNBdLRVtjrRNSw/hfSu 16 | ayLuG36+yukSH7wq7mfoUX34ZwKBgQDPTrgyyw8n5XhZT/qTTRnQJ2GTvPxDzNoJ 17 | IAGhGJGFAb6wgLoSpGx6BC122vuRxcTjkjAiMDci5N2zNW+YZVni+F0KTVvNFfU2 18 | pOTJUg3luRTygCra6O02PxwpbP/9KCBAKq/kYw/eBW+gxhPwP3ZrbAirvBjgBh0I 19 | kIrFijNOHQKBgGUUAbFGZD4fwCCVflLOWnr5uUaVPcFGi6fR1w2EEgNy8iVh1vYz 20 | YVdqg3E5aepqWgLvoRY+or64LbXEsQ70A+tvbxSdxXvR0mnd5lmGS0JAuSuE4gvg 21 | dAhybrMwJf8NB/7KnX4G8mix3/WKxEQB2y2bqGcT+U/g+phTzuy1NXVdAoGBAIrl 22 | jVjK4J60iswcYCEteWwT1rbr2oF60WNnxG+xTF63apJLzWAMNnoSLnwCAKgMv/xR 23 | yFo/v9FrUnduCBUtYupFyeDLMATa/27bUEbq6VDPjw9jfFMr2TONWUsQMvvlVKZp 24 | c2wsS0dQkRhBXr6LZsZWngCiiHAg6HcCkVgFXpapAoGBAJ/8oLGt0Ar+0MTl+gyk 25 | xSqgHnsc5jgqhix3nIoI5oEAbfibdGmRD1S3rtWD9YsnPxMIl+6E5bOAHrmd+Zr8 26 | O7EP+CLvbz4JXidaaa85h9ThXSG5xk1A1UTtSFrp+KolLE1Vvmjjd+R844XsM2wZ 27 | OAHbihzk0iPPphjEWR4lU4Av 28 | -----END PRIVATE KEY----- 29 | --------------------------------------------------------------------------------