Minor Change (oops)

This commit is contained in:
TheEmeraldStarr 2021-08-08 14:55:47 -07:00
parent af2ca7e034
commit 6bd7e10c4d
31 changed files with 34771 additions and 1 deletions

2
app.js
View file

@ -4,7 +4,7 @@
* MIT license: http://opensource.org/licenses/MIT
* ----------------------------------------------- */
const
corrosion = require('corrosion'),
corrosion = require('./src/Corrosion'),
path = require('path'),
config = require('./config.json'),
fs = require('fs'),

223
src/Corrosion/README.md Normal file
View file

@ -0,0 +1,223 @@
# Corrosion
Titanium Networks main web proxy.
Successor to [Alloy](https://github.com/titaniumnetwork-dev/alloy)
# Installation:
```
npm i corrosion
```
# Example:
```javascript
const Corrosion = require('corrosion');
const proxy = new Corrosion();
const http = require('http')
http.createServer((req, res) =>
proxy.request(req, res) // Request Proxy
).on('upgrade', (req, socket, head) =>
proxy.upgrade(req, socket, head) // WebSocket Proxy
).listen(80);
```
Much more in depth one is in the [demo folder](demo/).
# API:
## Index
- `config`
- `prefix` String - URL Prefix
- `title` (Boolean / String) - Title used for HTML documents
- `ws` Boolean - WebSocket rewriting
- `cookie` Boolean - Request Cookies
- `codec` String - URL encoding (base64, plain, xor).
- `requestMiddleware` Array - Array of [middleware](#middleware) functions for proxy request (Server).
- `responseMiddleware` Array - Array of [middleware](#middleware) functions for proxy response (Server).
- `standardMiddleware` Boolean - Use the prebuilt [middleware](#middleware) used by default (Server).
#### request
- `request` Request
- `response` Response
#### upgrade
- `request` Request
- `socket` Socket
- `head` Head
#### bundleScripts
Bundles scripts for client injection. Important when updating proxy.
## Properties
- [url](#url)
- [html](#html)
- [js](#js)
- [css](#css)
- [cookies](#cookies)
- [config](#index)
- [codec](#codec)
- [prefix](#url)
## url
#### wrap
- `val` String
- `config` Configuration
- `base` WHATWG URL
- `origin` Location origin - Adds a location origin before the proxy url
- `flags` Array - ['xhr'] => /service/xhr_/https%3A%2F%2Fexample.org/
#### unwrap
- `val` String
- `config` Configuration
- `origin` Location origin - Required if a location origin starts before the proxy url
- `flags` Boolean - Returns with both the URL and flags found { value: 'https://example.org', flags: ['xhr'], })
- `leftovers` Boolean - Use any leftovers if any after the encoded proxy url
## Properties
- `regex` Regex used to determine to rewrite the URL or not.
- `prefix` URL Prefix
- `codec` (base64, plain, xor)
## js
#### process
- `source` JS script
- `url` URL for heading
#### iterate
- `ast` JS AST
- `Callback` Handler initated on AST node
#### createHead
- `url` URL for heading
#### createCallExperssion
- `callee` Acorn.js Node
- `args` Array
#### createArrayExpression
- `elements` Array
#### createIdentifier
- `name` Identifier name
- `preventRewrite` Prevent further rewrites
#### createLiteral
- `value` Literal value
## css
#### process
- `source` CSS
- `config` Configuration
- `base` WHATWG URL
- `origin` Location origin
- `context` CSS-Tree context
## html
#### process
- `source` HTML Source
- `config` Configuration
- `document` Determines of its a document or fragment for parsing
- `base` WHATWG URL
- `origin` Location origin
#### source
- `processed` Rewritten HTML
- `config` Configuration
- `document` Determines of its a document or fragment for parsing
### Properties
- `map` Map for attribute rewriting
## cookies
#### encode
- `input` New (Cookie / Cookies)
- `config` Configuration
- `url` WHATWG URL
- `domain` Cookie Domain
- `secure` Cookie Secure
#### decode
- `store` Encoded Cookies
- `config` Configuration
- `url` WHATWG URL
## codec
#### encode
#### decode
- `str` String
## middleware
Middleware are functions that will be executed either before request or after response. These can alter the way a request is made or response is sent.
```javascript
function(ctx) {r
ctx.body; // (Request / Response) Body (Will return null if none)
ctx.headers; // (Request / Response) Headers
ctx.url; // WHATWG URL
ctx.flags; // URL Flags
ctx.origin; // Request origin
ctx.method; // Request method
ctx.rewrite; // Corrosion object
ctx.statusCode; // Response status (Only available on response)
ctx.agent; // HTTP agent
ctx.address; // Address used to make remote request
ctx.clientSocket; // Node.js Server Socket (Only available on upgrade)
ctx.clientRequest; // Node.js Server Request
ctx.clientResponse; // Node.js Server Response
ctx.remoteResponse; // Node.js Remote Response (Only available on response)
};
```
### Default middleware
- Request
- requestHeaders
- Response
- responseHeaders
- decompress
- rewriteBody
### Available Middleware
#### address (Request)
- `arr` Array of IP addresses to use in request
```javascript
const Corrosion = require('corrosion');
const proxy = new Corrosion({
requestMiddleware: [
Corrosion.middleware.address([
0.0.0.0,
0.0.0.0
]),
],
});
```
### blacklist
- `arr` Array of hostnames to block clients from seeing
- `page` Block page
```javascript
const Corrosion = require('corrosion');
const proxy = new Corrosion({
requestMiddleware: [
Corrosion.middleware.blacklist([
'example.org',
'example.com',
], 'Page is blocked'),
],
});
```

View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
text-align: center;
}
</style>
</head>
<body>
<form action="/service/gateway/" method="POST">
<input name="url" placeholder="Search the web">
<input type="submit" value="Go">
</form>
</body>
</html>

View file

@ -0,0 +1,19 @@
const https = require('https');
const fs = require('fs');
const path = require('path');
const ssl = {
key: fs.readFileSync(path.join(__dirname, '/ssl.key')),
cert: fs.readFileSync(path.join(__dirname, '/ssl.cert')),
};
const server = https.createServer(ssl);
const Corrosion = require('../');
const proxy = new Corrosion({
codec: 'xor',
});
proxy.bundleScripts();
server.on('request', (request, response) => {
if (request.url.startsWith(proxy.prefix)) return proxy.request(request, response);
response.end(fs.readFileSync(__dirname + '/index.html', 'utf-8'));
}).on('upgrade', (clientRequest, clientSocket, clientHead) => proxy.upgrade(clientRequest, clientSocket, clientHead)).listen(443);

View file

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDqzCCApOgAwIBAgIJAJnCkScWtmL0MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMRgwFgYDVQQKDA9UaXRhbml1bU5l
dHdvcmsxDjAMBgNVBAsMBWdhbWVyMR4wHAYDVQQDDBUqLnRpdGFuaXVtbmV0d29y
ay5vcmcwHhcNMjAwNjEzMTg0OTU2WhcNMjEwNjEzMTg0OTU2WjBsMQswCQYDVQQG
EwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEYMBYGA1UECgwPVGl0YW5pdW1OZXR3
b3JrMQ4wDAYDVQQLDAVnYW1lcjEeMBwGA1UEAwwVKi50aXRhbml1bW5ldHdvcmsu
b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPL69+RE6r8RrFh4
njzC8ZRnLB+yNtuGw14C0dvNb5JwgdLl5g9/wK/s0V5NGlqwxlQlxQ/gUSuYEcUR
6MYjcnaUmZZe/gaKVV0fkfkuigOWhLnI5AQxx7rhkzx1ujuyJ9D2pkDtZpSvv0yn
2yrvWhJMtjuxGYip8jaLuRpbXoafvR7nrlDaNcE/GwIjnCCxsRnY2bGbxYK840mN
fuMfF2nz+fXKPuQ/9PT48e3wOo9vM5s7yKhiHYwrogqzGN4cH4sSr1FE8C7flFyT
Yw101u7fUaopfeGCo9Pg6IrfzyzE5Qb7OlqlVk2IkvXx7pPqVc6lZCJEhOX/qF9o
n3mFqwIDAQABo1AwTjAdBgNVHQ4EFgQUC561ob2kGtFQ4az6y64b98+Fy+IwHwYD
VR0jBBgwFoAUC561ob2kGtFQ4az6y64b98+Fy+IwDAYDVR0TBAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAQEAotvUsSLSzFyxQz329tEPyH6Tmi19FQoA5ZbLg6EqeTI9
08qOByDGkSYJi0npaIlPO1I557NxRzdO0PxK3ybol6lnzuSlqCJP5nb1dr0z2Eax
wgKht9P+ap/yozU5ye05ah2nkpcaeDPnwnnWFmfsnYNfgu62EshOS+5FETWEKVUb
LXQhGInOdJq8KZvhoLZWJoUhyAqxBfW4oVvaqs+Ff96A2NNKrvbiAVYX30rVa+x0
KIl0/DoVvDx2Q6TiL396cAXdKUW7edRQcSsGFcxwIrU5lePm0V05aN+oCoEBvXBG
ArPN+a5kpGjJwfcpcBVf9cJ6IsvptGS9de3eTHoTyw==
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDA8vr35ETqvxGs
WHiePMLxlGcsH7I224bDXgLR281vknCB0uXmD3/Ar+zRXk0aWrDGVCXFD+BRK5gR
xRHoxiNydpSZll7+BopVXR+R+S6KA5aEucjkBDHHuuGTPHW6O7In0PamQO1mlK+/
TKfbKu9aEky2O7EZiKnyNou5Gltehp+9HueuUNo1wT8bAiOcILGxGdjZsZvFgrzj
SY1+4x8XafP59co+5D/09Pjx7fA6j28zmzvIqGIdjCuiCrMY3hwfixKvUUTwLt+U
XJNjDXTW7t9Rqil94YKj0+Doit/PLMTlBvs6WqVWTYiS9fHuk+pVzqVkIkSE5f+o
X2ifeYWrAgMBAAECggEAbihK8Ev6rKr5RBQeiPjXs2SuoppV/MvIXLHHmliLKS/J
29S0PGyM202VPtM/4dP1KMXR6nft8WmaIEsKtoKoqijZHfajtRO21pWb+JLy5wi1
XoFTGBrs8MLZFl5mODTsuZ6rsq9O2kn5LJZvHsmcbSgVc9UQfytvG0HY840ArS3g
kSDtUFb1xRui6wtCBKzHVvCT+FXhSBbwkHalmbqP6BefhJ3lW2VonkOcHDrdXPfW
CEN18IJ2v8QYgXqZP6VUlAweNXLJ33ZOl+jXGdygcOG24MFqdw0VtP0XFGk0jnSS
W6dX67BZKeZ71EKaTy02jw5LpQNXA70ismPJHQ2uQQKBgQDuROawnBIW1fC3xOle
m+JmP0eMe0eIQycxRsMXsXhYAA0wV3qYZSLZrNK2eRhmSNt+ODSmZ2Vt11dwOv5u
bo8WONrRlM097SmitS2S+8o7ASem2VKQzyRE72Y9517Q+aNBdLRVtjrRNSw/hfSu
ayLuG36+yukSH7wq7mfoUX34ZwKBgQDPTrgyyw8n5XhZT/qTTRnQJ2GTvPxDzNoJ
IAGhGJGFAb6wgLoSpGx6BC122vuRxcTjkjAiMDci5N2zNW+YZVni+F0KTVvNFfU2
pOTJUg3luRTygCra6O02PxwpbP/9KCBAKq/kYw/eBW+gxhPwP3ZrbAirvBjgBh0I
kIrFijNOHQKBgGUUAbFGZD4fwCCVflLOWnr5uUaVPcFGi6fR1w2EEgNy8iVh1vYz
YVdqg3E5aepqWgLvoRY+or64LbXEsQ70A+tvbxSdxXvR0mnd5lmGS0JAuSuE4gvg
dAhybrMwJf8NB/7KnX4G8mix3/WKxEQB2y2bqGcT+U/g+phTzuy1NXVdAoGBAIrl
jVjK4J60iswcYCEteWwT1rbr2oF60WNnxG+xTF63apJLzWAMNnoSLnwCAKgMv/xR
yFo/v9FrUnduCBUtYupFyeDLMATa/27bUEbq6VDPjw9jfFMr2TONWUsQMvvlVKZp
c2wsS0dQkRhBXr6LZsZWngCiiHAg6HcCkVgFXpapAoGBAJ/8oLGt0Ar+0MTl+gyk
xSqgHnsc5jgqhix3nIoI5oEAbfibdGmRD1S3rtWD9YsnPxMIl+6E5bOAHrmd+Zr8
O7EP+CLvbz4JXidaaa85h9ThXSG5xk1A1UTtSFrp+KolLE1Vvmjjd+R844XsM2wZ
OAHbihzk0iPPphjEWR4lU4Av
-----END PRIVATE KEY-----

View file

@ -0,0 +1,271 @@
function createDocumentRewriter(ctx) {
return function rewriteDocument() {
if (ctx.serviceWorker) return;
const {
HTMLMediaElement,
HTMLScriptElement,
HTMLAudioElement,
HTMLVideoElement,
HTMLInputElement,
HTMLEmbedElement,
HTMLTrackElement,
HTMLAnchorElement,
HTMLIFrameElement,
HTMLAreaElement,
HTMLLinkElement,
HTMLBaseElement,
HTMLFormElement,
HTMLImageElement,
HTMLSourceElement,
} = ctx.window;
const cookie = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'cookie');
const domain = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'domain');
const title = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'title');
const baseURI = Object.getOwnPropertyDescriptor(ctx.window.Node.prototype, 'baseURI');
const cookieEnabled = Object.getOwnPropertyDescriptor(ctx.window.Navigator.prototype, 'cookieEnabled');
let spoofTitle = '';
let spoofDomain = ctx.location.hostname;
if (ctx.window.Document.prototype.write) {
ctx.window.Document.prototype.write = new Proxy(ctx.window.Document.prototype.write, {
apply: (target, that , args) => {
if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ];
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.Document.prototype.hasOwnProperty('cookie')) {
Object.defineProperty(ctx.window.Document.prototype, 'cookie', {
get: new Proxy(cookie.get, {
apply: (target, that, args) => {
const cookies = Reflect.apply(target, that, args);
return ctx.config.cookie ? ctx.cookies.decode(cookies, ctx.meta) : '';
},
}),
set: new Proxy(cookie.set, {
apply: (target, that, [ val ]) => {
return Reflect.apply(target, that, [ ctx.config.cookie ? ctx.cookies.encode(val, ctx.meta) : '' ]);
},
}),
});
};
if (ctx.window.Document.prototype.writeln) {
ctx.window.Document.prototype.writeln = new Proxy(ctx.window.Document.prototype.writeln, {
apply: (target, that , args) => {
if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ];
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.Element.prototype.setAttribute) {
ctx.window.Element.prototype.setAttribute = new Proxy(ctx.window.Element.prototype.setAttribute, {
apply: (target, that, args) => {
if (args[0] && args[1]) {
const handler = ctx.html.attributeRoute({
name: args[0],
value: args[1],
node: that,
});
switch(handler) {
case 'url':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
//if (that.tagName == 'SCRIPT' && args[0] == 'src') flags.push('js');
args[1] = ctx.url.wrap(args[1], ctx.meta);
break;
case 'srcset':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
args[1] = ctx.html.srcset(args[1], ctx.meta);
break;
case 'css':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
args[1] = ctx.css.process(args[1], { ...ctx.meta, context: 'declarationList' });
break;
case 'html':
Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
args[1] = ctx.html.process(args[1], ctx.meta);
break;
case 'delete':
return Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]);
};
};
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.Element.prototype.getAttribute) {
ctx.window.Element.prototype.getAttribute = new Proxy(ctx.window.Element.prototype.getAttribute, {
apply: (target, that, args) => {
if (args[0] && that.hasAttribute(`corrosion-${args[0]}`)) args[0] = `corrosion-${args[0]}`;
return Reflect.apply(target, that, args);
},
});
};
ctx.window.CSSStyleDeclaration.prototype.setProperty = new Proxy(ctx.window.CSSStyleDeclaration.prototype.setProperty, {
apply: (target, that, args) => {
if (args[1]) args[1] = ctx.css.process(args[1], { context: 'value', ...ctx.meta, });
return Reflect.apply(target, that, args);
},
});
if (ctx.window.Audio) {
ctx.window.Audio = new Proxy(ctx.window.Audio, {
construct: (target, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return Reflect.construct(target, args);
},
});
};
[
'innerHTML',
'outerHTML',
].forEach(html => {
const descriptor = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, html);
Object.defineProperty(ctx.window.Element.prototype, html, {
get: new Proxy(descriptor.get, {
apply: (target, that, args) => {
const body = Reflect.apply(target, that, args);
if (!body || html == 'innerHTML' && that.tagName == 'SCRIPT') return body;
return ctx.html.source(body, ctx.meta);
},
}),
set: new Proxy(descriptor.set, {
apply(target, that, [ val ]) {
return Reflect.apply(target, that, [ val ? ctx.html.process(val.toString(), ctx.meta) : val, ]);
},
}),
});
});
[
['background', 'background'],
['backgroundImage', 'background-image'],
['listStyleImage', 'list-style-image'],
].forEach(([key, cssProperty]) => {
Object.defineProperty(ctx.window.CSS2Properties ? ctx.window.CSS2Properties.prototype : ctx.window.CSSStyleDeclaration.prototype, key, {
get() {
return this.getPropertyValue(cssProperty);
},
set(val) {
return this.setProperty(cssProperty, val);
},
});
});
Object.defineProperty(ctx.window.Document.prototype, 'domain', {
get: new Proxy(domain.get, {
apply: () => spoofDomain,
}),
set: new Proxy(domain.set, {
apply: (target, that, [ val ]) => {
if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) return Reflect.apply(target, that, ['']);
return spoofDomain = val;
},
}),
});
if (ctx.config.title) Object.defineProperty(ctx.window.Document.prototype, 'title', {
get: new Proxy(title.get, {
apply: () => spoofTitle,
}),
set: new Proxy(title.set, {
apply: (target, that, [ val ]) => spoofTitle = val,
}),
});
Object.defineProperty(ctx.window.Navigator.prototype, 'cookieEnabled', {
get: new Proxy(cookieEnabled.get, {
apply: () => ctx.config.cookie,
}),
});
Object.defineProperty(ctx.window.Node.prototype, 'baseURI', {
get: new Proxy(baseURI.get, {
apply: (target, that, args) => {
const val = Reflect.apply(target, that, args);
return val.startsWith(ctx.meta.origin) ? ctx.url.unwrap(val, ctx.meta) : val;
},
}),
});
[
{
elements: [ HTMLScriptElement, HTMLMediaElement, HTMLImageElement, HTMLAudioElement, HTMLVideoElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement],
properties: ['src'],
handler: 'url',
},
{
elements: [ HTMLFormElement ],
properties: ['action'],
handler: 'url',
},
{
elements: [ HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement ],
properties: ['href'],
handler: 'url',
},
{
elements: [ HTMLImageElement, HTMLSourceElement ],
properties: ['srcset'],
handler: 'srcset',
},
{
elements: [ HTMLScriptElement ],
properties: ['integrity'],
handler: 'delete',
},
{
elements: [ HTMLIFrameElement ],
properties: ['contentWindow'],
handler: 'window',
},
].forEach(entry => {
entry.elements.forEach(element => {
if (!element) return;
entry.properties.forEach(property => {
if (!element.prototype.hasOwnProperty(property)) return;
const descriptor = Object.getOwnPropertyDescriptor(element.prototype, property);
Object.defineProperty(element.prototype, property, {
get: descriptor.get ? new Proxy(descriptor.get, {
apply: (target, that, args) => {
let val = Reflect.apply(target, that, args);
let flags = [];
switch(entry.handler) {
case 'url':
//if (that.tagName == 'SCRIPT' && property == 'src') flags.push('js');
val = ctx.url.unwrap(val, ctx.meta);
break;
case 'srcset':
val = ctx.html.unsrcset(val, ctx.meta);
break;
case 'delete':
val = that.getAttribute(`corrosion-${property}`);
break;
case 'window':
try {
if (!val.$corrosion) {
val.$corrosion = new ctx.constructor({ ...ctx.config, window: val, });
val.$corrosion.init();
val.$corrosion.meta = ctx.meta;
};
} catch(e) {};
};
return val;
},
}) : undefined,
set: descriptor.set ? new Proxy(descriptor.set, {
apply(target, that, [ val ]) {
let newVal = val;
switch(entry.handler) {
case 'url':
newVal = ctx.url.wrap(newVal, ctx.meta);
break;
case 'srcset':
newVal = ctx.html.srcset(newVal, ctx.meta);
break;
case 'delete':
that.setAttribute(property, newVal);
return newVal;
};
return Reflect.apply(target, that, [ newVal ]);
},
}) : undefined,
});
});
});
});
};
};
module.exports = createDocumentRewriter;

View file

@ -0,0 +1,26 @@
function createHistoryRewriter(ctx) {
return function rewriteHistory() {
if (ctx.serviceWorker) return;
if (ctx.window.History.prototype.pushState) {
ctx.window.History.prototype.pushState = new Proxy(ctx.window.History.prototype.pushState, {
apply: (target, that, args) => {
if (args[2]) args[2] = ctx.url.wrap(args[2], ctx.meta);
const ret = Reflect.apply(target, that, args);
ctx.updateLocation();
return ret;
},
});
};
if (ctx.window.History.prototype.replaceState) {
ctx.window.History.prototype.replaceState = new Proxy(ctx.window.History.prototype.replaceState, {
apply: (target, that, args) => {
if (args[2]) args[2] = ctx.url.wrap(args[2], ctx.meta);
const ret = Reflect.apply(target, that, args);
ctx.updateLocation();
return ret;
},
});
};
};
};
module.exports = createHistoryRewriter;

View file

@ -0,0 +1,92 @@
function createHttpRewriter(ctx = {}) {
return function rewriteHttp() {
if (ctx.window.Request) {
const requestURL = Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url');
ctx.window.Request = new Proxy(ctx.window.Request, {
construct(target, args) {
if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], })
return Reflect.construct(target, args);
},
});
Object.defineProperty(ctx.window.Request.prototype, 'url', {
get: new Proxy(requestURL.get, {
apply: (target, that, args) => {
var url = Reflect.apply(target, that, args);
return url ? ctx.url.unwrap(url, ctx.meta) : url;
},
}),
});
};
if (ctx.window.Response) {
const responseURL = Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url');
Object.defineProperty(ctx.window.Response.prototype, 'url', {
get: new Proxy(responseURL.get, {
apply: (target, that, args) => {
var url = Reflect.apply(target, that, args);
return url ? ctx.url.unwrap(url, ctx.meta) : url;
},
}),
});
};
if (ctx.window.open) {
ctx.window.open = new Proxy(ctx.window.open, {
apply: (target, that, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return Reflect.apply(target, that, args)
},
});
};
if (ctx.window.fetch) {
ctx.window.fetch = new Proxy(ctx.window.fetch, {
apply: (target, that, args) => {
if (args[0] instanceof ctx.window.Request) return Reflect.apply(target, that, args);
if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], });
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.Navigator && ctx.window.Navigator.prototype.sendBeacon) {
ctx.window.Navigator.prototype.sendBeacon = new Proxy(ctx.window.Navigator.prototype.sendBeacon, {
apply: (target, that, args) => {
if (args[0]) ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], });
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.XMLHttpRequest) {
const responseURL = Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL');
ctx.window.XMLHttpRequest.prototype.open = new Proxy(ctx.window.XMLHttpRequest.prototype.open, {
apply: (target, that, args) => {
if (args[1]) args[1] = ctx.url.wrap(args[1], { ...ctx.meta, flags: ['xhr'], });
return Reflect.apply(target, that, args);
},
});
Object.defineProperty(ctx.window.XMLHttpRequest.prototype, 'responseURL', {
get: new Proxy(responseURL.get, {
apply: (target, that, args) => {
const url = Reflect.apply(target, that, args);
return url ? ctx.url.unwrap(url, ctx.meta) : url;
},
}),
});
};
if (ctx.window.postMessage) {
ctx.window.postMessage = new Proxy(ctx.window.postMessage, {
apply: (target, that, args) => {
if (!ctx.serviceWorker && args[1]) args[1] = ctx.meta.origin;
return Reflect.apply(target, that, args);
},
});
};
if (ctx.window.WebSocket && ctx.config.ws) {
ctx.window.WebSocket = new Proxy(ctx.window.WebSocket, {
construct: (target, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0].toString().replace('ws', 'http'), ctx.meta).replace('http', 'ws') + '?origin=' + ctx.location.origin;
return Reflect.construct(target, args);
},
});
};
};
};
module.exports = createHttpRewriter;

View file

@ -0,0 +1,195 @@
const createDocumentRewriter = require('./document');
const createHistoryRewriter = require('./history');
const createHttpRewriter = require('./http');
const createLocation = require('./location');
const createWorkerRewriter = require('./worker');
const defaultConfig = {
prefix: '/service/',
codec: 'plain',
ws: true,
cookie: true,
title: 'Service',
serviceWorker: false,
url: null,
window: false,
};
class Corrosion extends require('../rewrite') {
constructor(config = defaultConfig) {
super(Object.assign(defaultConfig, config));
const corrosion = this;
if (!this.config.window) throw 'Corrosion Error: No window was given.';
this.serviceWorker = this.config.serviceWorker || false;
this.window = this.config.window;
this.document = this.serviceWorker ? this.window.document : {};
this._url = new URL((this.config.url || this.url.unwrap(this.window.location.href, { origin: this.window.location.origin, })));
this.originalXhr = this.window.XMLHttpRequest;
this.meta = {
origin: this.window.location.origin,
get base() {
if (corrosion.serviceWorker) return corrosion._url;
return corrosion.window.document.baseURI != corrosion.location.href ? new URL(corrosion.window.document.baseURI) : corrosion._url;
},
url: this._url,
};
this.location = createLocation(this, this._url);
this.rewriteHttp = createHttpRewriter(this);
this.rewriteDocument = createDocumentRewriter(this);
this.rewriteHistory = createHistoryRewriter(this);
this.rewriteWorker = createWorkerRewriter(this);
if (!this.serviceWorker && this.window.document.currentScript) this.window.document.currentScript.remove();
};
get parent() {
if (this.serviceWorker) return false;
try {
return this.window.parent.$corrosion ? this.window.parent : this.window;
} catch(e) {
return this.window;
};
};
get top() {
if (this.serviceWorker) return false;
try {
return this.window.top.$corrosion ? this.window.top : this.parent;
} catch(e) {
return this.parent;
};
};
init() {
this.rewriteHttp();
this.rewriteDocument();
this.rewriteHistory();
this.rewriteWorker();
this.window.Location = createLocation.Location;
this.window.$corrosionGet$ = this.get$.bind(this);
this.window.$corrosionSet$ = this.set$.bind(this);
this.window.$corrosionGet$m = this.get$m.bind(this);
this.window.$corrosionSet$m = this.set$m.bind(this);
this.window.$corrosionCall$m = this.call$m.bind(this);
};
get$m(obj, key) {
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) {
return this.parent.$corrosion.get$m(this.parent, key);
};
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) {
return this.top.$corrosion.get$m(this.top, key);
};
if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') return this.location;
if (!this.serviceWorker && obj == this.window && key == 'parent' && this.window != this.window.parent) return this.parent;
if (!this.serviceWorker && obj == this.window && key == 'top' && this.window != this.window.top) return this.top;
return obj[key];
};
set$m(obj, key, val, operator) {
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) {
return this.parent.$corrosion.set$m(this.parent, key, val, operator);
};
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) {
return this.top.$corrosion.set$m(this.top, key, val, operator);
};
if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') obj = this;
switch(operator) {
case '+=':
return obj[key] += val;
case '-=':
return obj[key] -= val;
case '*=':
return obj[key] *= val;
case '/=':
return obj[key] /= val;
case '%=':
return obj[key] %= val;
case '**=':
return obj[key] **= val;
case '<<=':
return obj[key] <<= val;
case '>>=':
return obj[key] >>= val;
case '>>>=':
return obj[key] >>>= val;
case '&=':
return obj[key] &= val;
case '^=':
return obj[key] ^= val;
case '|=':
return obj[key] |= val;
case '&&=':
return obj[key] &&= val;
case '||=':
return obj[key] ||= val;
case '??=':
return obj[key] ??= val;
case '++':
return obj[key]++;
case '--':
return obj[key]--;
case '=':
default:
return obj[key] = val;
};
};
call$m(obj, key, args) {
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) {
return this.parent.$corrosion.call$m(this.parent, key, args);
};
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) {
return this.top.$corrosion.call$m(this.top, key, args);
};
return obj[key](...args);
};
get$(obj) {
if (obj == this.window.location) return this.location;
if (!this.serviceWorker && obj == this.window.parent) return this.parent;
if (!this.serviceWorker && obj == this.window.top) return this.top;
return obj;
};
set$(obj, val, operator) {
if (obj == this.window.location) return this.set$(this.location, val, operator);
if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) return this.parent.set$(this.parent, val, operator);
if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) return this.top.set$(this.top, val, operator);
switch(operator) {
case '+=':
return obj += val;
case '-=':
return obj -= val;
case '*=':
return obj *= val;
case '/=':
return obj /= val;
case '%=':
return obj %= val;
case '**=':
return obj **= val;
case '<<=':
return obj <<= val;
case '>>=':
return obj >>= val;
case '>>>=':
return obj >>>= val;
case '&=':
return obj &= val;
case '^=':
return obj ^= val;
case '|=':
return obj |= val;
case '&&=':
return obj &&= val;
case '||=':
return obj ||= val;
case '??=':
return obj ??= val;
case '++':
return obj++;
case '--':
return obj--;
case '=':
default:
return obj = val;
};
};
updateLocation() {
this._url = new URL(this.url.unwrap(this.window.location.href, this.meta));
this.location = createLocation(this, this._url);
};
};
globalThis.Corrosion = Corrosion;

