diff --git a/app.js b/app.js index 915f953..2848481 100644 --- a/app.js +++ b/app.js @@ -1,39 +1,3 @@ -import Server from 'bare-server-node'; -import https from 'https'; -import nodeStatic from 'node-static'; -import fs from 'fs'; - -const bare = new Server('/bare/', ''); - -const serve = new nodeStatic.Server('static/'); -const patronServe = new nodeStatic.Server('static/'); -const fakeServe = new nodeStatic.Server('fakeStatic/'); - -const server = https.createServer(); - -fs.readdir('/etc/letsencrypt/live', { withFileTypes: true }, (err, files) => { - if (!err) - files - .filter(file => file.isDirectory()) - .map(folder => folder.name) - .forEach(dir => { - server.addContext(dir, { - key: fs.readFileSync(`/etc/letsencrypt/live/${dir}/privkey.pem`), - cert: fs.readFileSync(`/etc/letsencrypt/live/${dir}/fullchain.pem`) - }); - }); -}); - -server.on('request', (request, response) => { - - serve.serve(request, response); -}); - -server.on('upgrade', (req, socket, head) => { - if (bare.route_upgrade(req, socket, head)) - return; - - socket.end(); -}); - -server.listen(443); \ No newline at end of file +(async() => { + await import('./app.mjs'); +})(); \ No newline at end of file diff --git a/app.mjs b/app.mjs new file mode 100644 index 0000000..19a6603 --- /dev/null +++ b/app.mjs @@ -0,0 +1,43 @@ +import Server from 'bare-server-node'; +import http from 'http'; +import nodeStatic from 'node-static'; +import fs from 'fs'; +const custombare = require('./static/customBare.js'); + +const bare = new Server('/bare/', ''); + +const serve = new nodeStatic.Server('static/'); +const patronServe = new nodeStatic.Server('static/'); +const fakeServe = new nodeStatic.Server('fakeStatic/'); + +const server = https.createServer(); + +fs.readdir('/etc/letsencrypt/live', { withFileTypes: true }, (err, files) => { + if (!err) + files + .filter(file => file.isDirectory()) + .map(folder => folder.name) + .forEach(dir => { + server.addContext(dir, { + key: fs.readFileSync(`/etc/letsencrypt/live/${dir}/privkey.pem`), + cert: fs.readFileSync(`/etc/letsencrypt/live/${dir}/fullchain.pem`) + }); + }); +}); + +server.on('request', (request, response) => { + if (custombare.isBare(request, response)) { + custombare.route(request,response); + } + if (bare.route_request(request, response)) return true; + serve.serve(request, response); +}); + +server.on('upgrade', (req, socket, head) => { + if (bare.route_upgrade(req, socket, head)) + return; + + socket.end(); +}); + +server.listen(443); \ No newline at end of file diff --git a/static/customBare.js b/static/customBare.js new file mode 100644 index 0000000..b2ff81e --- /dev/null +++ b/static/customBare.js @@ -0,0 +1,111 @@ +const fetch = require('node-fetch'); +const fs = require('fs'); + +const config = { + prefix: "/service" +} + +function rewriteJavascript(js) { + var javascript = js.replace('window.location', 'document._dlocation') + javascript = javascript.replace('document.location', 'document._dlocation') + javascript = javascript.replace('location.', 'document._location.') + return javascript +} + +function insertScript(html, origin) { + var res = ` + +
+ + + +${html} + +` + return res +} // + +async function fetchBare(url, res, req) { + try { + var origin = 'https' + "://" + req.rawHeaders[1] + + var options = { + method: req.method, + headers: { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36", + cookies: req.cookies + }, + credentials: "same-origin" + } + + var request = await fetch(url.href, options); + var contentType = request.headers.get('content-type') || 'application/javascript' + + if (contentType.includes('html') || contentType.includes('javascript')) { + var doc = await request.text(); + } + + res.writeHead(200, "Sucess", { + "content-type": contentType + }) + + if (contentType.includes('html')) { + output = insertScript(doc, origin); + res.end(output) + } else if (contentType.includes('javascript')) { + output = rewriteJavascript(doc) + res.end(output) + } else { + request.body.pipe(res) + } + + + } catch (e) { + console.log(e); + + res.end(e) + } +} + +async function route(req, res) { + var path = req.url; + + if (path.startsWith(config.prefix + "/")) { + + try { + var url = new URL(path.split(config.prefix + "/")[1]) + } catch { + var url = new URL("https://" + path.split(config.prefix + "/")[1]) + } + + fetchBare(url, res, req); + + } else { + if (path === "/cyclone.js") { + var file = fs.readFileSync(__dirname + '/cyclone.js', 'utf8') + res.writeHead(200, 'Sucess', { + "content-type": 'application/javascript' + }) + res.end(file) + } + if (path === "/sw.js") { + var file = fs.readFileSync(__dirname + '/sw.js', 'utf8') + res.writeHead(200, 'Sucess', { + "content-type": 'application/javascript' + }) + res.end(file) + } + } +} + +function isBare(req, res) { + res.writeHead(200, "Sucess", { + "Cros-Origin": "Access-Control-Allow-Origin" + }) + return (req.url === "/cyclone.js" || req.url === "/cySw.js") || req.url.startsWith(config.prefix); +} + +module.exports = { + route, + isBare +} \ No newline at end of file diff --git a/static/cySw.js b/static/cySw.js new file mode 100644 index 0000000..2f9856d --- /dev/null +++ b/static/cySw.js @@ -0,0 +1,108 @@ +class Cyclone { + constructor() { + tmp = location.pathname.split('/service')[1] + + tmp = tmp.substring(1, tmp.length); + let re = /(http(s|):)/g + + //if (tmp.match(re)) { + tmp = tmp.replace("http://", '') + tmp = tmp.replace("https://", '') + tmp = tmp.replace("http:/", '') + tmp = tmp.replace("https:/", '') + tmp = location.protocol + "//" + tmp + + document._location = new URL(tmp); + + this.url = new URL(document._location.href); + + this.bareEndpoint = location.host + "/service"; + + if (this.url.pathname == "/") { + this.paths = ['/'] + } else { + this.paths = this.url.pathname.split('/') + } + this.host = 'https://' + this.url.host + + this.targetAttrs = ['href', 'src', 'action', 'srcdoc', 'srcset']; + + console.log("Cyclone Injected with paths of:", this.paths, this.url.pathname) + + /*const LocationHandler = { + get(target, prop, reciver) { + return loc[prop] + }, + set(target, prop, val) { + return 'hi' + } + } + document._location = new Proxy(LocationHandler, loc)*/ + } + + rewriteUrl(link) { + var rewritten; + + if (link.startsWith('https://') || link.startsWith('http://') || link.startsWith('//')) { + if (link.startsWith('//')) { + rewritten = 'https:' + link; + } else { + rewritten = link; + }; + } else { + if (link.startsWith('.')) { + let offset = 1; + if (link.startsWith('..')) { + offset = 2; + } + let file = link.substr(link.indexOf('.') + 1 + offset, link.length) + + rewritten = this.url.hostname + file + } else { + if (link.startsWith('/')) { + rewritten = this.host + link + } else { + rewritten = this.host + '/' + link; + } + } + } + + var exceptions = ['about:', 'mailto:', 'javascript:', 'data:'] + let needstowrite = true; + for (let i = 0; i < exceptions.length; i++) { + if (link.startsWith(exceptions[i])) { + needstowrite = false + } + } + + if (needstowrite) { + rewritten = location.protocol + '//' + this.bareEndpoint + '/' + rewritten + return rewritten; + } else { + return link; + } + } + + rewriteSrcset(sample) { + return sample.split(',').map(e => { + return (e.split(' ').map(a => { + if (a.startsWith('http') || (a.startsWith('/') && !a.startsWith(this.prefix))) { + var url = this.rewriteUrl(a) + } + return a.replace(a, (url || a)) + }).join(' ')) + }).join(',') + } + } + + self.addEventListener('fetch', function(event) { + var uri = new URL(event.request.url); + + if (!uri.pathname.startsWith('/service') && uri.pathname == "/facicon.ico") { + var tmp = uri.href; + + event.respondWith( + fetch("https://Cyclone2.jimmynuetron.repl.co/service/"+tmp) + ) + } + }); \ No newline at end of file diff --git a/static/cyclone.js b/static/cyclone.js new file mode 100644 index 0000000..bf829f8 --- /dev/null +++ b/static/cyclone.js @@ -0,0 +1,296 @@ +class Cyclone { + constructor() { + this.tmp = location.pathname.split('/service')[1] + + this.tmp = this.tmp.substring(1, this.tmp.length); + let re = /(http(s|):)/g + + //if (this.tmp.match(re)) { + this.tmp = this.tmp.replace("http://", '') + this.tmp = this.tmp.replace("https://", '') + this.tmp = this.tmp.replace("http:/", '') + this.tmp = this.tmp.replace("https:/", '') + this.tmp = location.protocol + "//" + this.tmp + + document._location = new URL(this.tmp); + + this.url = new URL(document._location.href); + + this.bareEndpoint = location.host + "/service"; + + if (this.url.pathname == "/") { + this.paths = ['/'] + } else { + this.paths = this.url.pathname.split('/') + } + this.host = 'https://' + this.url.host + + this.targetAttrs = ['href', 'src', 'action', 'srcdoc', 'srcset']; + + console.log("Cyclone Injected with paths of:", this.paths, this.url.pathname) + + /*const LocationHandler = { + get(target, prop, reciver) { + return loc[prop] + }, + set(target, prop, val) { + return 'hi' + } + } + document._location = new Proxy(LocationHandler, loc)*/ + } + + rewriteUrl(link) { + var rewritten; + + if (link.startsWith('https://') || link.startsWith('http://') || link.startsWith('//')) { + if (link.startsWith('//')) { + rewritten = 'https:' + link; + } else { + rewritten = link; + }; + } else { + if (link.startsWith('.')) { + let offset = 1; + if (link.startsWith('..')) { + offset = 2; + } + let file = link.substr(link.indexOf('.') + 1 + offset, link.length) + + rewritten = this.url.hostname + file + } else { + if (link.startsWith('/')) { + rewritten = this.host + link + } else { + rewritten = this.host + '/' + link; + } + } + } + + var exceptions = ['about:', 'mailto:', 'javascript:', 'data:'] + let needstowrite = true; + for (let i = 0; i < exceptions.length; i++) { + if (link.startsWith(exceptions[i])) { + needstowrite = false + } + } + + if (needstowrite) { + rewritten = location.protocol + '//' + this.bareEndpoint + '/' + rewritten + return rewritten; + } else { + return link; + } + } + + rewriteSrcset(sample) { + return sample.split(',').map(e => { + return (e.split(' ').map(a => { + if (a.startsWith('http') || (a.startsWith('/') && !a.startsWith(this.prefix))) { + var url = this.rewriteUrl(a) + } + return a.replace(a, (url || a)) + }).join(' ')) + }).join(',') + } + } + + function rewriteJavascript(js) { + var javascript = js.replace('window.location', 'document._dlocation') + javascript = javascript.replace('document.location', 'document._dlocation') + javascript = javascript.replace('location.', 'document._location.') + return javascript + } + + class HTMLRewriter extends Cyclone { + rewriteElement(element) { + var targetAttrs = this.targetAttrs; + const attrs = [...element.attributes].reduce((attrs, attribute) => { + attrs[attribute.name] = attribute.value; + return attrs; + }, {}); + + var elementAttributes = []; + + for (var i = 0; i < targetAttrs.length; i++) { + var attr = targetAttrs[i] + var attrName = Object.keys(attrs)[i]; + var data = { + name: attr, + value: element.getAttribute('data-origin-' + attr) || element.getAttribute(attr) + } + if (data.value) { + elementAttributes.push(data); + } + + if (element.nonce) { + element.setAttribute('nononce', element.nonce) + element.removeAttribute('nonce') + } + if (element.integrity) { + element.setAttribute('nointegrity', element.integrity) + element.removeAttribute('integrity') + } + + if (element.tagName == "script") { + element.innerHTML = rewriteJavascript(element.innerHTML); + } + } + + for (var i = 0; i < elementAttributes.length; i++) { + var attr = elementAttributes[i] + var attrName = attr.name; + var value = attr.value; + + var bareValue = this.rewriteUrl(value); + if (attrName == "srcset") { + this.rewriteSrcset(value); + } + + element.setAttribute(attrName, bareValue); + element.setAttribute("data-origin-" + attrName, value); + } + } + + rewriteDocument() { + var docElements = document.querySelectorAll('*'); + for (var i = 0; i < docElements.length; i++) { + var element = docElements[i]; + + this.rewriteElement(element) + } + } + + rewriteiFrame(iframe) { + var frameDoc = (iframe.contentWindow || iframe.contentDocument || iframe.document); + + let tags = frameDoc.querySelectorAll('*') + + for (var i = 0; i < tags.length; i++) { + var tag = tags[i] + this.rewriteElement(tag) + } + } + } + + + const cyclone = new Cyclone(); + + const htmlRewriter = new HTMLRewriter(); + + const FetchIntercept = window.fetch; + window.fetch = async (...args) => { + let [resource, config] = args; + resource = cyclone.rewriteUrl(resource); + + const response = await FetchIntercept(resource, config); + return response; + } + + const MessageIntercept = window.postMessage; + + window.postMessage = (...args) => { + let [message, target, config] = args; + target = cyclone.rewriteUrl(target); + + const response = MessageIntercept(message, target, config); + return response; + } + + var CWOriginal = Object.getOwnPropertyDescriptor(window.HTMLIFrameElement.prototype, 'contentWindow') + + Object.defineProperty(window.HTMLIFrameElement.prototype, 'contentWindow', { + get() { + var iWindow = CWOriginal.get.call(this) + cyclone.rewriteiFrame(iWindow) + + return iWindow + }, + set() { + return false; + } + }) + + + const open = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function(method, url, ...rest) { + url = cyclone.rewriteUrl(url) + + return open.call(this, method, url, ...rest); + }; + + var oPush = window.history.pushState; + + function CycloneStates(obj, title, path) { + if (path.startsWith('/service/')) { + return; + } else { + var url = cyclone.rewriteUrl(path) + + oPush.apply(this, [obj, title, url]) + } + } + + window.history.pushState = CycloneStates + window.history.replaceState = CycloneStates + history.pushState = CycloneStates + history.replaceState = CycloneStates + + const OriginalWebsocket = window.WebSocket + const ProxiedWebSocket = function() { + const ws = new OriginalWebsocket(...arguments) + + const originalAddEventListener = ws.addEventListener + const proxiedAddEventListener = function() { + if (arguments[0] === "message") { + const cb = arguments[1] + arguments[1] = function() { + var origin = arguments[0].origin + arguments[0].origin = cyclone.rewriteUrl(origin); + + return cb.apply(this, arguments) + } + } + return originalAddEventListener.apply(this, arguments) + } + ws.addEventListener = proxiedAddEventListener + + Object.defineProperty(ws, "onmessage", { + set(func) { + return proxiedAddEventListener.apply(this, [ + "message", + func, + false + ]); + } + }); + return ws; + }; + + window.WebSocket = ProxiedWebSocket; + + const nwtb = window.open + function openNewTab(url, target, features) { + url = cyclone.rewriteUrl(url) + nwtb(url, target, features) + } + window.open = openNewTab + + htmlRewriter.rewriteDocument(); + setInterval(function() { + htmlRewriter.rewriteDocument(); + }, 10000) + + //For intercepting all requests + if (!document.serviceWorkerRegistered) { + if ('serviceWorker' in navigator) { + window.addEventListener('load', function() { + navigator.serviceWorker.register(location.origin + '/cySw.js').then(function(registration) { + console.log('Service worker registered with scope: ', registration.scope); + }, function(err) { + console.log('ServiceWorker registration failed: ', err); + }); + }); + } + document.serviceWorkerRegistered = true + } \ No newline at end of file diff --git a/static/resources/form.js b/static/resources/form.js index 5b84a09..6c5bc33 100644 --- a/static/resources/form.js +++ b/static/resources/form.js @@ -12,29 +12,43 @@ window.addEventListener('load', () => { // NOGG const useNoGG = false; + const proxy = localStorage.getItem("proxy") || "uv" + const form = document.querySelector('form'); form.addEventListener('submit', event => { event.preventDefault(); if (typeof navigator.serviceWorker === 'undefined') alert('Your browser does not support service workers or you are in private browsing!'); - + if (proxy == 'uv'){ navigator.serviceWorker.register('./sw.js', { scope: __uv$config.prefix }).then(() => { const value = event.target.firstElementChild.value; let url = value.trim(); - if (!isUrl(url)) - url = 'https://www.google.com/search?q=' + url; - else + if (!isUrl(url)) url = 'https://www.google.com/search?q=' + url; if (!(url.startsWith('https://') || url.startsWith('http://'))) url = 'http://' + url; - const redirectTo = __uv$config.prefix + __uv$config.encodeUrl(url); + let redirectTo = __uv$config.prefix + __uv$config.encodeUrl(url); const option = localStorage.getItem('nogg'); if (option === 'on') { stealthEngine(redirectTo); } else location.href = redirectTo; }); + } else if (proxy == 'cyclone') { + + const value = event.target.firstElementChild.value; + + let url = value.trim(); + if (!isUrl(url)) url = 'www.google.com/search?q=' + url; + if (!(url.startsWith('https://') || url.startsWith('http://'))) url = 'http://' + url; + let redirectTo = '/service/' + url; + const option = localStorage.getItem('nogg'); + if (option === 'on') { + stealthEngine(redirectTo); + } else location.href = redirectTo; + + } }); // NoGG Engine