├── .gitignore ├── .replit ├── LICENSE ├── README.md ├── app.js ├── app.json ├── config.json ├── lib ├── browser │ ├── document.js │ ├── history.js │ ├── http.js │ ├── index.js │ ├── location.js │ └── worker.js ├── codec.js ├── cookie-parser.js ├── cookie.js ├── css.js ├── esotope.js ├── html.js ├── js.js ├── rewrite.js ├── server │ ├── bundle.js │ ├── decompress.js │ ├── gateway.js │ ├── headers.js │ ├── index.js │ ├── middleware.js │ ├── request.js │ ├── rewrite-body.js │ └── upgrade.js └── url.js ├── package-lock.json ├── package.json └── public ├── 404.html ├── css └── style.css ├── img └── logo.png ├── index.html └── js ├── go.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | language = "nodejs" 2 | run = "npm start" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Fog Network 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated! 2 | This project is deprecated! I am no longer maintaining this project. Why not check out [Metallic](https://github.com/Metallic-Web/Metallic)? 3 | 4 | # Shadow 5 | Shadow is a simple yet stunning service built to access any website 6 | 7 | Join our [discord](https://discord.gg/yk33HZSZkU) for more links 8 | 9 | 10 | 11 | 12 | 13 | ### Features 14 | - Incredible single page design 15 | - Simple to use 16 | - Fully working omnibox 17 | - Easy tab cloaking 18 | - Quick links for easy access 19 | 20 | ### Setup 21 | 22 | ### Locally 23 | 24 | ```sh 25 | git clone https://github.com/FogNetwork/Shadow 26 | 27 | cd Shadow 28 | 29 | npm install 30 | 31 | npm start 32 | ``` 33 | 34 | ### Deploy 35 | 36 | Click one of the buttons above and follow the steps 37 | 38 | ### Configuration 39 | 40 | ```json 41 | { 42 | "port": "8080", 43 | "prefix": "/service/", 44 | "codec": "xor" 45 | } 46 | ``` 47 | 48 | `"port": "8080"` - Changes the port 49 | 50 | `"prefix": "/service/"` - Changes the proxy prefix 51 | 52 | `"codec": "xor"` - Changes the proxy encoding (plain, base64 or xor) 53 | 54 | ### Support 55 | 56 | Nebelung - [Nebelung#1335](https://discord.com/users/887118260963782686) 57 | 58 | ### Proxy 59 | 60 | [Corrosion](https://github.com/titaniumnetwork-dev/Corrosion) 61 | 62 | [Modified Corrosion](https://github.com/BinBashBanana/Corrosion-Heroku) 63 | 64 | ### Credits 65 | 66 | [Nebelung](https://github.com/Nebelung-Dev) - Owner and Main Developer 67 | 68 | [EnderKingJ](https://github.com/EnderKingJ) - Proxy Developer 69 | 70 | [Quite A Fancy Emerald](https://github.com/QuiteAFancyEmerald) - Holy Unblocker King 71 | 72 | [Caracal.js](https://github.com/caracal-js) - Proxy Developer 73 | 74 | [MikeLime](https://github.com/MikeLime-dev) - Developer 75 | 76 | [Binary Person](https://github.com/binary-person) - Creator of Womginx 77 | 78 | [Shirt](https://github.com/shirt-dev) - Proxy Developer 79 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © Fog Network 3 | Made by Nebelung 4 | MIT license: https://opensource.org/licenses/MIT 5 | */ 6 | 7 | const express = require("express") 8 | const app = express() 9 | const fetch = require("node-fetch") 10 | const config = require("./config.json") 11 | const port = process.env.PORT || config.port 12 | const Corrosion = require("./lib/server") 13 | 14 | const proxy = new Corrosion({ 15 | prefix: config.prefix, 16 | codec: config.codec, 17 | title: "Shadow", 18 | forceHttps: true, 19 | requestMiddleware: [ 20 | Corrosion.middleware.blacklist([ 21 | "accounts.google.com", 22 | ], "Page is blocked"), 23 | ] 24 | }); 25 | 26 | proxy.bundleScripts(); 27 | 28 | app.use(express.static("./public", { 29 | extensions: ["html"] 30 | })); 31 | 32 | app.get("/", function(req, res){ 33 | res.sendFile("index.html", {root: "./public"}); 34 | }); 35 | 36 | app.get("/suggestions", function(req, res){ 37 | async function getsuggestions() { 38 | var term = req.query.q || ""; 39 | var response = await fetch("https://duckduckgo.com/ac/?q=" + term + "&type=list"); 40 | var result = await response.json(); 41 | var suggestions = result[1] 42 | res.send(suggestions) 43 | } 44 | getsuggestions() 45 | }); 46 | 47 | app.use(function (req, res) { 48 | if (req.url.startsWith(proxy.prefix)) { 49 | proxy.request(req,res); 50 | } else { 51 | res.status(404).sendFile("404.html", {root: "./public"}); 52 | } 53 | }) 54 | 55 | app.listen(port, () => { 56 | console.log(`Shadow is running at localhost:${port}`) 57 | }) -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shadow", 3 | "description": "Shadow is a simple yet stunning service built to access any website", 4 | "repository": "https://github.com/FogNetwork/Shadow", 5 | "logo": "https://raw.githubusercontent.com/FogNetwork/Shadow/main/public/img/logo.png" 6 | } 7 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": "8080", 3 | "prefix": "/service/", 4 | "codec": "xor" 5 | } -------------------------------------------------------------------------------- /lib/browser/document.js: -------------------------------------------------------------------------------- 1 | function createDocumentRewriter(ctx) { 2 | return function rewriteDocument() { 3 | if (ctx.serviceWorker) return; 4 | const { 5 | HTMLMediaElement, 6 | HTMLScriptElement, 7 | HTMLAudioElement, 8 | HTMLVideoElement, 9 | HTMLInputElement, 10 | HTMLEmbedElement, 11 | HTMLTrackElement, 12 | HTMLAnchorElement, 13 | HTMLIFrameElement, 14 | HTMLAreaElement, 15 | HTMLLinkElement, 16 | HTMLBaseElement, 17 | HTMLFormElement, 18 | HTMLImageElement, 19 | HTMLSourceElement, 20 | } = ctx.window; 21 | const cookie = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'cookie'); 22 | const domain = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'domain'); 23 | const title = Object.getOwnPropertyDescriptor(ctx.window.Document.prototype, 'title'); 24 | const baseURI = Object.getOwnPropertyDescriptor(ctx.window.Node.prototype, 'baseURI'); 25 | const cookieEnabled = Object.getOwnPropertyDescriptor(ctx.window.Navigator.prototype, 'cookieEnabled'); 26 | let spoofTitle = ''; 27 | let spoofDomain = ctx.location.hostname; 28 | 29 | if (ctx.window.Document.prototype.write) { 30 | ctx.window.Document.prototype.write = new Proxy(ctx.window.Document.prototype.write, { 31 | apply: (target, that , args) => { 32 | if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ]; 33 | return Reflect.apply(target, that, args); 34 | }, 35 | }); 36 | }; 37 | if (ctx.window.Document.prototype.hasOwnProperty('cookie')) { 38 | Object.defineProperty(ctx.window.Document.prototype, 'cookie', { 39 | get: new Proxy(cookie.get, { 40 | apply: (target, that, args) => { 41 | const cookies = Reflect.apply(target, that, args); 42 | return ctx.config.cookie ? ctx.cookies.decode(cookies, ctx.meta) : ''; 43 | }, 44 | }), 45 | set: new Proxy(cookie.set, { 46 | apply: (target, that, [ val ]) => { 47 | return Reflect.apply(target, that, [ ctx.config.cookie ? ctx.cookies.encode(val, ctx.meta) : '' ]); 48 | }, 49 | }), 50 | }); 51 | }; 52 | if (ctx.window.Document.prototype.writeln) { 53 | ctx.window.Document.prototype.writeln = new Proxy(ctx.window.Document.prototype.writeln, { 54 | apply: (target, that , args) => { 55 | if (args.length) args = [ ctx.html.process(args.join(''), ctx.meta) ]; 56 | return Reflect.apply(target, that, args); 57 | }, 58 | }); 59 | }; 60 | if (ctx.window.Element.prototype.setAttribute) { 61 | ctx.window.Element.prototype.setAttribute = new Proxy(ctx.window.Element.prototype.setAttribute, { 62 | apply: (target, that, args) => { 63 | if (args[0] && args[1]) { 64 | const handler = ctx.html.attributeRoute({ 65 | name: args[0], 66 | value: args[1], 67 | node: that, 68 | }); 69 | switch(handler) { 70 | case 'url': 71 | Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); 72 | //if (that.tagName == 'SCRIPT' && args[0] == 'src') flags.push('js'); 73 | args[1] = ctx.url.wrap(args[1], ctx.meta); 74 | break; 75 | case 'srcset': 76 | Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); 77 | args[1] = ctx.html.srcset(args[1], ctx.meta); 78 | break; 79 | case 'css': 80 | Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); 81 | args[1] = ctx.css.process(args[1], { ...ctx.meta, context: 'declarationList' }); 82 | break; 83 | case 'html': 84 | Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); 85 | args[1] = ctx.html.process(args[1], ctx.meta); 86 | break; 87 | case 'delete': 88 | return Reflect.apply(target, that, [`corrosion-${args[0]}`, args[1]]); 89 | }; 90 | }; 91 | return Reflect.apply(target, that, args); 92 | }, 93 | }); 94 | }; 95 | if (ctx.window.Element.prototype.getAttribute) { 96 | ctx.window.Element.prototype.getAttribute = new Proxy(ctx.window.Element.prototype.getAttribute, { 97 | apply: (target, that, args) => { 98 | if (args[0] && that.hasAttribute(`corrosion-${args[0]}`)) args[0] = `corrosion-${args[0]}`; 99 | return Reflect.apply(target, that, args); 100 | }, 101 | }); 102 | }; 103 | ctx.window.CSSStyleDeclaration.prototype.setProperty = new Proxy(ctx.window.CSSStyleDeclaration.prototype.setProperty, { 104 | apply: (target, that, args) => { 105 | if (args[1]) args[1] = ctx.css.process(args[1], { context: 'value', ...ctx.meta, }); 106 | return Reflect.apply(target, that, args); 107 | }, 108 | }); 109 | if (ctx.window.Audio) { 110 | ctx.window.Audio = new Proxy(ctx.window.Audio, { 111 | construct: (target, args) => { 112 | if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); 113 | return Reflect.construct(target, args); 114 | }, 115 | }); 116 | }; 117 | [ 118 | 'innerHTML', 119 | 'outerHTML', 120 | ].forEach(html => { 121 | const descriptor = Object.getOwnPropertyDescriptor(ctx.window.Element.prototype, html); 122 | Object.defineProperty(ctx.window.Element.prototype, html, { 123 | get: new Proxy(descriptor.get, { 124 | apply: (target, that, args) => { 125 | const body = Reflect.apply(target, that, args); 126 | if (!body || html == 'innerHTML' && that.tagName == 'SCRIPT') return body; 127 | return ctx.html.source(body, ctx.meta); 128 | }, 129 | }), 130 | set: new Proxy(descriptor.set, { 131 | apply(target, that, [ val ]) { 132 | return Reflect.apply(target, that, [ val ? ctx.html.process(val.toString(), ctx.meta) : val, ]); 133 | }, 134 | }), 135 | }); 136 | }); 137 | [ 138 | ['background', 'background'], 139 | ['backgroundImage', 'background-image'], 140 | ['listStyleImage', 'list-style-image'], 141 | ].forEach(([key, cssProperty]) => { 142 | Object.defineProperty(ctx.window.CSS2Properties ? ctx.window.CSS2Properties.prototype : ctx.window.CSSStyleDeclaration.prototype, key, { 143 | get() { 144 | return this.getPropertyValue(cssProperty); 145 | }, 146 | set(val) { 147 | return this.setProperty(cssProperty, val); 148 | }, 149 | }); 150 | }); 151 | Object.defineProperty(ctx.window.Document.prototype, 'domain', { 152 | get: new Proxy(domain.get, { 153 | apply: () => spoofDomain, 154 | }), 155 | set: new Proxy(domain.set, { 156 | apply: (target, that, [ val ]) => { 157 | if (!val.toString().endsWith(ctx.location.hostname.split('.').slice(-2).join('.'))) return Reflect.apply(target, that, ['']); 158 | return spoofDomain = val; 159 | }, 160 | }), 161 | }); 162 | if (ctx.config.title) Object.defineProperty(ctx.window.Document.prototype, 'title', { 163 | get: new Proxy(title.get, { 164 | apply: () => spoofTitle, 165 | }), 166 | set: new Proxy(title.set, { 167 | apply: (target, that, [ val ]) => spoofTitle = val, 168 | }), 169 | }); 170 | Object.defineProperty(ctx.window.Navigator.prototype, 'cookieEnabled', { 171 | get: new Proxy(cookieEnabled.get, { 172 | apply: () => ctx.config.cookie, 173 | }), 174 | }); 175 | Object.defineProperty(ctx.window.Node.prototype, 'baseURI', { 176 | get: new Proxy(baseURI.get, { 177 | apply: (target, that, args) => { 178 | const val = Reflect.apply(target, that, args); 179 | return val.startsWith(ctx.meta.origin) ? ctx.url.unwrap(val, ctx.meta) : val; 180 | }, 181 | }), 182 | }); 183 | [ 184 | { 185 | elements: [ HTMLScriptElement, HTMLMediaElement, HTMLImageElement, HTMLAudioElement, HTMLVideoElement, HTMLInputElement, HTMLEmbedElement, HTMLIFrameElement, HTMLTrackElement, HTMLSourceElement], 186 | properties: ['src'], 187 | handler: 'url', 188 | }, 189 | { 190 | elements: [ HTMLFormElement ], 191 | properties: ['action'], 192 | handler: 'url', 193 | }, 194 | { 195 | elements: [ HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement, HTMLBaseElement ], 196 | properties: ['href'], 197 | handler: 'url', 198 | }, 199 | { 200 | elements: [ HTMLImageElement, HTMLSourceElement ], 201 | properties: ['srcset'], 202 | handler: 'srcset', 203 | }, 204 | { 205 | elements: [ HTMLScriptElement ], 206 | properties: ['integrity'], 207 | handler: 'delete', 208 | }, 209 | { 210 | elements: [ HTMLIFrameElement ], 211 | properties: ['contentWindow'], 212 | handler: 'window', 213 | }, 214 | ].forEach(entry => { 215 | entry.elements.forEach(element => { 216 | if (!element) return; 217 | entry.properties.forEach(property => { 218 | if (!element.prototype.hasOwnProperty(property)) return; 219 | const descriptor = Object.getOwnPropertyDescriptor(element.prototype, property); 220 | Object.defineProperty(element.prototype, property, { 221 | get: descriptor.get ? new Proxy(descriptor.get, { 222 | apply: (target, that, args) => { 223 | let val = Reflect.apply(target, that, args); 224 | let flags = []; 225 | switch(entry.handler) { 226 | case 'url': 227 | //if (that.tagName == 'SCRIPT' && property == 'src') flags.push('js'); 228 | val = ctx.url.unwrap(val, ctx.meta); 229 | break; 230 | case 'srcset': 231 | val = ctx.html.unsrcset(val, ctx.meta); 232 | break; 233 | case 'delete': 234 | val = that.getAttribute(`corrosion-${property}`); 235 | break; 236 | case 'window': 237 | try { 238 | if (!val.$corrosion) { 239 | val.$corrosion = new ctx.constructor({ ...ctx.config, window: val, }); 240 | val.$corrosion.init(); 241 | val.$corrosion.meta = ctx.meta; 242 | }; 243 | } catch(e) {}; 244 | }; 245 | return val; 246 | }, 247 | }) : undefined, 248 | set: descriptor.set ? new Proxy(descriptor.set, { 249 | apply(target, that, [ val ]) { 250 | let newVal = val; 251 | switch(entry.handler) { 252 | case 'url': 253 | newVal = ctx.url.wrap(newVal, ctx.meta); 254 | break; 255 | case 'srcset': 256 | newVal = ctx.html.srcset(newVal, ctx.meta); 257 | break; 258 | case 'delete': 259 | that.setAttribute(property, newVal); 260 | return newVal; 261 | }; 262 | return Reflect.apply(target, that, [ newVal ]); 263 | }, 264 | }) : undefined, 265 | }); 266 | }); 267 | }); 268 | }); 269 | }; 270 | }; 271 | module.exports = createDocumentRewriter; -------------------------------------------------------------------------------- /lib/browser/history.js: -------------------------------------------------------------------------------- 1 | function createHistoryRewriter(ctx) { 2 | return function rewriteHistory() { 3 | if (ctx.serviceWorker) return; 4 | if (ctx.window.History.prototype.pushState) { 5 | ctx.window.History.prototype.pushState = new Proxy(ctx.window.History.prototype.pushState, { 6 | apply: (target, that, args) => { 7 | if (args[2]) args[2] = ctx.url.wrap(args[2], ctx.meta); 8 | const ret = Reflect.apply(target, that, args); 9 | ctx.updateLocation(); 10 | return ret; 11 | }, 12 | }); 13 | }; 14 | if (ctx.window.History.prototype.replaceState) { 15 | ctx.window.History.prototype.replaceState = new Proxy(ctx.window.History.prototype.replaceState, { 16 | apply: (target, that, args) => { 17 | if (args[2]) args[2] = ctx.url.wrap(args[2], ctx.meta); 18 | const ret = Reflect.apply(target, that, args); 19 | ctx.updateLocation(); 20 | return ret; 21 | }, 22 | }); 23 | }; 24 | }; 25 | }; 26 | module.exports = createHistoryRewriter; -------------------------------------------------------------------------------- /lib/browser/http.js: -------------------------------------------------------------------------------- 1 | function createHttpRewriter(ctx = {}) { 2 | return function rewriteHttp() { 3 | if (ctx.window.Request) { 4 | const requestURL = Object.getOwnPropertyDescriptor(ctx.window.Request.prototype, 'url'); 5 | ctx.window.Request = new Proxy(ctx.window.Request, { 6 | construct(target, args) { 7 | if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], }) 8 | return Reflect.construct(target, args); 9 | }, 10 | }); 11 | Object.defineProperty(ctx.window.Request.prototype, 'url', { 12 | get: new Proxy(requestURL.get, { 13 | apply: (target, that, args) => { 14 | var url = Reflect.apply(target, that, args); 15 | return url ? ctx.url.unwrap(url, ctx.meta) : url; 16 | }, 17 | }), 18 | }); 19 | }; 20 | if (ctx.window.Response) { 21 | const responseURL = Object.getOwnPropertyDescriptor(ctx.window.Response.prototype, 'url'); 22 | Object.defineProperty(ctx.window.Response.prototype, 'url', { 23 | get: new Proxy(responseURL.get, { 24 | apply: (target, that, args) => { 25 | var url = Reflect.apply(target, that, args); 26 | return url ? ctx.url.unwrap(url, ctx.meta) : url; 27 | }, 28 | }), 29 | }); 30 | }; 31 | if (ctx.window.open) { 32 | ctx.window.open = new Proxy(ctx.window.open, { 33 | apply: (target, that, args) => { 34 | if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); 35 | return Reflect.apply(target, that, args) 36 | }, 37 | }); 38 | }; 39 | if (ctx.window.fetch) { 40 | ctx.window.fetch = new Proxy(ctx.window.fetch, { 41 | apply: (target, that, args) => { 42 | if (args[0] instanceof ctx.window.Request) return Reflect.apply(target, that, args); 43 | if (args[0]) args[0] = ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], }); 44 | return Reflect.apply(target, that, args); 45 | }, 46 | }); 47 | }; 48 | if (ctx.window.Navigator && ctx.window.Navigator.prototype.sendBeacon) { 49 | ctx.window.Navigator.prototype.sendBeacon = new Proxy(ctx.window.Navigator.prototype.sendBeacon, { 50 | apply: (target, that, args) => { 51 | if (args[0]) ctx.url.wrap(args[0], { ...ctx.meta, flags: ['xhr'], }); 52 | return Reflect.apply(target, that, args); 53 | }, 54 | }); 55 | }; 56 | if (ctx.window.XMLHttpRequest) { 57 | const responseURL = Object.getOwnPropertyDescriptor(ctx.window.XMLHttpRequest.prototype, 'responseURL'); 58 | ctx.window.XMLHttpRequest.prototype.open = new Proxy(ctx.window.XMLHttpRequest.prototype.open, { 59 | apply: (target, that, args) => { 60 | if (args[1]) args[1] = ctx.url.wrap(args[1], { ...ctx.meta, flags: ['xhr'], }); 61 | return Reflect.apply(target, that, args); 62 | }, 63 | }); 64 | Object.defineProperty(ctx.window.XMLHttpRequest.prototype, 'responseURL', { 65 | get: new Proxy(responseURL.get, { 66 | apply: (target, that, args) => { 67 | const url = Reflect.apply(target, that, args); 68 | return url ? ctx.url.unwrap(url, ctx.meta) : url; 69 | }, 70 | }), 71 | }); 72 | }; 73 | if (ctx.window.postMessage) { 74 | ctx.window.postMessage = new Proxy(ctx.window.postMessage, { 75 | apply: (target, that, args) => { 76 | if (!ctx.serviceWorker && args[1]) args[1] = ctx.meta.origin; 77 | return Reflect.apply(target, that, args); 78 | }, 79 | }); 80 | }; 81 | if (ctx.window.WebSocket && ctx.config.ws) { 82 | ctx.window.WebSocket = new Proxy(ctx.window.WebSocket, { 83 | construct: (target, args) => { 84 | if (args[0]) args[0] = ctx.url.wrap(args[0].toString().replace('ws', 'http'), ctx.meta).replace('http', 'ws') + '?origin=' + ctx.location.origin; 85 | return Reflect.construct(target, args); 86 | }, 87 | }); 88 | }; 89 | }; 90 | }; 91 | 92 | module.exports = createHttpRewriter; -------------------------------------------------------------------------------- /lib/browser/index.js: -------------------------------------------------------------------------------- 1 | const createDocumentRewriter = require('./document'); 2 | const createHistoryRewriter = require('./history'); 3 | const createHttpRewriter = require('./http'); 4 | const createLocation = require('./location'); 5 | const createWorkerRewriter = require('./worker'); 6 | const defaultConfig = { 7 | prefix: '/service/', 8 | codec: 'plain', 9 | ws: true, 10 | cookie: true, 11 | title: 'Service', 12 | serviceWorker: false, 13 | url: null, 14 | window: false, 15 | }; 16 | 17 | class Corrosion extends require('../rewrite') { 18 | constructor(config = defaultConfig) { 19 | super(Object.assign(defaultConfig, config)); 20 | const corrosion = this; 21 | if (!this.config.window) throw 'Corrosion Error: No window was given.'; 22 | this.serviceWorker = this.config.serviceWorker || false; 23 | this.window = this.config.window; 24 | this.document = this.serviceWorker ? this.window.document : {}; 25 | this._url = new URL((this.config.url || this.url.unwrap(this.window.location.href, { origin: this.window.location.origin, }))); 26 | this.originalXhr = this.window.XMLHttpRequest; 27 | this.meta = { 28 | origin: this.window.location.origin, 29 | get base() { 30 | if (corrosion.serviceWorker) return corrosion._url; 31 | return corrosion.window.document.baseURI != corrosion.location.href ? new URL(corrosion.window.document.baseURI) : corrosion._url; 32 | }, 33 | url: this._url, 34 | }; 35 | this.location = createLocation(this, this._url); 36 | this.rewriteHttp = createHttpRewriter(this); 37 | this.rewriteDocument = createDocumentRewriter(this); 38 | this.rewriteHistory = createHistoryRewriter(this); 39 | this.rewriteWorker = createWorkerRewriter(this); 40 | if (!this.serviceWorker && this.window.document.currentScript) this.window.document.currentScript.remove(); 41 | }; 42 | get parent() { 43 | if (this.serviceWorker) return false; 44 | try { 45 | return this.window.parent.$corrosion ? this.window.parent : this.window; 46 | } catch(e) { 47 | return this.window; 48 | }; 49 | }; 50 | get top() { 51 | if (this.serviceWorker) return false; 52 | try { 53 | return this.window.top.$corrosion ? this.window.top : this.parent; 54 | } catch(e) { 55 | return this.parent; 56 | }; 57 | }; 58 | init() { 59 | this.rewriteHttp(); 60 | this.rewriteDocument(); 61 | this.rewriteHistory(); 62 | this.rewriteWorker(); 63 | this.window.Location = createLocation.Location; 64 | this.window.$corrosionGet$ = this.get$.bind(this); 65 | this.window.$corrosionSet$ = this.set$.bind(this); 66 | this.window.$corrosionGet$m = this.get$m.bind(this); 67 | this.window.$corrosionSet$m = this.set$m.bind(this); 68 | this.window.$corrosionCall$m = this.call$m.bind(this); 69 | }; 70 | get$m(obj, key) { 71 | if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) { 72 | return this.parent.$corrosion.get$m(this.parent, key); 73 | }; 74 | if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) { 75 | return this.top.$corrosion.get$m(this.top, key); 76 | }; 77 | if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') return this.location; 78 | if (!this.serviceWorker && obj == this.window && key == 'parent' && this.window != this.window.parent) return this.parent; 79 | if (!this.serviceWorker && obj == this.window && key == 'top' && this.window != this.window.top) return this.top; 80 | return obj[key]; 81 | }; 82 | set$m(obj, key, val, operator) { 83 | if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) { 84 | return this.parent.$corrosion.set$m(this.parent, key, val, operator); 85 | }; 86 | if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) { 87 | return this.top.$corrosion.set$m(this.top, key, val, operator); 88 | }; 89 | if (obj == this.window && key == 'location' || !this.serviceWorker && obj == this.window.document && key == 'location') obj = this; 90 | switch(operator) { 91 | case '+=': 92 | return obj[key] += val; 93 | case '-=': 94 | return obj[key] -= val; 95 | case '*=': 96 | return obj[key] *= val; 97 | case '/=': 98 | return obj[key] /= val; 99 | case '%=': 100 | return obj[key] %= val; 101 | case '**=': 102 | return obj[key] **= val; 103 | case '<<=': 104 | return obj[key] <<= val; 105 | case '>>=': 106 | return obj[key] >>= val; 107 | case '>>>=': 108 | return obj[key] >>>= val; 109 | case '&=': 110 | return obj[key] &= val; 111 | case '^=': 112 | return obj[key] ^= val; 113 | case '|=': 114 | return obj[key] |= val; 115 | case '&&=': 116 | return obj[key] &&= val; 117 | case '||=': 118 | return obj[key] ||= val; 119 | case '??=': 120 | return obj[key] ??= val; 121 | case '++': 122 | return obj[key]++; 123 | case '--': 124 | return obj[key]--; 125 | case '=': 126 | default: 127 | return obj[key] = val; 128 | }; 129 | }; 130 | call$m(obj, key, args) { 131 | if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) { 132 | return this.parent.$corrosion.call$m(this.parent, key, args); 133 | }; 134 | if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) { 135 | return this.top.$corrosion.call$m(this.top, key, args); 136 | }; 137 | return obj[key](...args); 138 | }; 139 | get$(obj) { 140 | if (obj == this.window.location) return this.location; 141 | if (!this.serviceWorker && obj == this.window.parent) return this.parent; 142 | if (!this.serviceWorker && obj == this.window.top) return this.top; 143 | return obj; 144 | }; 145 | set$(obj, val, operator) { 146 | if (obj == this.window.location) return this.set$(this.location, val, operator); 147 | if (!this.serviceWorker && this.window != this.window.parent && obj == this.window.parent) return this.parent.set$(this.parent, val, operator); 148 | if (!this.serviceWorker && this.window != this.window.top && obj == this.window.top) return this.top.set$(this.top, val, operator); 149 | switch(operator) { 150 | case '+=': 151 | return obj += val; 152 | case '-=': 153 | return obj -= val; 154 | case '*=': 155 | return obj *= val; 156 | case '/=': 157 | return obj /= val; 158 | case '%=': 159 | return obj %= val; 160 | case '**=': 161 | return obj **= val; 162 | case '<<=': 163 | return obj <<= val; 164 | case '>>=': 165 | return obj >>= val; 166 | case '>>>=': 167 | return obj >>>= val; 168 | case '&=': 169 | return obj &= val; 170 | case '^=': 171 | return obj ^= val; 172 | case '|=': 173 | return obj |= val; 174 | case '&&=': 175 | return obj &&= val; 176 | case '||=': 177 | return obj ||= val; 178 | case '??=': 179 | return obj ??= val; 180 | case '++': 181 | return obj++; 182 | case '--': 183 | return obj--; 184 | case '=': 185 | default: 186 | return obj = val; 187 | }; 188 | }; 189 | updateLocation() { 190 | this._url = new URL(this.url.unwrap(this.window.location.href, this.meta)); 191 | this.location = createLocation(this, this._url); 192 | }; 193 | }; 194 | 195 | globalThis.Corrosion = Corrosion; -------------------------------------------------------------------------------- /lib/browser/location.js: -------------------------------------------------------------------------------- 1 | class Location { 2 | get [Symbol.toPrimitive]() { 3 | return () => this.href; 4 | }; 5 | }; 6 | 7 | function createLocation(ctx, url) { 8 | const _location = new Location(); 9 | const _url = new URL(url); 10 | [ 11 | 'hash', 12 | 'host', 13 | 'hostname', 14 | 'href', 15 | 'pathname', 16 | 'port', 17 | 'protocol', 18 | 'search', 19 | 'origin', 20 | ].forEach(property => { 21 | Object.defineProperty(_location, property, { 22 | get() { 23 | return _url[property]; 24 | }, 25 | set(val) { 26 | if (ctx.serviceWorker || property == 'origin') return; 27 | if (property == 'href') { 28 | return ctx.window.location.href = ctx.url.wrap(new URL(val, _url).href); 29 | }; 30 | _url[property] = val; 31 | return ctx.window.location.href = ctx.url.wrap(_url); 32 | }, 33 | }); 34 | }); 35 | if (!ctx.serviceWorker) [ 36 | 'assign', 37 | 'replace', 38 | 'reload', 39 | ].forEach(method => { 40 | _location[method] = new Proxy(ctx.window.location[method], { 41 | apply(target, that, args) { 42 | if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); 43 | return Reflect.apply(target.bind(ctx.window.location), that, args); 44 | }, 45 | }); 46 | }); 47 | _location.toString = new Proxy(_url.toString, { 48 | apply(target, that, args) { 49 | return Reflect.apply(target.bind(_url), that, args); 50 | }, 51 | }); 52 | return _location; 53 | }; 54 | 55 | createLocation.Location = Location; 56 | module.exports = createLocation; -------------------------------------------------------------------------------- /lib/browser/worker.js: -------------------------------------------------------------------------------- 1 | function createWorkerRewriter(ctx = {}) { 2 | return function rewriteWorker() { 3 | if (ctx.window.Worker) { 4 | ctx.window.Worker = new Proxy(ctx.window.Worker, { 5 | construct: (target, args) => { 6 | if (args[0]) { 7 | if (args[0].trim().startsWith(`blob:${ctx.window.location.origin}`)) { 8 | const xhr = new ctx.originalXhr(); 9 | xhr.open('GET', args[0], false); 10 | xhr.send(); 11 | const script = ctx.js.process(xhr.responseText, ctx.location.origin + args[0].trim().slice(`blob:${ctx.window.location.origin}`.length)); 12 | const blob = new Blob([ script ], { type: 'application/javascript' }); 13 | args[0] = URL.createObjectURL(blob); 14 | } else { 15 | args[0] = ctx.url.wrap(args[0], ctx.meta); 16 | }; 17 | }; 18 | return Reflect.construct(target, args); 19 | }, 20 | }); 21 | }; 22 | if (ctx.serviceWorker && ctx.window.importScripts) { 23 | ctx.window.importScripts = new Proxy(ctx.window.importScripts, { 24 | apply: (target, that, args) => { 25 | if (args[0]) args[0] = ctx.url.wrap(args[0], ctx.meta); 26 | return Reflect.apply(target, that, args); 27 | }, 28 | }); 29 | }; 30 | }; 31 | }; 32 | 33 | module.exports = createWorkerRewriter; -------------------------------------------------------------------------------- /lib/codec.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | exports.xor = { 6 | encode(str){ 7 | if (!str) return str; 8 | return encodeURIComponent(str.toString().split('').map((char, ind) => ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char).join('')); 9 | }, 10 | decode(str){ 11 | if (!str) return str; 12 | return decodeURIComponent(str).split('').map((char, ind) => ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 2) : char).join(''); 13 | }, 14 | }; 15 | exports.plain = { 16 | encode(str) { 17 | if (!str) return str; 18 | return encodeURIComponent(str); 19 | }, 20 | decode(str) { 21 | if (!str) return str; 22 | return decodeURIComponent(str); 23 | }, 24 | }; 25 | exports.base64 = { 26 | encode(str){ 27 | if (!str) return str; 28 | str = str.toString(); 29 | const b64chs = Array.from('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='); 30 | let u32; 31 | let c0; 32 | let c1; 33 | let c2; 34 | let asc = ''; 35 | let pad = str.length % 3; 36 | 37 | for (let i = 0; i < str.length;) { 38 | if((c0 = str.charCodeAt(i++)) > 255 || (c1 = str.charCodeAt(i++)) > 255 || (c2 = str.charCodeAt(i++)) > 255)throw new TypeError('invalid character found'); 39 | u32 = (c0 << 16) | (c1 << 8) | c2; 40 | asc += b64chs[u32 >> 18 & 63] 41 | + b64chs[u32 >> 12 & 63] 42 | + b64chs[u32 >> 6 & 63] 43 | + b64chs[u32 & 63]; 44 | } 45 | 46 | return encodeURIComponent(pad ? asc.slice(0, pad - 3) + '==='.substr(pad) : asc); 47 | }, 48 | decode(str){ 49 | if (!str) return str; 50 | str = decodeURIComponent(str.toString()); 51 | 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}; 52 | str = str.replace(/\s+/g, ''); 53 | str += '=='.slice(2 - (str.length & 3)); 54 | let u24; 55 | let bin = ''; 56 | let r1; 57 | let r2; 58 | 59 | for (let i = 0; i < str.length;) { 60 | u24 = b64tab[str.charAt(i++)] << 18 61 | | b64tab[str.charAt(i++)] << 12 62 | | (r1 = b64tab[str.charAt(i++)]) << 6 63 | | (r2 = b64tab[str.charAt(i++)]); 64 | bin += r1 === 64 ? String.fromCharCode(u24 >> 16 & 255) 65 | : r2 === 64 ? String.fromCharCode(u24 >> 16 & 255, u24 >> 8 & 255) 66 | : String.fromCharCode(u24 >> 16 & 255, u24 >> 8 & 255, u24 & 255); 67 | }; 68 | return bin; 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /lib/cookie-parser.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | class CookieStore { 6 | constructor(val = ''){ 7 | this.data = {}; 8 | val.split(';').map(cookie => { 9 | var [ name, val = ''] = cookie.trimStart().split('='); 10 | if (name) this.data[name] = val; 11 | }); 12 | }; 13 | has(name){ 14 | if (!name || !this.data[name]) return false; 15 | return true; 16 | }; 17 | get(name){ 18 | return this.has(name) ? this.data[name] : null; 19 | }; 20 | set(name, val){ 21 | if (!name || !val) return; 22 | return this.data[name] = val; 23 | }; 24 | delete(name){ 25 | if (!name) return; 26 | return delete this.data[name]; 27 | }; 28 | forEach(action = (node, key) => null){ 29 | for (let prop in this.data) action(this.data[prop], prop); 30 | }; 31 | serialize(){ 32 | var str = ''; 33 | for (let i in this.data) str += ` ${i}=${this.data[i]};`; 34 | return str.substr(1); 35 | }; 36 | }; 37 | 38 | class SetCookie { 39 | constructor(val = ''){ 40 | 41 | var [ [ name, value = '' ], ...data ] = val.split(';').map(str => str.trimStart().split('=')); 42 | 43 | this.name = name; 44 | this.value = value; 45 | this.expires = null; 46 | this.maxAge = null; 47 | this.domain = null; 48 | this.secure = false; 49 | this.httpOnly = false; 50 | this.path = null; 51 | this.sameSite = null; 52 | 53 | data.forEach(([name = null, value = null]) => { 54 | if (typeof name == 'string') switch(name.toLowerCase()){ 55 | case 'domain': 56 | this.domain = value; 57 | break; 58 | case 'secure': 59 | this.secure = true; 60 | break; 61 | case 'httponly': 62 | this.httpOnly = true; 63 | break; 64 | case 'samesite': 65 | this.sameSite = value; 66 | break; 67 | case 'path': 68 | this.path = value; 69 | break; 70 | case 'expires': 71 | this.expires = value; 72 | break; 73 | case 'maxage': 74 | this.maxAge = value; 75 | break; 76 | }; 77 | }); 78 | }; 79 | serialize(){ 80 | if (!this.name) return; 81 | var str = `${this.name}=${this.value};`; 82 | if (this.expires) str += ` Expires=${this.expires};`; 83 | if (this.maxAge) str += ` Max-Age=${this.max_age};`; 84 | if (this.domain) str += ` Domain=${this.domain};`; 85 | if (this.secure) str += ` Secure;`; 86 | if (this.httpOnly) str += ` HttpOnly;`; 87 | if (this.path) str += ` Path=${this.path};`; 88 | if (this.sameSite) str += ` SameSite=${this.sameSite};`; 89 | return str; 90 | }; 91 | }; 92 | 93 | exports.CookieStore = CookieStore; 94 | exports.SetCookie = SetCookie; 95 | -------------------------------------------------------------------------------- /lib/cookie.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | const { SetCookie, CookieStore } = require('./cookie-parser'); 6 | 7 | class CookieRewriter { 8 | constructor(ctx) { 9 | this.ctx = ctx; 10 | }; 11 | decode(store, config = {}) { 12 | const url = new URL(config.url); 13 | const cookies = new CookieStore(store); 14 | cookies.forEach((val, key) => { 15 | if (!key.includes('@') || key.slice(key.length - url.hostname.length) != url.hostname) return cookies.delete(key); 16 | cookies.delete(key); 17 | cookies.set(key.substr(0, key.length - url.hostname.length - 1), val); 18 | }); 19 | return cookies.serialize(); 20 | }; 21 | encode(input, config = {}) { 22 | if (Array.isArray(input)) { 23 | const rw = [ ...input ]; 24 | for (let i in rw) rw[i] = this.encode(rw[i], config); 25 | return rw; 26 | }; 27 | const url = new URL(config.url); 28 | const cookie = new SetCookie(input); 29 | if (!cookie.name) return null; 30 | cookie.domain = config.domain; 31 | cookie.secure = config.secure; 32 | cookie.name += `@${url.hostname}`; 33 | cookie.path = this.ctx.prefix; 34 | return cookie.serialize() || ''; 35 | }; 36 | }; 37 | 38 | module.exports = CookieRewriter; 39 | -------------------------------------------------------------------------------- /lib/css.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | const csstree = require('css-tree'); 6 | 7 | class CSSRewriter { 8 | constructor(ctx) { 9 | this.ctx = ctx; 10 | }; 11 | process(source, config = {}) { 12 | const ast = csstree.parse(source, { 13 | context: config.context || 'stylesheet', 14 | parseCustomProperty: true, 15 | }); 16 | const urls = csstree.findAll(ast, node => 17 | node.type == 'Url' 18 | ); 19 | const imports = csstree.findAll(ast, node => 20 | node.type == 'Atrule' && node.name == 'import' && node.prelude && node.prelude.type == 'AtrulePrelude' && node.prelude.children.head.data.type == 'String' 21 | ); 22 | urls.forEach(({ value }) => { 23 | switch(value.type) { 24 | case 'String': 25 | const quote = value.value.substring(0, 1); 26 | value.value = quote + this.ctx.url.wrap(value.value.slice(1).slice(0, -1), config) + quote; 27 | break; 28 | case 'Raw': 29 | value.value = this.ctx.url.wrap(value.value, config); 30 | break; 31 | }; 32 | }); 33 | imports.forEach(({ prelude }) => { 34 | const { data } = prelude.children.head; 35 | const quote = data.value.substring(0, 1); 36 | data.value = quote + this.ctx.url.wrap(data.value.slice(1).slice(0, -1), config) + quote; 37 | }); 38 | return csstree.generate(ast); 39 | }; 40 | source(processed, config = {}) { 41 | const ast = csstree.parse(processed, { 42 | context: config.context || 'stylesheet', 43 | parseCustomProperty: true, 44 | }); 45 | const urls = csstree.findAll(ast, node => 46 | node.type == 'Url' 47 | ); 48 | const imports = csstree.findAll(ast, node => 49 | node.type == 'Atrule' && node.name == 'import' && node.prelude && node.prelude.type == 'AtrulePrelude' && node.prelude.children.head.data.type == 'String' 50 | ); 51 | urls.forEach(({ value }) => { 52 | switch(value.type) { 53 | case 'String': 54 | const quote = value.value.substring(0, 1); 55 | value.value = quote + this.ctx.url.unwrap(value.value.slice(1).slice(0, -1), config) + quote; 56 | break; 57 | case 'Raw': 58 | value.value = this.ctx.url.unwrap(value.value, config); 59 | break; 60 | }; 61 | }); 62 | imports.forEach(({ prelude }) => { 63 | const { data } = prelude.children.head; 64 | const quote = data.value.substring(0, 1); 65 | data.value = quote + this.ctx.url.unwrap(data.value.slice(1).slice(0, -1), config) + quote; 66 | }); 67 | return csstree.generate(ast); 68 | }; 69 | }; 70 | 71 | module.exports = CSSRewriter; 72 | -------------------------------------------------------------------------------- /lib/html.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | const parse5 = require('parse5'); 6 | 7 | class HTMLRewriter { 8 | constructor(ctx) { 9 | this.ctx = ctx; 10 | this.attrs = [ 11 | { 12 | tags: ['form', 'object', 'a', 'link', 'area', 'base', 'script', 'img', 'audio', 'video', 'input', 'embed', 'iframe', 'track', 'source', 'html', 'table', 'head'], 13 | attrs: ['src', 'href', 'ping', 'data', 'movie', 'action', 'poster', 'profile', 'background', 'target'], 14 | handler: 'url', 15 | }, 16 | { 17 | tags: ['iframe'], 18 | attrs: ['srcdoc'], 19 | handler: 'html', 20 | }, 21 | { 22 | tags: ['img', 'link', 'source'], 23 | attrs: ['srcset', 'imagesrcset'], 24 | handler: 'srcset', 25 | }, 26 | { 27 | tags: '*', 28 | attrs: ['style'], 29 | handler: 'css', 30 | }, 31 | { 32 | tags: '*', 33 | attrs: ['http-equiv', 'integrity', 'nonce', 'crossorigin'], 34 | handler: 'delete', 35 | }, 36 | ]; 37 | }; 38 | process(source, config = {}) { 39 | const ast = parse5[config.document ? 'parse' : 'parseFragment'](source); 40 | const meta = { 41 | origin: config.origin, 42 | base: new URL(config.base), 43 | }; 44 | iterate(ast, node => { 45 | if (!node.tagName) return; 46 | switch(node.tagName) { 47 | case 'STYLE': 48 | if (node.textContent) node.textContent = this.ctx.css.process(node.textContent, meta); 49 | break; 50 | case 'TITLE': 51 | if (node.textContent && this.ctx.config.title) node.textContent = this.ctx.config.title; 52 | break; 53 | case 'SCRIPT': 54 | if (node.getAttribute('type') != 'application/json' && node.textContent) node.textContent = this.ctx.js.process(node.textContent); 55 | break; 56 | case 'BASE': 57 | if (node.hasAttribute('href')) meta.base = new URL(node.getAttribute('href'), config.base); 58 | break; 59 | }; 60 | node.attrs.forEach(attr => { 61 | const handler = this.attributeRoute({ 62 | ...attr, 63 | node, 64 | }); 65 | let flags = []; 66 | if (node.tagName == 'SCRIPT' && attr.name == 'src') flags.push('js'); 67 | if (node.tagName == 'LINK' && node.getAttribute('rel') == 'stylesheet') flags.push('css'); 68 | switch(handler) { 69 | case 'url': 70 | node.setAttribute(`corrosion-${attr.name}`, attr.value); 71 | attr.value = this.ctx.url.wrap(attr.value, { ...meta, flags }); 72 | break; 73 | case 'srcset': 74 | node.setAttribute(`corrosion-${attr.name}`, attr.value); 75 | attr.value = this.srcset(attr.value, meta); 76 | break; 77 | case 'css': 78 | attr.value = this.ctx.css.process(attr.value, { ...meta, context: 'declarationList' }); 79 | break; 80 | case 'html': 81 | node.setAttribute(`corrosion-${attr.name}`, attr.value); 82 | attr.value = this.process(attr.value, { ...config, ...meta }); 83 | break; 84 | case 'delete': 85 | node.removeAttribute(attr.name); 86 | break; 87 | }; 88 | }); 89 | }); 90 | if (config.document) { 91 | for (let i in ast.childNodes) if (ast.childNodes[i].tagName == 'html') ast.childNodes[i].childNodes.forEach(node => { 92 | if (node.tagName == 'head') { 93 | node.childNodes.unshift(...this.injectHead(config.base)); 94 | }; 95 | }); 96 | }; 97 | return parse5.serialize(ast); 98 | }; 99 | source(processed, config = {}) { 100 | const ast = parse5[config.document ? 'parse' : 'parseFragment'](processed); 101 | iterate(ast, node => { 102 | if (!node.tagName) return; 103 | node.attrs.forEach(attr => { 104 | if (node.hasAttribute(`corrosion-${attr.name}`)) { 105 | attr.value = node.getAttribute(`corrosion-${attr.name}`); 106 | node.removeAttribute(`corrosion-${attr.name}`) 107 | }; 108 | }); 109 | }); 110 | return parse5.serialize(ast); 111 | }; 112 | injectHead() { 113 | return [ 114 | { 115 | nodeName: 'title', 116 | tagName: 'title', 117 | childNodes: [ 118 | { 119 | nodeName: '#text', 120 | value: this.ctx.config.title || '', 121 | } 122 | ], 123 | attrs: [], 124 | }, 125 | { 126 | nodeName: 'script', 127 | tagName: 'script', 128 | childNodes: [], 129 | attrs: [ 130 | { 131 | name: 'src', 132 | value: this.ctx.prefix + 'index.js', 133 | }, 134 | ], 135 | }, 136 | { 137 | nodeName: 'script', 138 | tagName: 'script', 139 | childNodes: [ 140 | { 141 | nodeName: '#text', 142 | 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();` 143 | }, 144 | ], 145 | attrs: [], 146 | } 147 | ]; 148 | } 149 | attributeRoute(data) { 150 | 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)); 151 | return match ? match.handler : false; 152 | }; 153 | srcset(val, config = {}) { 154 | return val.split(',').map(src => { 155 | const parts = src.trimStart().split(' '); 156 | if (parts[0]) parts[0] = this.ctx.url.wrap(parts[0], config); 157 | return parts.join(' '); 158 | }).join(', '); 159 | }; 160 | unsrcset(val, config = {}) { 161 | return val.split(',').map(src => { 162 | const parts = src.trimStart().split(' '); 163 | if (parts[0]) parts[0] = this.ctx.url.unwrap(parts[0], config); 164 | return parts.join(' '); 165 | }).join(', '); 166 | }; 167 | }; 168 | 169 | class Parse5Wrapper { 170 | constructor(node){ 171 | this.raw = node || { 172 | attrs: [], 173 | childNodes: [], 174 | namespaceURI: '', 175 | nodeName: '', 176 | parentNode: {}, 177 | tagName: '', 178 | }; 179 | }; 180 | hasAttribute(name){ 181 | return this.raw.attrs.some(attr => attr.name == name); 182 | }; 183 | removeAttribute(name){ 184 | if (!this.hasAttribute(name)) return; 185 | this.raw.attrs.splice(this.raw.attrs.findIndex(attr => attr.name == name), 1); 186 | }; 187 | setAttribute(name, val = ''){ 188 | if (!name) return; 189 | this.removeAttribute(name); 190 | this.raw.attrs.push({ 191 | name: name, 192 | value: val, 193 | }); 194 | }; 195 | getAttribute(name){ 196 | return (this.raw.attrs.find(attr => attr.name == name) || { value: null }).value; 197 | }; 198 | get textContent(){ 199 | return (this.raw.childNodes.find(node => node.nodeName == '#text') || { value: '', }).value 200 | }; 201 | set textContent(val){ 202 | if (this.raw.childNodes.some(node => node.nodeName == '#text')) return this.raw.childNodes[this.raw.childNodes.findIndex(node => node.nodeName == '#text')].value = val; 203 | this.raw.childNodes.push({ 204 | nodeName: '#text', 205 | value: val, 206 | }); 207 | }; 208 | get tagName(){ 209 | return (this.raw.tagName || '').toUpperCase(); 210 | }; 211 | get nodeName(){ 212 | return this.raw.nodeName; 213 | }; 214 | get parentNode(){ 215 | return this.raw.parentNode; 216 | }; 217 | get childNodes(){ 218 | return this.raw.childNodes || []; 219 | }; 220 | get attrs() { 221 | return this.raw.attrs || []; 222 | }; 223 | }; 224 | 225 | function iterate(ast, fn = (node = Parse5Wrapper.prototype) => null) { 226 | fn(new Parse5Wrapper(ast)); 227 | if (ast.childNodes) for (let i in ast.childNodes) iterate(ast.childNodes[i], fn); 228 | }; 229 | 230 | module.exports = HTMLRewriter; 231 | -------------------------------------------------------------------------------- /lib/js.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | const { parse } = require('acorn-hammerhead'); 6 | const { generate } = require('./esotope'); 7 | 8 | class JSRewriter { 9 | constructor(ctx) { 10 | this.parseOptions = { 11 | allowReturnOutsideFunction: true, 12 | allowImportExportEverywhere: true, 13 | ecmaVersion: 2021, 14 | }; 15 | this.generationOptions = { 16 | format: { 17 | quotes: 'double', 18 | escapeless: true, 19 | compact: true, 20 | }, 21 | }; 22 | this.rewrite = ['location', 'parent', 'top']; 23 | this.map = [ 24 | { 25 | type: 'MemberExpression', 26 | handler: (node, parent) => { 27 | let rewrite = false; 28 | if (parent.type == 'UnaryExpression' && parent.operator == 'delete') return; 29 | if (parent.type == 'NewExpression' && parent.callee == node) return; 30 | if (parent.type === 'CallExpression' && parent.callee === node) return; 31 | if (node.preventRewrite) return; 32 | switch(node.property.type) { 33 | case 'Identifier': 34 | //if (node.computed) rewrite = true; 35 | if (!node.computed && this.rewrite.includes(node.property.name)) { 36 | node.property = this.createLiteral(node.property.name); 37 | rewrite = true; 38 | }; 39 | break; 40 | case 'Literal': 41 | if (this.rewrite.includes(node.property.name)) rewrite = true; 42 | break; 43 | case 'TemplateLiteral': 44 | rewrite = true; 45 | break; 46 | default: 47 | if (node.computed) rewrite = true; 48 | }; 49 | if (rewrite) { 50 | let identifier = '$corrosionGet$m'; 51 | let nodeToRewrite = node; 52 | const args = [ 53 | node.object, 54 | node.property, 55 | ]; 56 | if (node.computed) args[1].preventRewrite = true; 57 | if (parent.type == 'AssignmentExpression' && parent.left == node) { 58 | identifier = '$corrosionSet$m'; 59 | nodeToRewrite = parent; 60 | args.push(parent.right, this.createLiteral(parent.operator)); 61 | }; 62 | if (parent.type == 'CallExpression' && parent.callee == node) { 63 | identifier = '$corrosionCall$m'; 64 | nodeToRewrite = parent; 65 | args.push(this.createArrayExpression(...parent.arguments)) 66 | }; 67 | if (parent.type == 'UpdateExpression') { 68 | identifier = '$corrosionSet$m'; 69 | nodeToRewrite = parent; 70 | args.push(this.createLiteral(null), this.createLiteral(parent.operator)); 71 | }; 72 | Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier, }, args)); 73 | }; 74 | }, 75 | }, 76 | { 77 | type: 'Identifier', 78 | handler: (node, parent) => { 79 | if (parent.type == 'MemberExpression' && parent.property == node) return; // window.location; 80 | if (parent.type == 'LabeledStatement') return; // { location: null, }; 81 | if (parent.type == 'VariableDeclarator' && parent.id == node) return; 82 | if (parent.type == 'Property' && parent.key == node) return; 83 | if (parent.type == 'MethodDefinition') return; 84 | if (parent.type == 'ClassDeclaration') return; 85 | if (parent.type == 'RestElement') return; 86 | if (parent.type == 'ExportSpecifier') return; 87 | if (parent.type == 'ImportSpecifier') return; 88 | if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression' || parent.type == 'ArrowFunctionExpression') && parent.params.includes(node)) return; 89 | if ((parent.type == 'FunctionDeclaration' || parent.type == 'FunctionExpression') && parent.id == node) return; 90 | if (parent.type == 'AssignmentPattern' && parent.left == node) return; 91 | if (!this.rewrite.includes(node.name)) return; 92 | if (node.preventRewrite) return; 93 | let identifier = '$corrosionGet$'; 94 | let nodeToRewrite = node; 95 | const args = [ 96 | this.createIdentifier(node.name, true), 97 | ]; 98 | 99 | if (parent.type == 'AssignmentExpression' && parent.left == node) { 100 | identifier = '$corrosionSet$'; 101 | nodeToRewrite = parent; 102 | args.push(parent.right); 103 | args.push(this.createLiteral(parent.operator)); 104 | }; 105 | 106 | Object.assign(nodeToRewrite, this.createCallExpression({ type: 'Identifier', name: identifier }, args)); 107 | }, 108 | }, 109 | { 110 | type: 'ImportDeclaration', 111 | handler: (node, parent, url) => { 112 | if (node.source.type != 'Literal' || !url) return; 113 | node.source = this.createLiteral(ctx.url.wrap(node.source.value, { base: url, })); 114 | }, 115 | }, 116 | { 117 | type: 'ImportExpression', 118 | handler: (node, parent) => { 119 | node.source = this.createCallExpression(this.createMemberExpression(this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('url')), this.createIdentifier('wrap')), [ 120 | node.source, 121 | this.createMemberExpression(this.createIdentifier('$corrosion'), this.createIdentifier('meta')), 122 | ]); 123 | }, 124 | }, 125 | ]; 126 | this.ctx = ctx; 127 | }; 128 | process(source, url) { 129 | try { 130 | const ast = parse(source, this.parseOptions); 131 | this.iterate(ast, (node, parent) => { 132 | const fn = this.map.find(entry => entry.type == (node || {}).type); 133 | if (fn) fn.handler(node, parent, url); 134 | }); 135 | return (url ? this.createHead(url) : '') + generate(ast, this.generationOptions); 136 | } catch(e) { 137 | return source; 138 | }; 139 | }; 140 | createHead(url) { 141 | return ` 142 | if (!self.$corrosion && self.importScripts) { 143 | importScripts(location.origin + '${this.ctx.prefix}index.js'); 144 | 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(); 145 | };\n`; 146 | }; 147 | iterate(ast, handler) { 148 | if (typeof ast != 'object' || !handler) return; 149 | walk(ast, null, handler); 150 | function walk(node, parent, handler) { 151 | if (typeof node != 'object' || !handler) return; 152 | handler(node, parent, handler); 153 | for (const child in node) { 154 | if (Array.isArray(node[child])) { 155 | node[child].forEach(entry => walk(entry, node, handler)); 156 | } else { 157 | walk(node[child], node, handler); 158 | }; 159 | }; 160 | }; 161 | }; 162 | createCallExpression(callee, args) { 163 | return { type: 'CallExpression', callee, arguments: args, optional: false, }; 164 | }; 165 | createArrayExpression(...elements) { 166 | return { 167 | type: 'ArrayExpression', 168 | elements, 169 | }; 170 | }; 171 | createMemberExpression(object, property) { 172 | return { 173 | type: 'MemberExpression', 174 | object, 175 | property, 176 | }; 177 | }; 178 | createLiteral(value) { 179 | return { 180 | type: 'Literal', 181 | value, 182 | } 183 | }; 184 | createIdentifier(name, preventRewrite) { 185 | return { type: 'Identifier', name, preventRewrite: preventRewrite || false, }; 186 | }; 187 | }; 188 | 189 | module.exports = JSRewriter; 190 | -------------------------------------------------------------------------------- /lib/rewrite.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | const URLWrapper = require('./url'); 6 | const CookieRewriter = require('./cookie'); 7 | const CSSRewriter = require('./css'); 8 | const HTMLRewriter = require('./html'); 9 | const JSRewriter = require('./js'); 10 | const defaultConfig = { 11 | prefix: '/service/', 12 | codec: 'plain', 13 | ws: true, 14 | cookie: true, 15 | title: 'Service', 16 | }; 17 | 18 | class Rewrite { 19 | constructor(config = defaultConfig) { 20 | this.config = Object.assign(defaultConfig, config); 21 | this.prefix = this.config.prefix; 22 | this.forceHttps = this.config.forceHttps; 23 | this.url = new URLWrapper(this.config || {}); 24 | this.codec = this.url.codec; 25 | this.cookies = new CookieRewriter(this); 26 | this.css = new CSSRewriter(this); 27 | this.js = new JSRewriter(this); 28 | this.html = new HTMLRewriter(this); 29 | }; 30 | }; 31 | 32 | module.exports = Rewrite; 33 | -------------------------------------------------------------------------------- /lib/server/decompress.js: -------------------------------------------------------------------------------- 1 | const zlib = require('zlib'); 2 | 3 | function decompress(ctx) { 4 | if (!ctx.body || !ctx.remoteResponse) return; 5 | try { 6 | switch(ctx.headers['content-encoding']) { 7 | case 'br': 8 | ctx.body = zlib.brotliDecompressSync(ctx.body); 9 | break; 10 | case 'gzip': 11 | ctx.body = zlib.gunzipSync(ctx.body); 12 | break; 13 | case 'deflate': 14 | ctx.body = zlib.inflateRawSync(ctx.body); 15 | break; 16 | }; 17 | } catch(err) {}; 18 | delete ctx.headers['content-encoding']; 19 | return true; 20 | }; 21 | 22 | module.exports = decompress; -------------------------------------------------------------------------------- /lib/server/gateway.js: -------------------------------------------------------------------------------- 1 | function createGateway(ctx) { 2 | return function gateway(clientRequest, clientResponse) { 3 | const chunks = []; 4 | clientRequest.on('data', chunk => 5 | chunks.push(chunk) 6 | ).on('end', () => { 7 | const body = chunks.length ? Buffer.concat(chunks) : ''; 8 | const query = clientRequest.method == 'POST' ? new URLSearchParams((body || '').toString()) : new URLSearchParams((clientRequest.url.split('?')[1] || '')); 9 | if (!query.has('url')) return clientResponse.end(); 10 | const url = query.get('url'); 11 | if (/https?:\/\/([a-zA-Z0-9\-\_])|([a-zA-Z0-9\-\_])\.([a-zA-Z])/.test(url)) { 12 | clientResponse.writeHead(301, { Location: ctx.url.wrap(/https?:\/\//.test(url) ? url : 'http://' + url) }); 13 | clientResponse.end(); 14 | } else { 15 | clientResponse.writeHead(301, { Location: ctx.url.wrap('https://www.google.com/search?q=' + url) }); 16 | clientResponse.end(); 17 | }; 18 | }); 19 | }; 20 | }; 21 | module.exports = createGateway; -------------------------------------------------------------------------------- /lib/server/headers.js: -------------------------------------------------------------------------------- 1 | function requestHeaders(ctx) { 2 | if (ctx.headers.cookie && ctx.rewrite.config.cookie) ctx.headers.cookie = ctx.rewrite.cookies.decode(ctx.headers.cookie, { url: ctx.url, }); 3 | else delete ctx.headers.cookie; 4 | if (ctx.headers.origin) { 5 | if (ctx.clientSocket) { 6 | const params = new URLSearchParams((ctx.clientRequest.url.split('?')[1] || '')); 7 | delete ctx.headers.origin; 8 | delete ctx.headers.host; 9 | ctx.headers.Origin = params.get('origin') || ctx.url.origin; 10 | ctx.headers.Host = ctx.url.host; 11 | // Some websocket servers oddly only accept Host and Origin headers if the first character of the header is uppercase. 12 | } else { 13 | ctx.headers.origin = ctx.url.origin; 14 | }; 15 | }; 16 | 17 | if (ctx.headers.referer) { 18 | try { 19 | ctx.headers.referer = new URL(ctx.rewrite.url.unwrap(ctx.headers.referer, { origin: ctx.origin, })).href; 20 | } catch(err) { 21 | ctx.headers.referer = ctx.url.href; 22 | }; 23 | }; 24 | for (let header in ctx.headers) { 25 | if (header.startsWith('cf-') || header.startsWith('x-forwarded') || header == 'cdn-loop') delete ctx.headers[header]; 26 | }; 27 | ctx.headers.host = ctx.url.host; 28 | return true; 29 | }; 30 | 31 | function responseHeaders(ctx) { 32 | if (ctx.headers.location) ctx.headers.location = ctx.rewrite.url.wrap(ctx.headers.location, { base: ctx.url, origin: ctx.origin, }); 33 | 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, }); 34 | [ 35 | 'content-length', 36 | 'content-security-policy', 37 | 'content-security-policy-report-only', 38 | 'strict-transport-security', 39 | 'x-frame-options' 40 | ].forEach(name => delete ctx.headers[name]); 41 | return true; 42 | }; 43 | 44 | exports.requestHeaders = requestHeaders; 45 | exports.responseHeaders = responseHeaders; -------------------------------------------------------------------------------- /lib/server/index.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const createWebSocketProxy = require('./upgrade'); 3 | const createRequestProxy = require('./request'); 4 | const createGateway = require('./gateway'); 5 | const middleware = { 6 | ...require('./headers'), 7 | ...require('./middleware'), 8 | decompress: require('./decompress'), 9 | rewriteBody: require('./rewrite-body'), 10 | }; 11 | const path = require('path'); 12 | const fs = require('fs'); 13 | const defaultConfig = { 14 | prefix: '/service/', 15 | codec: 'plain', 16 | forceHttps: false, 17 | ws: true, 18 | cookie: true, 19 | title: 'Service', 20 | requestMiddleware: [], 21 | responseMiddleware: [], 22 | standardMiddleware: true, 23 | }; 24 | 25 | class Corrosion extends require('../rewrite') { 26 | constructor(config = defaultConfig) { 27 | super(Object.assign(defaultConfig, config)); 28 | if (this.config.standardMiddleware) { 29 | this.config.requestMiddleware.unshift( 30 | middleware.requestHeaders, 31 | ); 32 | this.config.responseMiddleware.unshift( 33 | middleware.responseHeaders, 34 | middleware.decompress, 35 | middleware.rewriteBody, 36 | ); 37 | }; 38 | this.gateway = createGateway(this); 39 | this.upgrade = createWebSocketProxy(this); 40 | this.request = createRequestProxy(this); 41 | if (!fs.existsSync(path.join(__dirname, 'bundle.js'))) this.bundleScripts(); 42 | }; 43 | bundleScripts() { 44 | webpack({ 45 | mode: 'none', 46 | entry: path.join(__dirname, '../../lib/browser/index.js'), 47 | output: { 48 | path: __dirname, 49 | filename: 'bundle.js', 50 | } 51 | }, err => 52 | console.log(err || 'Bundled scripts') 53 | ); 54 | }; 55 | get script() { 56 | return fs.existsSync(path.join(__dirname, 'bundle.js')) ? fs.readFileSync(path.join(__dirname, 'bundle.js')) : 'Client script is still compiling or has crashed.' 57 | }; 58 | }; 59 | 60 | Corrosion.middleware = middleware; 61 | module.exports = Corrosion; -------------------------------------------------------------------------------- /lib/server/middleware.js: -------------------------------------------------------------------------------- 1 | function address(arr = []) { 2 | return function (ctx) { 3 | ctx.address = arr[Math.floor(Math.random() * arr.length)]; 4 | }; 5 | }; 6 | 7 | function blacklist(arr = [], page = '') { 8 | return function (ctx) { 9 | if (arr.includes(ctx.url.hostname)) ctx.clientResponse.end(page); 10 | }; 11 | }; 12 | 13 | exports.address = address; 14 | exports.blacklist = blacklist; -------------------------------------------------------------------------------- /lib/server/request.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const https = require('https'); 3 | function createRequestProxy(ctx) { 4 | return async function onRequest(clientRequest, clientResponse) { 5 | try { 6 | if (new RegExp(`^${ctx.prefix}gateway/?`).test(clientRequest.url)) return ctx.gateway(clientRequest, clientResponse); 7 | if (clientRequest.url.startsWith(`${ctx.prefix}index.js`)) { 8 | clientResponse.setHeader('Content-Type', 'application/javascript'); 9 | return clientResponse.end(ctx.script); 10 | }; 11 | const urlData = ctx.url.unwrap(clientRequest.url, { flags: true, leftovers: true, }); 12 | urlData.value = new URL(urlData.value); 13 | const requestContext = { 14 | url: urlData.value, 15 | flags: urlData.flags, 16 | origin: ((clientRequest.socket.encrypted || ctx.config.forceHttps) ? 'https://' : 'http://') + clientRequest.headers.host, 17 | body: await getChunks(clientRequest), 18 | headers: { ...clientRequest.headers }, 19 | method: clientRequest.method, 20 | rewrite: ctx, 21 | agent: new ((urlData.value.protocol == 'https:' || ctx.config.forceHttps) ? https : http).Agent({ 22 | rejectUnauthorized: false, 23 | }), 24 | address: null, 25 | clientRequest, 26 | clientResponse, 27 | }; 28 | for (let i in ctx.config.requestMiddleware) ctx.config.requestMiddleware[i](requestContext); 29 | if (clientResponse.writableEnded) return; 30 | ((requestContext.url.protocol == 'https:' || ctx.config.forceHttps) ? https : http).request({ 31 | headers: requestContext.headers, 32 | method: requestContext.method, 33 | hostname: requestContext.url.hostname, 34 | port: requestContext.url.port, 35 | path: requestContext.url.pathname + requestContext.url.search, 36 | agent: requestContext.agent, 37 | localAddress: requestContext.address, 38 | rejectUnauthorized: false, 39 | }, async remoteResponse => { 40 | const responseContext = { 41 | url: requestContext.url, 42 | flags: requestContext.flags, 43 | origin: requestContext.origin, 44 | body: await getChunks(remoteResponse), 45 | headers: { ...remoteResponse.headers }, 46 | statusCode: remoteResponse.statusCode, 47 | agent: requestContext.agent, 48 | address: requestContext.address, 49 | method: requestContext.method, 50 | rewrite: ctx, 51 | clientRequest, 52 | clientResponse, 53 | remoteResponse, 54 | }; 55 | for (let i in ctx.config.responseMiddleware) ctx.config.responseMiddleware[i](responseContext); 56 | if (clientResponse.writableEnded) return; 57 | clientResponse.writeHead(responseContext.statusCode, responseContext.headers); 58 | clientResponse.end((responseContext.body || '')); 59 | }).on('error', err => { 60 | if (clientResponse.writableEnded) return; 61 | clientResponse.setHeader('Content-Type', 'text/plain'); 62 | clientResponse.end(err.toString()) 63 | }).end(requestContext.body); 64 | } catch(err) { 65 | if (clientResponse.writableEnded) return; 66 | clientResponse.setHeader('Content-Type', 'text/plain'); 67 | clientResponse.end(err.toString()); 68 | }; 69 | }; 70 | }; 71 | 72 | function getChunks(stream) { 73 | const chunks = []; 74 | return new Promise(resolve => 75 | stream.on('data', chunk => 76 | chunks.push(chunk) 77 | ).on('end', () => 78 | chunks.length ? resolve(Buffer.concat(chunks)) : resolve(null) 79 | ) 80 | ); 81 | }; 82 | 83 | module.exports = createRequestProxy; -------------------------------------------------------------------------------- /lib/server/rewrite-body.js: -------------------------------------------------------------------------------- 1 | const route = [ 2 | { 3 | types: ['text/html'], 4 | handler: 'html', 5 | }, 6 | { 7 | types: ['text/css'], 8 | handler: 'css', 9 | }, 10 | { 11 | types: ['application/javascript', 'application/x-javascript', 'text/javascript', 'text/x-javascript'], 12 | handler: 'js', 13 | }, 14 | ] 15 | function rewriteBody(ctx) { 16 | if (!ctx.body || !ctx.remoteResponse || ctx.flags.includes('xhr')) return; 17 | const meta = { 18 | base: ctx.url, 19 | origin: ctx.origin, 20 | }; 21 | const data = route.find(entry => ctx.flags == entry.handler) || route.find(entry => entry.types.includes((ctx.headers['content-type'] || '').split(';')[0])) || {}; 22 | 23 | switch(data.handler) { 24 | case 'html': 25 | ctx.body = ctx.rewrite.html.process(ctx.body.toString(), { ...meta, document: true }); 26 | break; 27 | case 'css': 28 | ctx.body = ctx.rewrite.css.process(ctx.body.toString(), meta); 29 | break; 30 | case 'js': 31 | ctx.body = ctx.rewrite.js.process(ctx.body.toString(), ctx.url); 32 | break; 33 | }; 34 | }; 35 | 36 | module.exports = rewriteBody; -------------------------------------------------------------------------------- /lib/server/upgrade.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const https = require('https'); 3 | 4 | function createWebSocketProxy(ctx) { 5 | return function onUpgrade(clientRequest, clientSocket, clientHead) { 6 | try { 7 | const urlData = ctx.url.unwrap(clientRequest.url, { flags: true, }); 8 | urlData.value = new URL(urlData.value); 9 | const requestContext = { 10 | url: urlData.value, 11 | flags: urlData.flags, 12 | body: null, 13 | headers: { ...clientRequest.headers }, 14 | method: clientRequest.method, 15 | rewrite: ctx, 16 | agent: new ((urlData.value.protocol == 'https:' || ctx.config.forceHttps) ? https : http).Agent({ 17 | rejectUnauthorized: false, 18 | }), 19 | address: null, 20 | clientRequest, 21 | clientSocket, 22 | clientHead, 23 | }; 24 | ctx.config.requestMiddleware.forEach(fn => fn(requestContext)); 25 | ((requestContext.url.protocol == 'https:' || ctx.config.forceHttps) ? https : http).request({ 26 | headers: requestContext.headers, 27 | method: requestContext.method, 28 | hostname: requestContext.url.hostname, 29 | port: requestContext.url.port, 30 | path: requestContext.url.pathname + requestContext.url.search, 31 | agent: requestContext.agent, 32 | localAddress: requestContext.address, 33 | }).on('upgrade', (remoteResponse, remoteSocket, remoteHead) => { 34 | let handshake = 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'; 35 | for (let key in remoteResponse.headers) { 36 | handshake += `${key}: ${remoteResponse.headers[key]}\r\n`; 37 | }; 38 | handshake += '\r\n'; 39 | clientSocket.write(handshake); 40 | clientSocket.write(remoteHead); 41 | remoteSocket.on('close', () => clientSocket.end()); 42 | clientSocket.on('close', () => remoteSocket.end()); 43 | remoteSocket.on('error', () => clientSocket.end()); 44 | clientSocket.on('error', () => remoteSocket.end()); 45 | remoteSocket.pipe(clientSocket); 46 | clientSocket.pipe(remoteSocket); 47 | }).on('error', () => { 48 | clientSocket.end() 49 | }).end(); 50 | } catch(err) { 51 | clientSocket.end(); 52 | }; 53 | }; 54 | }; 55 | 56 | module.exports = createWebSocketProxy; -------------------------------------------------------------------------------- /lib/url.js: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------- 2 | // WARNING: this file is used by both the client and the server. 3 | // Do not use any browser or node-specific API! 4 | // ------------------------------------------------------------- 5 | const codec = require('./codec'); 6 | const defaultConfig = { 7 | prefix: '/service/', 8 | codec: 'plain' 9 | }; 10 | 11 | class URLWrapper { 12 | constructor(config = defaultConfig) { 13 | this.prefix = config.prefix || defaultConfig.prefix; 14 | this.codec = codec[config.codec || 'plain'] || codec['plain']; 15 | this.regex = /^(#|about:|data:|blob:|mailto:|javascript:)/; 16 | }; 17 | wrap(val, config = {}) { 18 | if (!val || this.regex.test(val)) return val; 19 | let flags = ''; 20 | (config.flags || []).forEach(flag => flags += `${flag}_/`); 21 | if (config.base) try { 22 | if (!['http:', 'https:', 'ws:', 'wss:'].includes(new URL(val, config.base).protocol)) return val; 23 | } catch(e) { 24 | return val; 25 | }; 26 | return (config.origin || '') + this.prefix + flags + this.codec.encode(config.base ? new URL(val, config.base) : val) + '/'; 27 | }; 28 | unwrap(val, config = {}) { 29 | if (!val || this.regex.test(val)) return val; 30 | let processed = val.slice((config.origin || '').length + this.prefix.length); 31 | const flags = ('/' + processed).match(/(?<=\/)(.*?)(?=_\/)/g) || []; 32 | flags.forEach(flag => processed = processed.slice(`${flag}_/`.length)); 33 | let [ url, leftovers ] = processed.split(/\/(.+)?/); 34 | return config.flags ? { value: this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : ''), flags } : this.codec.decode((url || '')) + (config.leftovers && leftovers ? leftovers : ''); 35 | }; 36 | }; 37 | 38 | module.exports = URLWrapper; 39 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shadow", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/eslint": { 8 | "version": "7.28.2", 9 | "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz", 10 | "integrity": "sha512-KubbADPkfoU75KgKeKLsFHXnU4ipH7wYg0TRT33NK3N3yiu7jlFAAoygIWBV+KbuHx/G+AvuGX6DllnK35gfJA==", 11 | "requires": { 12 | "@types/estree": "*", 13 | "@types/json-schema": "*" 14 | } 15 | }, 16 | "@types/eslint-scope": { 17 | "version": "3.7.1", 18 | "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.1.tgz", 19 | "integrity": "sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g==", 20 | "requires": { 21 | "@types/eslint": "*", 22 | "@types/estree": "*" 23 | } 24 | }, 25 | "@types/estree": { 26 | "version": "0.0.46", 27 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", 28 | "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==" 29 | }, 30 | "@types/json-schema": { 31 | "version": "7.0.9", 32 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", 33 | "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" 34 | }, 35 | "@types/node": { 36 | "version": "16.11.6", 37 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", 38 | "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==" 39 | }, 40 | "@webassemblyjs/ast": { 41 | "version": "1.11.1", 42 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", 43 | "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", 44 | "requires": { 45 | "@webassemblyjs/helper-numbers": "1.11.1", 46 | "@webassemblyjs/helper-wasm-bytecode": "1.11.1" 47 | } 48 | }, 49 | "@webassemblyjs/floating-point-hex-parser": { 50 | "version": "1.11.1", 51 | "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", 52 | "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==" 53 | }, 54 | "@webassemblyjs/helper-api-error": { 55 | "version": "1.11.1", 56 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", 57 | "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==" 58 | }, 59 | "@webassemblyjs/helper-buffer": { 60 | "version": "1.11.1", 61 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", 62 | "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==" 63 | }, 64 | "@webassemblyjs/helper-numbers": { 65 | "version": "1.11.1", 66 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", 67 | "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", 68 | "requires": { 69 | "@webassemblyjs/floating-point-hex-parser": "1.11.1", 70 | "@webassemblyjs/helper-api-error": "1.11.1", 71 | "@xtuc/long": "4.2.2" 72 | } 73 | }, 74 | "@webassemblyjs/helper-wasm-bytecode": { 75 | "version": "1.11.1", 76 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", 77 | "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==" 78 | }, 79 | "@webassemblyjs/helper-wasm-section": { 80 | "version": "1.11.1", 81 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", 82 | "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", 83 | "requires": { 84 | "@webassemblyjs/ast": "1.11.1", 85 | "@webassemblyjs/helper-buffer": "1.11.1", 86 | "@webassemblyjs/helper-wasm-bytecode": "1.11.1", 87 | "@webassemblyjs/wasm-gen": "1.11.1" 88 | } 89 | }, 90 | "@webassemblyjs/ieee754": { 91 | "version": "1.11.1", 92 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", 93 | "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", 94 | "requires": { 95 | "@xtuc/ieee754": "^1.2.0" 96 | } 97 | }, 98 | "@webassemblyjs/leb128": { 99 | "version": "1.11.1", 100 | "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", 101 | "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", 102 | "requires": { 103 | "@xtuc/long": "4.2.2" 104 | } 105 | }, 106 | "@webassemblyjs/utf8": { 107 | "version": "1.11.1", 108 | "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", 109 | "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==" 110 | }, 111 | "@webassemblyjs/wasm-edit": { 112 | "version": "1.11.1", 113 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", 114 | "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", 115 | "requires": { 116 | "@webassemblyjs/ast": "1.11.1", 117 | "@webassemblyjs/helper-buffer": "1.11.1", 118 | "@webassemblyjs/helper-wasm-bytecode": "1.11.1", 119 | "@webassemblyjs/helper-wasm-section": "1.11.1", 120 | "@webassemblyjs/wasm-gen": "1.11.1", 121 | "@webassemblyjs/wasm-opt": "1.11.1", 122 | "@webassemblyjs/wasm-parser": "1.11.1", 123 | "@webassemblyjs/wast-printer": "1.11.1" 124 | } 125 | }, 126 | "@webassemblyjs/wasm-gen": { 127 | "version": "1.11.1", 128 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", 129 | "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", 130 | "requires": { 131 | "@webassemblyjs/ast": "1.11.1", 132 | "@webassemblyjs/helper-wasm-bytecode": "1.11.1", 133 | "@webassemblyjs/ieee754": "1.11.1", 134 | "@webassemblyjs/leb128": "1.11.1", 135 | "@webassemblyjs/utf8": "1.11.1" 136 | } 137 | }, 138 | "@webassemblyjs/wasm-opt": { 139 | "version": "1.11.1", 140 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", 141 | "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", 142 | "requires": { 143 | "@webassemblyjs/ast": "1.11.1", 144 | "@webassemblyjs/helper-buffer": "1.11.1", 145 | "@webassemblyjs/wasm-gen": "1.11.1", 146 | "@webassemblyjs/wasm-parser": "1.11.1" 147 | } 148 | }, 149 | "@webassemblyjs/wasm-parser": { 150 | "version": "1.11.1", 151 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", 152 | "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", 153 | "requires": { 154 | "@webassemblyjs/ast": "1.11.1", 155 | "@webassemblyjs/helper-api-error": "1.11.1", 156 | "@webassemblyjs/helper-wasm-bytecode": "1.11.1", 157 | "@webassemblyjs/ieee754": "1.11.1", 158 | "@webassemblyjs/leb128": "1.11.1", 159 | "@webassemblyjs/utf8": "1.11.1" 160 | } 161 | }, 162 | "@webassemblyjs/wast-printer": { 163 | "version": "1.11.1", 164 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", 165 | "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", 166 | "requires": { 167 | "@webassemblyjs/ast": "1.11.1", 168 | "@xtuc/long": "4.2.2" 169 | } 170 | }, 171 | "@xtuc/ieee754": { 172 | "version": "1.2.0", 173 | "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", 174 | "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" 175 | }, 176 | "@xtuc/long": { 177 | "version": "4.2.2", 178 | "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", 179 | "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" 180 | }, 181 | "accepts": { 182 | "version": "1.3.7", 183 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 184 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 185 | "requires": { 186 | "mime-types": "~2.1.24", 187 | "negotiator": "0.6.2" 188 | } 189 | }, 190 | "acorn": { 191 | "version": "8.5.0", 192 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", 193 | "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==" 194 | }, 195 | "acorn-hammerhead": { 196 | "version": "0.5.0", 197 | "resolved": "https://registry.npmjs.org/acorn-hammerhead/-/acorn-hammerhead-0.5.0.tgz", 198 | "integrity": "sha512-TI9TFfJBfduhcM2GggayNhdYvdJ3UgS/Bu3sB7FB2AUmNCmCJ+TSOT6GXu+bodG5/xL74D5zE4XRaqyjgjsYVQ==", 199 | "requires": { 200 | "@types/estree": "0.0.46" 201 | } 202 | }, 203 | "acorn-import-assertions": { 204 | "version": "1.8.0", 205 | "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", 206 | "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==" 207 | }, 208 | "ajv": { 209 | "version": "6.12.6", 210 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 211 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 212 | "requires": { 213 | "fast-deep-equal": "^3.1.1", 214 | "fast-json-stable-stringify": "^2.0.0", 215 | "json-schema-traverse": "^0.4.1", 216 | "uri-js": "^4.2.2" 217 | } 218 | }, 219 | "ajv-keywords": { 220 | "version": "3.5.2", 221 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", 222 | "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" 223 | }, 224 | "array-flatten": { 225 | "version": "1.1.1", 226 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 227 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 228 | }, 229 | "body-parser": { 230 | "version": "1.19.0", 231 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 232 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 233 | "requires": { 234 | "bytes": "3.1.0", 235 | "content-type": "~1.0.4", 236 | "debug": "2.6.9", 237 | "depd": "~1.1.2", 238 | "http-errors": "1.7.2", 239 | "iconv-lite": "0.4.24", 240 | "on-finished": "~2.3.0", 241 | "qs": "6.7.0", 242 | "raw-body": "2.4.0", 243 | "type-is": "~1.6.17" 244 | } 245 | }, 246 | "browserslist": { 247 | "version": "4.17.6", 248 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.6.tgz", 249 | "integrity": "sha512-uPgz3vyRTlEiCv4ee9KlsKgo2V6qPk7Jsn0KAn2OBqbqKo3iNcPEC1Ti6J4dwnz+aIRfEEEuOzC9IBk8tXUomw==", 250 | "requires": { 251 | "caniuse-lite": "^1.0.30001274", 252 | "electron-to-chromium": "^1.3.886", 253 | "escalade": "^3.1.1", 254 | "node-releases": "^2.0.1", 255 | "picocolors": "^1.0.0" 256 | } 257 | }, 258 | "buffer-from": { 259 | "version": "1.1.2", 260 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 261 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 262 | }, 263 | "bytes": { 264 | "version": "3.1.0", 265 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 266 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 267 | }, 268 | "caniuse-lite": { 269 | "version": "1.0.30001274", 270 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001274.tgz", 271 | "integrity": "sha512-+Nkvv0fHyhISkiMIjnyjmf5YJcQ1IQHZN6U9TLUMroWR38FNwpsC51Gb68yueafX1V6ifOisInSgP9WJFS13ew==" 272 | }, 273 | "chrome-trace-event": { 274 | "version": "1.0.3", 275 | "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", 276 | "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" 277 | }, 278 | "commander": { 279 | "version": "2.20.3", 280 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 281 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 282 | }, 283 | "content-disposition": { 284 | "version": "0.5.3", 285 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 286 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 287 | "requires": { 288 | "safe-buffer": "5.1.2" 289 | } 290 | }, 291 | "content-type": { 292 | "version": "1.0.4", 293 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 294 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 295 | }, 296 | "cookie": { 297 | "version": "0.4.0", 298 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 299 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 300 | }, 301 | "cookie-signature": { 302 | "version": "1.0.6", 303 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 304 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 305 | }, 306 | "css-tree": { 307 | "version": "1.1.3", 308 | "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", 309 | "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", 310 | "requires": { 311 | "mdn-data": "2.0.14", 312 | "source-map": "^0.6.1" 313 | } 314 | }, 315 | "debug": { 316 | "version": "2.6.9", 317 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 318 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 319 | "requires": { 320 | "ms": "2.0.0" 321 | } 322 | }, 323 | "depd": { 324 | "version": "1.1.2", 325 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 326 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 327 | }, 328 | "destroy": { 329 | "version": "1.0.4", 330 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 331 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 332 | }, 333 | "ee-first": { 334 | "version": "1.1.1", 335 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 336 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 337 | }, 338 | "electron-to-chromium": { 339 | "version": "1.3.887", 340 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.887.tgz", 341 | "integrity": "sha512-QQUumrEjFDKSVYVdaeBmFdyQGoaV+fCSMyWHvfx/u22bRHSTeBQYt6P4jMY+gFd4kgKB9nqk7RMtWkDB49OYPA==" 342 | }, 343 | "encodeurl": { 344 | "version": "1.0.2", 345 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 346 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 347 | }, 348 | "enhanced-resolve": { 349 | "version": "5.8.3", 350 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz", 351 | "integrity": "sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA==", 352 | "requires": { 353 | "graceful-fs": "^4.2.4", 354 | "tapable": "^2.2.0" 355 | } 356 | }, 357 | "es-module-lexer": { 358 | "version": "0.9.3", 359 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", 360 | "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" 361 | }, 362 | "escalade": { 363 | "version": "3.1.1", 364 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 365 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 366 | }, 367 | "escape-html": { 368 | "version": "1.0.3", 369 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 370 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 371 | }, 372 | "eslint-scope": { 373 | "version": "5.1.1", 374 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 375 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 376 | "requires": { 377 | "esrecurse": "^4.3.0", 378 | "estraverse": "^4.1.1" 379 | } 380 | }, 381 | "esrecurse": { 382 | "version": "4.3.0", 383 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 384 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 385 | "requires": { 386 | "estraverse": "^5.2.0" 387 | }, 388 | "dependencies": { 389 | "estraverse": { 390 | "version": "5.3.0", 391 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 392 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" 393 | } 394 | } 395 | }, 396 | "estraverse": { 397 | "version": "4.3.0", 398 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 399 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" 400 | }, 401 | "etag": { 402 | "version": "1.8.1", 403 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 404 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 405 | }, 406 | "events": { 407 | "version": "3.3.0", 408 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 409 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" 410 | }, 411 | "express": { 412 | "version": "4.17.1", 413 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 414 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 415 | "requires": { 416 | "accepts": "~1.3.7", 417 | "array-flatten": "1.1.1", 418 | "body-parser": "1.19.0", 419 | "content-disposition": "0.5.3", 420 | "content-type": "~1.0.4", 421 | "cookie": "0.4.0", 422 | "cookie-signature": "1.0.6", 423 | "debug": "2.6.9", 424 | "depd": "~1.1.2", 425 | "encodeurl": "~1.0.2", 426 | "escape-html": "~1.0.3", 427 | "etag": "~1.8.1", 428 | "finalhandler": "~1.1.2", 429 | "fresh": "0.5.2", 430 | "merge-descriptors": "1.0.1", 431 | "methods": "~1.1.2", 432 | "on-finished": "~2.3.0", 433 | "parseurl": "~1.3.3", 434 | "path-to-regexp": "0.1.7", 435 | "proxy-addr": "~2.0.5", 436 | "qs": "6.7.0", 437 | "range-parser": "~1.2.1", 438 | "safe-buffer": "5.1.2", 439 | "send": "0.17.1", 440 | "serve-static": "1.14.1", 441 | "setprototypeof": "1.1.1", 442 | "statuses": "~1.5.0", 443 | "type-is": "~1.6.18", 444 | "utils-merge": "1.0.1", 445 | "vary": "~1.1.2" 446 | } 447 | }, 448 | "fast-deep-equal": { 449 | "version": "3.1.3", 450 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 451 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 452 | }, 453 | "fast-json-stable-stringify": { 454 | "version": "2.1.0", 455 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 456 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 457 | }, 458 | "finalhandler": { 459 | "version": "1.1.2", 460 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 461 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 462 | "requires": { 463 | "debug": "2.6.9", 464 | "encodeurl": "~1.0.2", 465 | "escape-html": "~1.0.3", 466 | "on-finished": "~2.3.0", 467 | "parseurl": "~1.3.3", 468 | "statuses": "~1.5.0", 469 | "unpipe": "~1.0.0" 470 | } 471 | }, 472 | "forwarded": { 473 | "version": "0.2.0", 474 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 475 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 476 | }, 477 | "fresh": { 478 | "version": "0.5.2", 479 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 480 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 481 | }, 482 | "glob-to-regexp": { 483 | "version": "0.4.1", 484 | "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", 485 | "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" 486 | }, 487 | "graceful-fs": { 488 | "version": "4.2.8", 489 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", 490 | "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" 491 | }, 492 | "has-flag": { 493 | "version": "4.0.0", 494 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 495 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 496 | }, 497 | "http-errors": { 498 | "version": "1.7.2", 499 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 500 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 501 | "requires": { 502 | "depd": "~1.1.2", 503 | "inherits": "2.0.3", 504 | "setprototypeof": "1.1.1", 505 | "statuses": ">= 1.5.0 < 2", 506 | "toidentifier": "1.0.0" 507 | } 508 | }, 509 | "iconv-lite": { 510 | "version": "0.4.24", 511 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 512 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 513 | "requires": { 514 | "safer-buffer": ">= 2.1.2 < 3" 515 | } 516 | }, 517 | "inherits": { 518 | "version": "2.0.3", 519 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 520 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 521 | }, 522 | "ipaddr.js": { 523 | "version": "1.9.1", 524 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 525 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 526 | }, 527 | "jest-worker": { 528 | "version": "27.3.1", 529 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz", 530 | "integrity": "sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g==", 531 | "requires": { 532 | "@types/node": "*", 533 | "merge-stream": "^2.0.0", 534 | "supports-color": "^8.0.0" 535 | } 536 | }, 537 | "json-parse-better-errors": { 538 | "version": "1.0.2", 539 | "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", 540 | "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" 541 | }, 542 | "json-schema-traverse": { 543 | "version": "0.4.1", 544 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 545 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 546 | }, 547 | "loader-runner": { 548 | "version": "4.2.0", 549 | "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", 550 | "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" 551 | }, 552 | "mdn-data": { 553 | "version": "2.0.14", 554 | "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", 555 | "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" 556 | }, 557 | "media-typer": { 558 | "version": "0.3.0", 559 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 560 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 561 | }, 562 | "merge-descriptors": { 563 | "version": "1.0.1", 564 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 565 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 566 | }, 567 | "merge-stream": { 568 | "version": "2.0.0", 569 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 570 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" 571 | }, 572 | "methods": { 573 | "version": "1.1.2", 574 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 575 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 576 | }, 577 | "mime": { 578 | "version": "1.6.0", 579 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 580 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 581 | }, 582 | "mime-db": { 583 | "version": "1.50.0", 584 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", 585 | "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" 586 | }, 587 | "mime-types": { 588 | "version": "2.1.33", 589 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", 590 | "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", 591 | "requires": { 592 | "mime-db": "1.50.0" 593 | } 594 | }, 595 | "ms": { 596 | "version": "2.0.0", 597 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 598 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 599 | }, 600 | "negotiator": { 601 | "version": "0.6.2", 602 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 603 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 604 | }, 605 | "neo-async": { 606 | "version": "2.6.2", 607 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", 608 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" 609 | }, 610 | "node-fetch": { 611 | "version": "2.1.0", 612 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.0.tgz", 613 | "integrity": "sha1-nZbd6f4fzvbkCBImuklrjVlQtkc=" 614 | }, 615 | "node-releases": { 616 | "version": "2.0.1", 617 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", 618 | "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==" 619 | }, 620 | "on-finished": { 621 | "version": "2.3.0", 622 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 623 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 624 | "requires": { 625 | "ee-first": "1.1.1" 626 | } 627 | }, 628 | "p-limit": { 629 | "version": "3.1.0", 630 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 631 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 632 | "requires": { 633 | "yocto-queue": "^0.1.0" 634 | } 635 | }, 636 | "parse5": { 637 | "version": "6.0.1", 638 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", 639 | "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" 640 | }, 641 | "parseurl": { 642 | "version": "1.3.3", 643 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 644 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 645 | }, 646 | "path-to-regexp": { 647 | "version": "0.1.7", 648 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 649 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 650 | }, 651 | "picocolors": { 652 | "version": "1.0.0", 653 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 654 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 655 | }, 656 | "proxy-addr": { 657 | "version": "2.0.7", 658 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 659 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 660 | "requires": { 661 | "forwarded": "0.2.0", 662 | "ipaddr.js": "1.9.1" 663 | } 664 | }, 665 | "punycode": { 666 | "version": "2.1.1", 667 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 668 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 669 | }, 670 | "qs": { 671 | "version": "6.7.0", 672 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 673 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 674 | }, 675 | "randombytes": { 676 | "version": "2.1.0", 677 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 678 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 679 | "requires": { 680 | "safe-buffer": "^5.1.0" 681 | } 682 | }, 683 | "range-parser": { 684 | "version": "1.2.1", 685 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 686 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 687 | }, 688 | "raw-body": { 689 | "version": "2.4.0", 690 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 691 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 692 | "requires": { 693 | "bytes": "3.1.0", 694 | "http-errors": "1.7.2", 695 | "iconv-lite": "0.4.24", 696 | "unpipe": "1.0.0" 697 | } 698 | }, 699 | "safe-buffer": { 700 | "version": "5.1.2", 701 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 702 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 703 | }, 704 | "safer-buffer": { 705 | "version": "2.1.2", 706 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 707 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 708 | }, 709 | "schema-utils": { 710 | "version": "3.1.1", 711 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", 712 | "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", 713 | "requires": { 714 | "@types/json-schema": "^7.0.8", 715 | "ajv": "^6.12.5", 716 | "ajv-keywords": "^3.5.2" 717 | } 718 | }, 719 | "send": { 720 | "version": "0.17.1", 721 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 722 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 723 | "requires": { 724 | "debug": "2.6.9", 725 | "depd": "~1.1.2", 726 | "destroy": "~1.0.4", 727 | "encodeurl": "~1.0.2", 728 | "escape-html": "~1.0.3", 729 | "etag": "~1.8.1", 730 | "fresh": "0.5.2", 731 | "http-errors": "~1.7.2", 732 | "mime": "1.6.0", 733 | "ms": "2.1.1", 734 | "on-finished": "~2.3.0", 735 | "range-parser": "~1.2.1", 736 | "statuses": "~1.5.0" 737 | }, 738 | "dependencies": { 739 | "ms": { 740 | "version": "2.1.1", 741 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 742 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 743 | } 744 | } 745 | }, 746 | "serialize-javascript": { 747 | "version": "6.0.0", 748 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", 749 | "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", 750 | "requires": { 751 | "randombytes": "^2.1.0" 752 | } 753 | }, 754 | "serve-static": { 755 | "version": "1.14.1", 756 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 757 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 758 | "requires": { 759 | "encodeurl": "~1.0.2", 760 | "escape-html": "~1.0.3", 761 | "parseurl": "~1.3.3", 762 | "send": "0.17.1" 763 | } 764 | }, 765 | "setprototypeof": { 766 | "version": "1.1.1", 767 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 768 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 769 | }, 770 | "source-map": { 771 | "version": "0.6.1", 772 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 773 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 774 | }, 775 | "source-map-support": { 776 | "version": "0.5.20", 777 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", 778 | "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", 779 | "requires": { 780 | "buffer-from": "^1.0.0", 781 | "source-map": "^0.6.0" 782 | } 783 | }, 784 | "statuses": { 785 | "version": "1.5.0", 786 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 787 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 788 | }, 789 | "supports-color": { 790 | "version": "8.1.1", 791 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 792 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 793 | "requires": { 794 | "has-flag": "^4.0.0" 795 | } 796 | }, 797 | "tapable": { 798 | "version": "2.2.1", 799 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", 800 | "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" 801 | }, 802 | "terser": { 803 | "version": "5.9.0", 804 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.9.0.tgz", 805 | "integrity": "sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ==", 806 | "requires": { 807 | "commander": "^2.20.0", 808 | "source-map": "~0.7.2", 809 | "source-map-support": "~0.5.20" 810 | }, 811 | "dependencies": { 812 | "source-map": { 813 | "version": "0.7.3", 814 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 815 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" 816 | } 817 | } 818 | }, 819 | "terser-webpack-plugin": { 820 | "version": "5.2.4", 821 | "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz", 822 | "integrity": "sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==", 823 | "requires": { 824 | "jest-worker": "^27.0.6", 825 | "p-limit": "^3.1.0", 826 | "schema-utils": "^3.1.1", 827 | "serialize-javascript": "^6.0.0", 828 | "source-map": "^0.6.1", 829 | "terser": "^5.7.2" 830 | } 831 | }, 832 | "toidentifier": { 833 | "version": "1.0.0", 834 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 835 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 836 | }, 837 | "type-is": { 838 | "version": "1.6.18", 839 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 840 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 841 | "requires": { 842 | "media-typer": "0.3.0", 843 | "mime-types": "~2.1.24" 844 | } 845 | }, 846 | "unpipe": { 847 | "version": "1.0.0", 848 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 849 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 850 | }, 851 | "uri-js": { 852 | "version": "4.4.1", 853 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 854 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 855 | "requires": { 856 | "punycode": "^2.1.0" 857 | } 858 | }, 859 | "utils-merge": { 860 | "version": "1.0.1", 861 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 862 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 863 | }, 864 | "vary": { 865 | "version": "1.1.2", 866 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 867 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 868 | }, 869 | "watchpack": { 870 | "version": "2.2.0", 871 | "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", 872 | "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", 873 | "requires": { 874 | "glob-to-regexp": "^0.4.1", 875 | "graceful-fs": "^4.1.2" 876 | } 877 | }, 878 | "webpack": { 879 | "version": "5.61.0", 880 | "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.61.0.tgz", 881 | "integrity": "sha512-fPdTuaYZ/GMGFm4WrPi2KRCqS1vDp773kj9S0iI5Uc//5cszsFEDgHNaX4Rj1vobUiU1dFIV3mA9k1eHeluFpw==", 882 | "requires": { 883 | "@types/eslint-scope": "^3.7.0", 884 | "@types/estree": "^0.0.50", 885 | "@webassemblyjs/ast": "1.11.1", 886 | "@webassemblyjs/wasm-edit": "1.11.1", 887 | "@webassemblyjs/wasm-parser": "1.11.1", 888 | "acorn": "^8.4.1", 889 | "acorn-import-assertions": "^1.7.6", 890 | "browserslist": "^4.14.5", 891 | "chrome-trace-event": "^1.0.2", 892 | "enhanced-resolve": "^5.8.3", 893 | "es-module-lexer": "^0.9.0", 894 | "eslint-scope": "5.1.1", 895 | "events": "^3.2.0", 896 | "glob-to-regexp": "^0.4.1", 897 | "graceful-fs": "^4.2.4", 898 | "json-parse-better-errors": "^1.0.2", 899 | "loader-runner": "^4.2.0", 900 | "mime-types": "^2.1.27", 901 | "neo-async": "^2.6.2", 902 | "schema-utils": "^3.1.0", 903 | "tapable": "^2.1.1", 904 | "terser-webpack-plugin": "^5.1.3", 905 | "watchpack": "^2.2.0", 906 | "webpack-sources": "^3.2.0" 907 | }, 908 | "dependencies": { 909 | "@types/estree": { 910 | "version": "0.0.50", 911 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz", 912 | "integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==" 913 | } 914 | } 915 | }, 916 | "webpack-sources": { 917 | "version": "3.2.1", 918 | "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.1.tgz", 919 | "integrity": "sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA==" 920 | }, 921 | "yocto-queue": { 922 | "version": "0.1.0", 923 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 924 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" 925 | } 926 | } 927 | } 928 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Shadow", 3 | "version": "1.0.0", 4 | "repository": "https://github.com/FogNetwork/Shadow", 5 | "bugs": { 6 | "url": "https://github.com/FogNetwork/Shadow/issues" 7 | }, 8 | "description": "Shadow is a simple yet stunning service built to access any website", 9 | "main": "app.js", 10 | "scripts": { 11 | "start": "node app.js" 12 | }, 13 | "author": "Fog Network", 14 | "license": "MIT", 15 | "dependencies": { 16 | "acorn-hammerhead": "^0.5.0", 17 | "css-tree": "^1.1.3", 18 | "express": "^4.17.1", 19 | "parse5": "^6.0.1", 20 | "webpack": "^5.61.0", 21 | "node-fetch": "2.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
404 Error
20 |
The page was not found. 
Go back?
21 | 22 | 23 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap"); 2 | 3 | * { 4 | font-family: Roboto; 5 | } 6 | 7 | body { 8 | background: black; 9 | } 10 | 11 | #search { 12 | height: 45px; 13 | width: 580px; 14 | border-radius: 100px; 15 | outline: none; 16 | border: 2px solid white; 17 | background: transparent; 18 | color: white; 19 | font-size: 16px; 20 | padding-right: 10px; 21 | padding-left: 40px; 22 | } 23 | 24 | .title { 25 | color: white; 26 | text-align: center; 27 | font-size: 90px; 28 | margin-top: 200px; 29 | } 30 | 31 | .quicklinks { 32 | margin-top: 10px; 33 | } 34 | 35 | .quicklink { 36 | color: white; 37 | font-size: 20px; 38 | border: 1px solid white; 39 | border-radius: 100px; 40 | padding: 8px; 41 | cursor: pointer; 42 | height: 35px; 43 | line-height: 35px; 44 | } 45 | 46 | #web { 47 | display: none; 48 | position: fixed; 49 | height: 752px; 50 | width: 100%; 51 | top: 45px; 52 | bottom: 0; 53 | left: 0; 54 | right: 0; 55 | outline: none; 56 | border: none; 57 | background: white; 58 | z-index: 99; 59 | } 60 | 61 | .searchicon { 62 | color: grey; 63 | position: absolute; 64 | top: 322px; 65 | font-size: 23px; 66 | margin-left: 10px; 67 | } 68 | 69 | .settingsbtn { 70 | cursor: pointer; 71 | color: white; 72 | position: fixed; 73 | top: 8px; 74 | right: 8px; 75 | font-size: 35px; 76 | border-radius: 2.5px; 77 | border: 1px solid white; 78 | padding: 5px; 79 | } 80 | 81 | quicktext { 82 | font-size: 20px; 83 | padding: 5px; 84 | line-height: 35px; 85 | } 86 | 87 | footer { 88 | color: white; 89 | position: fixed; 90 | bottom: 5px; 91 | left: 0; 92 | right: 0; 93 | text-align: center; 94 | } 95 | 96 | a { 97 | color: white; 98 | text-decoration: none; 99 | } 100 | 101 | a:hover { 102 | text-decoration: underline; 103 | } 104 | 105 | .settings { 106 | display: none; 107 | position: fixed; 108 | top: 65px; 109 | right: 8px; 110 | border: 1px solid white; 111 | height: 300px; 112 | width: 200px; 113 | border-radius: 2px; 114 | background: black; 115 | } 116 | 117 | .webnav { 118 | position: fixed; 119 | top: 0; 120 | right: 0; 121 | left: 0; 122 | width: 100%; 123 | height: 45px; 124 | display: flex; 125 | flex-direction: row-reverse; 126 | display: none; 127 | z-index: 100; 128 | } 129 | 130 | .webtitle { 131 | color: white; 132 | line-height: 45px; 133 | font-size: 25px; 134 | padding-left: 8px; 135 | position: fixed; 136 | left: 0; 137 | } 138 | 139 | .webbtn { 140 | color: white; 141 | font-size: 30px; 142 | border-radius: 2.5px; 143 | border: 1px solid white; 144 | margin: 5px; 145 | height: 35px; 146 | width: 35px; 147 | text-align: center; 148 | cursor: pointer; 149 | } 150 | 151 | .webicon { 152 | line-height: 35px; 153 | } 154 | 155 | .settitle { 156 | color: white; 157 | text-align: center; 158 | font-size: 20px; 159 | margin: 5px; 160 | } 161 | 162 | .settab { 163 | outline: none; 164 | border: 1px solid white; 165 | background: black; 166 | color: white; 167 | height: 25px; 168 | margin: 5px; 169 | border-radius: 2.5px; 170 | padding-left: 5px; 171 | } 172 | 173 | .errortitle { 174 | font-size: 50px; 175 | color: white; 176 | text-align: center; 177 | margin-top: 20px; 178 | } 179 | 180 | .errordisc { 181 | font-size: 25px; 182 | color: white; 183 | text-align: center; 184 | display: flex; 185 | justify-content: center; 186 | align-items: center; 187 | margin-top: 10px; 188 | } 189 | 190 | .errorback { 191 | cursor: pointer; 192 | } 193 | 194 | .errorback:hover { 195 | text-decoration: underline; 196 | } 197 | 198 | #suggestions { 199 | display: none; 200 | width: 630px; 201 | height: 268px; 202 | border: 2px solid white; 203 | border-radius: 0 0 25px 25px; 204 | border-top: none; 205 | overflow: hidden; 206 | position: absolute; 207 | background: black; 208 | margin-left: auto; 209 | margin-right: auto; 210 | left: 0; 211 | right: 0; 212 | text-align: left; 213 | } 214 | 215 | .sugg { 216 | color: white; 217 | font-size: 20px; 218 | cursor: pointer; 219 | padding: 5px; 220 | padding-left: 10px; 221 | } 222 | 223 | .sugg:hover { 224 | background: white; 225 | color: black; 226 | } -------------------------------------------------------------------------------- /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FogNetwork/Shadow/b7afdfe66ed84330d49b24dc90817265899576b9/public/img/logo.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shadow 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
Settings
22 |
23 | 24 | 25 |
26 |
27 | 28 |
Shadow
29 | 30 |
31 | 32 | 33 | 34 |
35 | 36 | 42 |
43 | 44 |
45 |
Shadow
46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /public/js/go.js: -------------------------------------------------------------------------------- 1 | function hidesugg() { 2 | document.getElementById("search").style.borderRadius = "100px"; 3 | document.getElementById("suggestions").style.display = "none" 4 | } 5 | 6 | function showsugg() { 7 | document.getElementById("search").style.borderRadius = "25px 25px 0 0"; 8 | document.getElementById("suggestions").style.display = "inherit" 9 | } 10 | 11 | function sugggo(suggtext) { 12 | go(suggtext) 13 | document.getElementById("search").value = "" 14 | } 15 | 16 | window.addEventListener("load", function() { 17 | var search = document.getElementById("search") 18 | search.addEventListener("keyup", function(event) { 19 |     event.preventDefault() 20 |     if (event.keyCode == 13) 21 |         if (this.value !== "") { 22 |              go(this.value) 23 |              this.value = "" 24 |         } 25 | }); 26 | search.addEventListener("keyup", function(event) { 27 | event.preventDefault() 28 | if (search.value.trim().length !== 0) { 29 | document.getElementById("suggestions").innerText = "" 30 | showsugg() 31 | async function getsuggestions() { 32 | var term = search.value || ""; 33 | var response = await fetch("/suggestions?q=" + term); 34 | var result = await response.json(); 35 | var suggestions = result.slice(0, 8); 36 | for (sugg in suggestions) { 37 | var suggestion = suggestions[sugg] 38 | var sugg = document.createElement("div") 39 | sugg.innerText = suggestion 40 | sugg.setAttribute("onclick", "sugggo(this.innerText)") 41 | sugg.className = "sugg" 42 | document.getElementById("suggestions").appendChild(sugg) 43 | } 44 | } 45 | getsuggestions() 46 | } else { 47 | hidesugg() 48 | } 49 | }); 50 | 51 | search.addEventListener("click", function(event) { 52 | if (search.value.trim().length !== 0) { 53 | showsugg() 54 | } 55 | }) 56 | 57 | }) 58 | 59 | function go(url) { 60 | var web = document.getElementById("web") 61 | var webnav = document.getElementById("webnav") 62 | var settingsbtn = document.getElementById("settingsbtn") 63 | web.src = "/service/gateway?url=" + url 64 | web.style.display = "initial" 65 | webnav.style.display = "flex" 66 | settingsbtn.style.display = "none" 67 | } 68 | 69 | function closeweb() { 70 | var web = document.getElementById("web") 71 | var webnav = document.getElementById("webnav") 72 | var settingsbtn = document.getElementById("settingsbtn") 73 | var search = document.getElementById("search") 74 | web.src = "" 75 | web.style.display = "none" 76 | webnav.style.display = "none" 77 | settingsbtn.style.display = "initial" 78 | search.focus() 79 | } 80 | 81 | function reloadweb() { 82 | var web = document.getElementById("web") 83 | web.contentWindow.location.reload() 84 | } 85 | 86 | function settings() { 87 | var settings = document.getElementById("settings") 88 | if (settings.style.display == "none") { 89 | settings.style.display = "initial" 90 | } else if (settings.style.display == "initial") { 91 | settings.style.display = "none" 92 | } else { 93 | settings.style.display = "initial" 94 | } 95 | } 96 | 97 | function settitle(text) { 98 | if (text !== "") { 99 | document.title = text 100 | localStorage.setItem("title", text) 101 | } else { 102 | document.title = "Shadow" 103 | localStorage.removeItem("title") 104 | } 105 | } 106 | 107 | function seticon(url) { 108 | if (url !== "") { 109 | document.querySelector("link[rel='shortcut icon']").href = url 110 | localStorage.setItem("favicon", url) 111 | } else { 112 | document.querySelector("link[rel='shortcut icon']").href = "/img/logo.png" 113 | localStorage.removeItem("favicon") 114 | } 115 | } 116 | 117 | window.addEventListener('load', function() { 118 | var title = localStorage.getItem("title") 119 | var favicon = localStorage.getItem("favicon") 120 | var settitle = document.getElementsByClassName("settab")[0] 121 | var setfav = document.getElementsByClassName("settab")[1] 122 | 123 | if (title !== null) {settitle.value = title} 124 | if (favicon !== null) {setfav.value = favicon} 125 | }) 126 | 127 | function hidesettings(){ 128 | if(window.event.srcElement.id !== "settings" && window.event.srcElement.id !== "settingsbtn" && window.event.srcElement.className !== "settitle" && window.event.srcElement.className !== "settab"){ 129 | document.getElementById("settings").style.display = "none"; 130 | } 131 | if(window.event.srcElement.id !== "search" && window.event.srcElement.id !== "suggestions"){ 132 | hidesugg() 133 | } 134 | } 135 | 136 | document.onclick = hidesettings -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function() { 2 | var title = localStorage.getItem("title") 3 | var favicon = localStorage.getItem("favicon") 4 | 5 | if (title !== null) {document.title = title} 6 | if (favicon !== null) {document.querySelector("link[rel='shortcut icon']").href = favicon} 7 | 8 | }) --------------------------------------------------------------------------------