View file

@ -0,0 +1,56 @@
class Location {
get [Symbol.toPrimitive]() {
return () => this.href;
};
};
function createLocation(ctx, url) {
const _location = new Location();
const _url = new URL(url);
[
'hash',
'host',
'hostname',
'href',
'pathname',
'port',
'protocol',
'search',
'origin',
].forEach(property => {
Object.defineProperty(_location, property, {
get() {
return _url[property];
},
set(val) {
if (ctx.serviceWorker || property == 'origin') return;
if (property == 'href') {
return ctx.window.location.href = ctx.url.wrap(new URL(val, _url).href);
};
_url[property] = val;
return ctx.window.location.href = ctx.url.wrap(_url);
},
});
});
if (!ctx.serviceWorker) [
'assign',
'replace',
'reload',
].forEach(method => {
_location[method] = new Proxy(ctx.window.location[method], {
apply(target, that, args) {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return Reflect.apply(target.bind(ctx.window.location), that, args);
},
});
});
_location.toString = new Proxy(_url.toString, {
apply(target, that, args) {
return Reflect.apply(target.bind(_url), that, args);
},
});
return _location;
};
createLocation.Location = Location;
module.exports = createLocation;

View file

@ -0,0 +1,33 @@
function createWorkerRewriter(ctx = {}) {
return function rewriteWorker() {
if (ctx.window.Worker) {
ctx.window.Worker = new Proxy(ctx.window.Worker, {
construct: (target, args) => {
if (args[0]) {
if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) {
const xhr = new ctx.originalXhr();
xhr.open('GET', args[0], false);
xhr.send();
const script = ctx.js.process(xhr.responseText, ctx.location.origin + args[0].trim().slice(`blob:${ctx.window.location.origin}`.length));
const blob = new Blob([ script ], { type: 'application/javascript' });
args[0] = URL.createObjectURL(blob);
} else {
args[0] = ctx.url.wrap(args[0], ctx.meta);
};
};
return Reflect.construct(target, args);
},
});
};
if (ctx.serviceWorker && ctx.window.importScripts) {
ctx.window.importScripts = new Proxy(ctx.window.importScripts, {
apply: (target, that, args) => {
if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta);
return Reflect.apply(target, that, args);
},
});
};
};
};
module.exports = createWorkerRewriter;

