├── 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 | [](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 |