View file

@ -0,0 +1,66 @@
exports.xor = {
encode(str){
if (!str) return str;
return encodeURIComponent(str.toString().split('').map((char, ind) => ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char).join(''));
},
decode(str){
if (!str) return str;
return decodeURIComponent(str).split('').map((char, ind) => ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char).join('');
},
};
exports.plain = {
encode(str) {
if (!str) return str;
return encodeURIComponent(str);
},
decode(str) {
if (!str) return str;
return decodeURIComponent(str);
},
};
exports.base64 = {
encode(str){
if (!str) return str;
str = str.toString();
const b64chs = Array.from('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=');
let u32;
let c0;
let c1;
let c2;
let asc = '';
let pad = str.length % 3;
for (let i = 0; i < str.length;) {
if((c0 = str.charCodeAt(i++)) > 255 || (c1 = str.charCodeAt(i++)) > 255 || (c2 = str.charCodeAt(i++)) > 255)throw new TypeError('invalid character found');
u32 = (c0 << 16) | (c1 << 8) | c2;
asc += b64chs[u32 >> 18 & 63]
+ b64chs[u32 >> 12 & 63]
+ b64chs[u32 >> 6 & 63]
+ b64chs[u32 & 63];
}
return encodeURIComponent(pad ? asc.slice(0, pad - 3) + '==='.substr(pad) : asc);
},
decode(str){
if (!str) return str;
str = decodeURIComponent(str.toString());
const b64tab = {"0":52,"1":53,"2":54,"3":55,"4":56,"5":57,"6":58,"7":59,"8":60,"9":61,"A":0,"B":1,"C":2,"D":3,"E":4,"F":5,"G":6,"H":7,"I":8,"J":9,"K":10,"L":11,"M":12,"N":13,"O":14,"P":15,"Q":16,"R":17,"S":18,"T":19,"U":20,"V":21,"W":22,"X":23,"Y":24,"Z":25,"a":26,"b":27,"c":28,"d":29,"e":30,"f":31,"g":32,"h":33,"i":34,"j":35,"k":36,"l":37,"m":38,"n":39,"o":40,"p":41,"q":42,"r":43,"s":44,"t":45,"u":46,"v":47,"w":48,"x":49,"y":50,"z":51,"+":62,"/":63,"=":64};
str = str.replace(/\s+/g, '');
str += '=='.slice(2 - (str.length & 3));
let u24;
let bin = '';
let r1;
let r2;
for (let i = 0; i < str.length;) {
u24 = b64tab[str.charAt(i++)] << 18
| b64tab[str.charAt(i++)] << 12
| (r1 = b64tab[str.charAt(i++)]) << 6
| (r2 = b64tab[str.charAt(i++)]);
bin += r1 === 64 ? String.fromCharCode(u24 >> 16 & 255)
: r2 === 64 ? String.fromCharCode(u24 >> 16 & 255, u24 >> 8 & 255)
: String.fromCharCode(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255);
};
return bin;
},
};

View file

@ -0,0 +1,95 @@
// -------------------
// This file is shared both by the server and client.
// Do not include any browser or node specific APIs
// -------------------
class CookieStore {
constructor(val = ''){
this.data = {};
val.split(';').map(cookie => {
var [ name, val = ''] = cookie.trimStart().split('=');
if (name) this.data[name] = val;
});
};
has(name){
if (!name || !this.data[name]) return false;
return true;
};
get(name){
return this.has(name) ? this.data[name] : null;
};
set(name, val){
if (!name || !val) return;
return this.data[name] = val;
};
delete(name){
if (!name) return;
return delete this.data[name];
};
forEach(action = (node, key) => null){
for (let prop in this.data) action(this.data[prop], prop);
};
serialize(){
var str = '';
for (let i in this.data) str += ` ${i}=${this.data[i]};`;
return str.substr(1);
};
};
class SetCookie {
constructor(val = ''){
var [ [ name, value = '' ], ...data ] = val.split(';').map(str => str.trimStart().split('='));
this.name = name;
this.value = value;
this.expires = null;
this.maxAge = null;
this.domain = null;
this.secure = false;
this.httpOnly = false;
this.path = null;
this.sameSite = null;
data.forEach(([name = null, value = null]) => {
if (typeof name == 'string') switch(name.toLowerCase()){
case 'domain':
this.domain = value;
break;
case 'secure':
this.secure = true;
break;
case 'httponly':
this.httpOnly = true;
break;
case 'samesite':
this.sameSite = value;
break;
case 'path':
this.path = value;
break;
case 'expires':
this.expires = value;
break;
case 'maxage':
this.maxAge = value;
break;
};
});
};
serialize(){
if (!this.name) return;
var str = `${this.name}=${this.value};`;
if (this.expires) str += ` Expires=${this.expires};`;
if (this.maxAge) str += ` Max-Age=${this.max_age};`;
if (this.domain) str += ` Domain=${this.domain};`;
if (this.secure) str += ` Secure;`;
if (this.httpOnly) str += ` HttpOnly;`;
if (this.path) str += ` Path=${this.path};`;
if (this.sameSite) str += ` SameSite=${this.sameSite};`;
return str;
};
};
exports.CookieStore = CookieStore;
exports.SetCookie = SetCookie;

View file

@ -0,0 +1,34 @@
const { SetCookie, CookieStore } = require('./cookie-parser');
class CookieRewriter {
constructor(ctx) {
this.ctx = ctx;
};
decode(store, config = {}) {
const url = new URL(config.url);
const cookies = new CookieStore(store);
cookies.forEach((val, key) => {
if (!key.includes('@') || key.slice(key.length - url.hostname.length) != url.hostname) return cookies.delete(key);
cookies.delete(key);
cookies.set(key.substr(0, key.length - url.hostname.length - 1), val);
});
return cookies.serialize();
};
encode(input, config = {}) {
if (Array.isArray(input)) {
const rw = [ ...input ];
for (let i in rw) rw[i] = this.encode(rw[i], config);
return rw;
};
const url = new URL(config.url);
const cookie = new SetCookie(input);
if (!cookie.name) return null;
cookie.domain = config.domain;
cookie.secure = config.secure;
cookie.name += `@${url.hostname}`;
cookie.path = this.ctx.prefix;
return cookie.serialize() || '';
};
};
module.exports = CookieRewriter;

67
src/Corrosion/lib/css.js Normal file
View file

@ -0,0 +1,67 @@
const csstree = require('css-tree');
class CSSRewriter {
constructor(ctx) {
this.ctx = ctx;
};
process(source, config = {}) {
const ast = csstree.parse(source, {
context: config.context || 'stylesheet',
parseCustomProperty: true,
});
const urls = csstree.findAll(ast, node =>
node.type == 'Url'
);
const imports = csstree.findAll(ast, node =>
node.type == 'Atrule' && node.name == 'import' && node.prelude && node.prelude.type == 'AtrulePrelude' && node.prelude.children.head.data.type == 'String'
);
urls.forEach(({ value }) => {
switch(value.type) {
case 'String':
const quote = value.value.substring(0, 1);
value.value = quote + this.ctx.url.wrap(value.value.slice(1).slice(0, -1), config) + quote;
break;
case 'Raw':
value.value = this.ctx.url.wrap(value.value, config);
break;
};
});
imports.forEach(({ prelude }) => {
const { data } = prelude.children.head;
const quote = data.value.substring(0, 1);
data.value = quote + this.ctx.url.wrap(data.value.slice(1).slice(0, -1), config) + quote;
});
return csstree.generate(ast);
};
source(processed, config = {}) {
const ast = csstree.parse(processed, {
context: config.context || 'stylesheet',
parseCustomProperty: true,
});
const urls = csstree.findAll(ast, node =>
node.type == 'Url'
);
const imports = csstree.findAll(ast, node =>
node.type == 'Atrule' && node.name == 'import' && node.prelude && node.prelude.type == 'AtrulePrelude' && node.prelude.children.head.data.type == 'String'
);
urls.forEach(({ value }) => {
switch(value.type) {
case 'String':
const quote = value.value.substring(0, 1);
value.value = quote + this.ctx.url.unwrap(value.value.slice(1).slice(0, -1), config) + quote;
break;
case 'Raw':
value.value = this.ctx.url.unwrap(value.value, config);
break;
};
});
imports.forEach(({ prelude }) => {
const { data } = prelude.children.head;
const quote = data.value.substring(0, 1);
data.value = quote + this.ctx.url.unwrap(data.value.slice(1).slice(0, -1), config) + quote;
});
return csstree.generate(ast);
};
};
module.exports = CSSRewriter;

2562
src/Corrosion/lib/esotope.js Normal file

File diff suppressed because it is too large Load diff

226
src/Corrosion/lib/html.js Normal file
View file

@ -0,0 +1,226 @@
const parse5 = require('parse5');
class HTMLRewriter {
constructor(ctx) {
this.ctx = ctx;
this.attrs = [
{
tags: ['form', 'object', 'a', 'link', 'area', 'base', 'script', 'img', 'audio', 'video', 'input', 'embed', 'iframe', 'track', 'source', 'html', 'table', 'head'],
attrs: ['src', 'href', 'ping', 'data', 'movie', 'action', 'poster', 'profile', 'background'],
handler: 'url',
},
{
tags: ['iframe'],
attrs: ['srcdoc'],
handler: 'html',
},
{
tags: ['img', 'link', 'source'],
attrs: ['srcset', 'imagesrcset'],
handler: 'srcset',
},
{
tags: '*',
attrs: ['style'],
handler: 'css',
},
{
tags: '*',
attrs: ['http-equiv', 'integrity', 'nonce', 'crossorigin'],
handler: 'delete',
},
];
};
process(source, config = {}) {
const ast = parse5[config.document ? 'parse' : 'parseFragment'](source);
const meta = {
origin: config.origin,
base: new URL(config.base),
};
iterate(ast, node => {
if (!node.tagName) return;
switch(node.tagName) {
case 'STYLE':
if (node.textContent) node.textContent = this.ctx.css.process(node.textContent, meta);
break;
case 'TITLE':
if (node.textContent && this.ctx.config.title) node.textContent = this.ctx.config.title;
break;
case 'SCRIPT':
if (node.getAttribute('type') != 'application/json' && node.textContent) node.textContent = this.ctx.js.process(node.textContent);
break;
case 'BASE':
if (node.hasAttribute('href')) meta.base = new URL(node.getAttribute('href'), config.base);
break;
};
node.attrs.forEach(attr => {
const handler = this.attributeRoute({
...attr,
node,
});
let flags = [];
if (node.tagName == 'SCRIPT' && attr.name == 'src') flags.push('js');
if (node.tagName == 'LINK' && node.getAttribute('rel') == 'stylesheet') flags.push('css');
switch(handler) {
case 'url':
node.setAttribute(`corrosion-${attr.name}`, attr.value);
attr.value = this.ctx.url.wrap(attr.value, { ...meta, flags });
break;
case 'srcset':
node.setAttribute(`corrosion-${attr.name}`, attr.value);
attr.value = this.srcset(attr.value, meta);
break;
case 'css':
attr.value = this.ctx.css.process(attr.value, { ...meta, context: 'declarationList' });
break;
case 'html':
node.setAttribute(`corrosion-${attr.name}`, attr.value);
attr.value = this.process(attr.value, { ...config, ...meta });
break;
case 'delete':
node.removeAttribute(attr.name);
break;
};
});
});
if (config.document) {
for (let i in ast.childNodes) if (ast.childNodes[i].tagName == 'html') ast.childNodes[i].childNodes.forEach(node => {
if (node.tagName == 'head') {
node.childNodes.unshift(...this.injectHead(config.base));
};
});
};
return parse5.serialize(ast);
};
source(processed, config = {}) {
const ast = parse5[config.document ? 'parse' : 'parseFragment'](processed);
iterate(ast, node => {
if (!node.tagName) return;
node.attrs.forEach(attr => {
if (node.hasAttribute(`corrosion-${attr.name}`)) {
attr.value = node.getAttribute(`corrosion-${attr.name}`);
node.removeAttribute(`corrosion-${attr.name}`)
};
});
});
return parse5.serialize(ast);
};
injectHead() {
return [
{
nodeName: 'title',
tagName: 'title',
childNodes: [
{
nodeName: '#text',
value: this.ctx.config.title || '',
}
],
attrs: [],
},
{
nodeName: 'script',
tagName: 'script',
childNodes: [],
attrs: [
{
name: 'src',
value: this.ctx.prefix + 'index.js',
},
],
},
{
nodeName: 'script',
tagName: 'script',
childNodes: [
{
nodeName: '#text',
value: `window.$corrosion = new Corrosion({ window, codec: '${this.ctx.config.codec || 'plain'}', prefix: '${this.ctx.prefix}', ws: ${this.ctx.config.ws}, cookie: ${this.ctx.config.cookie}, title: ${typeof this.ctx.config.title == 'boolean' ? this.ctx.config.title : '\'' + this.ctx.config.title + '\''}, }); $corrosion.init(); document.currentScript.remove();`
},
],
attrs: [],
}
];
}
attributeRoute(data) {
const match = this.attrs.find(entry => entry.tags == '*' && entry.attrs.includes(data.name) || entry.tags.includes(data.node.tagName.toLowerCase()) && entry.attrs.includes(data.name));
return match ? match.handler : false;
};
srcset(val, config = {}) {
return val.split(',').map(src => {
const parts = src.trimStart().split(' ');
if (parts[0]) parts[0] = this.ctx.url.wrap(parts[0], config);
return parts.join(' ');
}).join(', ');
};
unsrcset(val, config = {}) {
return val.split(',').map(src => {
const parts = src.trimStart().split(' ');
if (parts[0]) parts[0] = this.ctx.url.unwrap(parts[0], config);
return parts.join(' ');
}).join(', ');
};
};
class Parse5Wrapper {
constructor(node){
this.raw = node || {
attrs: [],
childNodes: [],
namespaceURI: '',
nodeName: '',
parentNode: {},
tagName: '',
};
};
hasAttribute(name){
return this.raw.attrs.some(attr => attr.name == name);
};
removeAttribute(name){
if (!this.hasAttribute(name)) return;
this.raw.attrs.splice(this.raw.attrs.findIndex(attr => attr.name == name), 1);
};
setAttribute(name, val = ''){
if (!name) return;
this.removeAttribute(name);
this.raw.attrs.push({
name: name,
value: val,
});
};
getAttribute(name){
return (this.raw.attrs.find(attr => attr.name == name) || { value: null }).value;
};
get textContent(){
return (this.raw.childNodes.find(node => node.nodeName == '#text') || { value: '', }).value
};
set textContent(val){
if (this.raw.childNodes.some(node => node.nodeName == '#text')) return this.raw.childNodes[this.raw.childNodes.findIndex(node => node.nodeName == '#text')].value = val;
this.raw.childNodes.push({
nodeName: '#text',
value: val,
});
};
get tagName(){
return (this.raw.tagName || '').toUpperCase();
};
get nodeName(){
return this.raw.nodeName;
};
get parentNode(){
return this.raw.parentNode;
};
get childNodes(){
return this.raw.childNodes || [];
};
get attrs() {
return this.raw.attrs || [];
};
};
function iterate(ast, fn = (node = Parse5Wrapper.prototype) => null) {
fn(new Parse5Wrapper(ast));
if (ast.childNodes) for (let i in ast.childNodes) iterate(ast.childNodes[i], fn);
};
module.exports = HTMLRewriter;

185
src/Corrosion/lib/js.js Normal file
View file

@ -0,0 +1,185 @@
const { parse } = require('acorn-hammerhead');
const { generate } = require('./esotope');
class JSRewriter {
constructor(ctx) {
this.parseOptions = {
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
ecmaVersion: 2021,
};
this.generationOptions = {
format: {
quotes: 'double',
escapeless: true,
compact: true,
},
};
this.rewrite = ['location', 'parent', 'top'];
this.map = [
{
type: 'MemberExpression',
handler: (node, parent) => {
let rewrite = false;
if (parent.type == 'UnaryExpression' && parent.operator == 'delete') return;
if (parent.type == 'NewExpression' && parent.callee == node) return;
if (parent.type === 'CallExpression' && parent.callee === node) return;
if (node.preventRewrite) return;
switch(node.property.type) {
case 'Identifier':
//if (node.computed) rewrite = true;
if (!node.computed && this.rewrite.includes(node.property.name)) {
node.property = this.createLiteral(node.property.name);
rewrite = true;
};
break;
case 'Literal':
if (this.rewrite.includes(node.property.name)) rewrite = true;
break;
case 'TemplateLiteral':
rewrite = true;
break;
default:
if (node.computed) rewrite = true;
};
if (rewrite) {
let identifier = '$corrosionGet$m';
let nodeToRewrite = node;
const args = [
node.object,
node.property,
];
if (node.computed) args[1].preventRewrite = true;
if (parent.type == 'AssignmentExpression' && parent.left == node) {
identifier = '$corrosionSet$m';
nodeToRewrite = parent;
args.push(parent.right, this.createLiteral(parent.operator));
};
if (parent.type == 'CallExpression' && parent.callee == node) {
identifier = '$corrosionCall$m';
nodeToRewrite = parent;
args.push(this.createArrayExpression(...parent.arguments))
};
if (parent.type == 'UpdateExpression') {
identifier = '$corrosionSet$m';
nodeToRewrite = parent;
args.push(this.createLiteral(null), this.createLiteral(parent.operator));
};
Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier, }, args));
};
},
},
{
type: 'Identifier',
handler: (node, parent) => {
if (parent.type == 'MemberExpression' && parent.property == node) return; // window.location;
if (parent.type == 'LabeledStatement') return; // { location: null, };
if (parent.type == 'VariableDeclarator' && parent.id == node) return;
if (parent.type == 'Property' && parent.key == node) return;
if (parent.type == 'MethodDefinition') return;
if (parent.type == 'ClassDeclaration') return;
if (parent.type == 'RestElement') return;
if (parent.type == 'ExportSpecifier') return;
if (parent.type == 'ImportSpecifier') return;
if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression' || parent.type == 'ArrowFunctionExpression') && parent.params.includes(node)) return;
if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression') && parent.id == node) return;
if (parent.type == 'AssignmentPattern' && parent.left == node) return;
if (!this.rewrite.includes(node.name)) return;
if (node.preventRewrite) return;
let identifier = '$corrosionGet$';
let nodeToRewrite = node;
const args = [
this.createIdentifier(node.name, true),
];
if (parent.type == 'AssignmentExpression' && parent.left == node) {
identifier = '$corrosionSet$';
nodeToRewrite = parent;
args.push(parent.right);
args.push(this.createLiteral(parent.operator));
};
Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier }, args));
},
},
{
type: 'ImportDeclaration',
handler: (node, parent, url) => {
if (node.source.type != 'Literal' || !url) return;
node.source = this.createLiteral(ctx.url.wrap(node.source.value, { base: url, }));
},
},
{
type: 'ImportExpression',
handler: (node, parent) => {
node.source = this.createCallExpression(this.createMemberExpression(this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('url')), this.createIdentifier('wrap')), [
node.source,
this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('meta')),
]);
},
},
];
this.ctx = ctx;
};
process(source, url) {
try {
const ast = parse(source, this.parseOptions);
this.iterate(ast, (node, parent) => {
const fn = this.map.find(entry => entry.type == (node || {}).type);
if (fn) fn.handler(node, parent, url);
});
return (url ? this.createHead(url) : '') + generate(ast, this.generationOptions);
} catch(e) {
return source;
};
};
createHead(url) {
return `
if (!self.$corrosion && self.importScripts) {
importScripts(location.origin + '${this.ctx.prefix}index.js');
self.$corrosion = new Corrosion({ url: '${url}', codec: '${this.ctx.config.codec || 'plain'}', serviceWorker: true, window: self, prefix: '${this.ctx.prefix || '/service/'}', ws: ${this.ctx.config.ws || true}, cookies: ${this.ctx.config.cookies || false}, title: '${this.ctx.config.title}', }); $corrosion.init();
};\n`;
};
iterate(ast, handler) {
if (typeof ast != 'object' || !handler) return;
walk(ast, null, handler);
function walk(node, parent, handler) {
if (typeof node != 'object' || !handler) return;
handler(node, parent, handler);
for (const child in node) {
if (Array.isArray(node[child])) {
node[child].forEach(entry => walk(entry, node, handler));
} else {
walk(node[child], node, handler);
};
};
};
};
createCallExpression(callee, args) {
return { type: 'CallExpression', callee, arguments: args, optional: false, };
};
createArrayExpression(...elements) {
return {
type: 'ArrayExpression',
elements,
};
};
createMemberExpression(object, property) {
return {
type: 'MemberExpression',
object,
property,
};
};
createLiteral(value) {
return {
type: 'Literal',
value,
}
};
createIdentifier(name, preventRewrite) {
return { type: 'Identifier', name, preventRewrite: preventRewrite || false, };
};
};
module.exports = JSRewriter;

View file

@ -0,0 +1,27 @@
const URLWrapper = require('./url');
const CookieRewriter = require('./cookie');
const CSSRewriter = require('./css');
const HTMLRewriter = require('./html');
const JSRewriter = require('./js');
const defaultConfig = {
prefix: '/service/',
codec: 'plain',
ws: true,
cookie: true,
title: 'Service',
};
class Rewrite {
constructor(config = defaultConfig) {
this.config = Object.assign(defaultConfig, config);
this.prefix = this.config.prefix;
this.url = new URLWrapper(this.config || {});
this.codec = this.url.codec;
this.cookies = new CookieRewriter(this);
this.css = new CSSRewriter(this);
this.js = new JSRewriter(this);
this.html = new HTMLRewriter(this);
};
};
module.exports = Rewrite;

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

34
src/Corrosion/lib/url.js Normal file
View file

@ -0,0 +1,34 @@
const codec = require('./codec');
const defaultConfig = {
prefix: '/service/',
codec: 'plain'
};
class URLWrapper {
constructor(config = defaultConfig) {
this.prefix = config.prefix || defaultConfig.prefix;
this.codec = codec[config.codec || 'plain'] || codec['plain'];
this.regex = /^(#|about:|data:|blob:|mailto:|javascript:)/;
};
wrap(val, config = {}) {
if (!val || this.regex.test(val)) return val;
let flags = '';
(config.flags || []).forEach(flag => flags += `${flag}_/`);
if (config.base) try {
if (!['http:', 'https:', 'ws:', 'wss:'].includes(new URL(val, config.base).protocol)) return val;
} catch(e) {
return val;
};
return (config.origin || '') + this.prefix + flags + this.codec.encode(config.base ? new URL(val, config.base) : val) + '/';
};
unwrap(val, config = {}) {
if (!val || this.regex.test(val)) return val;
let processed = val.slice((config.origin || '').length + this.prefix.length);
const flags = ('/' + processed).match(/(?<=\/)(.*?)(?=_\/)/g) || [];
flags.forEach(flag => processed = processed.slice(`${flag}_/`.length));
let [ url, leftovers ] = processed.split(/\/(.+)?/);
return config.flags ? { value: this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : ''), flags } : this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : '');
};
};
module.exports = URLWrapper;

View file

@ -0,0 +1,47 @@
{
"_from": "corrosion",
"_id": "corrosion@1.0.0",
"_inBundle": false,
"_integrity": "sha512-jRKoOTWBmpylgOARW6vsVE4DaSsg9Qnt0E7c7Rp5q2kFAlYrr8KrhjUWJrKmlHL8wbLi8EUdlNId4vvL0PHGdA==",
"_location": "/corrosion",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "corrosion",
"name": "corrosion",
"escapedName": "corrosion",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/corrosion/-/corrosion-1.0.0.tgz",
"_shasum": "22aa376a6bd72720f3994f8e091b880b87c02cc4",
"_spec": "corrosion",
"_where": "C:\\Users\\Not A Porxy\\Documents\\HolyUnblockerWorkspace\\HolyUB",
"author": "",
"bundleDependencies": false,
"dependencies": {
"acorn-hammerhead": "^0.5.0",
"css-tree": "^1.1.3",
"esotope-hammerhead": "^0.6.1",
"parse5": "^6.0.1",
"webpack": "^5.46.0"
},
"deprecated": false,
"description": "Titanium Networks main web proxy.\r Successor to [Alloy](https://github.com/titaniumnetwork-dev/alloy)\r # Installation:\r ```\r npm i corrosion\r ```",
"directories": {
"lib": "lib"
},
"license": "ISC",
"main": "lib/server/index.js",
"name": "corrosion",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"version": "1.0.0"
}