215 lines
8.4 KiB
JavaScript
215 lines
8.4 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const axios = require('axios');
|
|
const Helpers = require('../helpers');
|
|
const qs = require('qs');
|
|
|
|
/**
|
|
* Super Stealth Proxy v3
|
|
* Engineered for high-fidelity dynamic site replication (YouTube, Google, etc.)
|
|
*/
|
|
|
|
const PROXY_PATH = '/api/proxy';
|
|
|
|
const proxyHandler = Helpers.wrapAsync(async (req, res) => {
|
|
let targetUrl = req.query.url;
|
|
|
|
// 1. URL Resolution
|
|
if (!targetUrl || !targetUrl.startsWith('http')) {
|
|
const originalUrl = req.originalUrl;
|
|
if (originalUrl.includes(PROXY_PATH + '/')) {
|
|
targetUrl = originalUrl.split(PROXY_PATH + '/')[1];
|
|
} else if (req.headers.referer && req.headers.referer.includes(PROXY_PATH)) {
|
|
try {
|
|
const refUrl = new URL(req.headers.referer);
|
|
const parentTarget = refUrl.searchParams.get('url');
|
|
if (parentTarget) {
|
|
const relativePath = req.url.replace(PROXY_PATH, '');
|
|
targetUrl = new URL(relativePath, parentTarget).href;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
if (!targetUrl || !targetUrl.startsWith('http')) {
|
|
return res.status(400).send('Invalid target URL.');
|
|
}
|
|
|
|
try {
|
|
const headers = {};
|
|
const forwardHeaders = ['accept', 'accept-language', 'range', 'authorization', 'content-type'];
|
|
forwardHeaders.forEach(h => { if (req.headers[h]) headers[h] = req.headers[h]; });
|
|
|
|
// Force Desktop User-Agent
|
|
headers['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36';
|
|
|
|
try {
|
|
const targetObj = new URL(targetUrl);
|
|
headers['referer'] = targetObj.origin + '/';
|
|
headers['origin'] = targetObj.origin;
|
|
headers['host'] = targetObj.host;
|
|
} catch(e) {}
|
|
|
|
const axiosConfig = {
|
|
url: targetUrl,
|
|
method: req.method,
|
|
headers: headers,
|
|
responseType: 'arraybuffer',
|
|
validateStatus: () => true,
|
|
maxRedirects: 5,
|
|
timeout: 30000,
|
|
maxContentLength: Infinity,
|
|
maxBodyLength: Infinity,
|
|
decompress: true
|
|
};
|
|
|
|
if (req.method !== 'GET' && req.body) {
|
|
axiosConfig.data = (headers['content-type'] || '').includes('form') ? qs.stringify(req.body) : req.body;
|
|
}
|
|
|
|
const response = await axios(axiosConfig);
|
|
const contentType = response.headers['content-type'] || '';
|
|
const finalUrl = response.request.res ? response.request.res.responseUrl : targetUrl;
|
|
|
|
// Handle Redirects
|
|
if (response.status >= 300 && response.status < 400 && response.headers.location) {
|
|
const redirUrl = new URL(response.headers.location, finalUrl).href;
|
|
return res.redirect(`${PROXY_PATH}?url=${encodeURIComponent(redirUrl)}`);
|
|
}
|
|
|
|
// Strip Security Headers
|
|
const forbiddenHeaders = ['x-frame-options', 'content-security-policy', 'content-security-policy-report-only', 'strict-transport-security', 'set-cookie'];
|
|
Object.keys(response.headers).forEach(key => {
|
|
if (!forbiddenHeaders.includes(key.toLowerCase())) {
|
|
res.setHeader(key, response.headers[key]);
|
|
}
|
|
});
|
|
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('X-Proxy-Target', finalUrl);
|
|
|
|
if (contentType.includes('text/html')) {
|
|
let html = response.data.toString('utf-8');
|
|
|
|
const injection = `
|
|
<script>
|
|
(function() {
|
|
const TARGET_URL = "${finalUrl}";
|
|
const PROXY_BASE = window.location.origin + "${PROXY_PATH}?url=";
|
|
|
|
function wrapUrl(u) {
|
|
if (!u || typeof u !== 'string' || u.startsWith('data:') || u.startsWith('javascript:') || u.startsWith('blob:')) return u;
|
|
if (u.startsWith(window.location.origin)) return u;
|
|
try {
|
|
const abs = new URL(u, TARGET_URL).href;
|
|
return PROXY_BASE + encodeURIComponent(abs);
|
|
} catch(e) { return u; }
|
|
}
|
|
|
|
// --- Network Virtualization ---
|
|
const _fetch = window.fetch;
|
|
window.fetch = (input, init) => {
|
|
if (typeof input === 'string') input = wrapUrl(input);
|
|
else if (input instanceof Request) {
|
|
Object.defineProperty(input, 'url', { value: wrapUrl(input.url) });
|
|
}
|
|
return _fetch(input, init);
|
|
};
|
|
|
|
const _open = XMLHttpRequest.prototype.open;
|
|
XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
|
|
return _open.apply(this, [method, wrapUrl(url), async, user, password]);
|
|
};
|
|
|
|
// --- Location & History Spoofing ---
|
|
const targetOrigin = new URL(TARGET_URL).origin;
|
|
const locationProxy = new Proxy(window.location, {
|
|
get: (t, p) => {
|
|
if (p === 'href') return TARGET_URL;
|
|
if (p === 'origin') return targetOrigin;
|
|
if (p === 'host' || p === 'hostname') return new URL(TARGET_URL).hostname;
|
|
const val = t[p];
|
|
return typeof val === 'function' ? val.bind(t) : val;
|
|
},
|
|
set: (t, p, v) => {
|
|
if (p === 'href') window.location.href = wrapUrl(v);
|
|
else t[p] = v;
|
|
return true;
|
|
}
|
|
});
|
|
|
|
try {
|
|
Object.defineProperty(window, 'location', { value: locationProxy, configurable: true });
|
|
Object.defineProperty(document, 'location', { value: locationProxy, configurable: true });
|
|
} catch(e) {}
|
|
|
|
const _pushState = history.pushState;
|
|
history.pushState = (state, title, url) => {
|
|
if (url) window.parent.postMessage({ type: 'proxy-url-change', url: new URL(url, TARGET_URL).href }, '*');
|
|
return _pushState.apply(history, [state, title, url ? wrapUrl(url) : url]);
|
|
};
|
|
|
|
const _replaceState = history.replaceState;
|
|
history.replaceState = (state, title, url) => {
|
|
if (url) window.parent.postMessage({ type: 'proxy-url-change', url: new URL(url, TARGET_URL).href }, '*');
|
|
return _replaceState.apply(history, [state, title, url ? wrapUrl(url) : url]);
|
|
};
|
|
|
|
// --- Attribute Rewriting (Real-time) ---
|
|
const observer = new MutationObserver(mutations => {
|
|
document.querySelectorAll('a:not([data-proxied]), form:not([data-proxied])').forEach(el => {
|
|
const attr = el.tagName === 'A' ? 'href' : 'action';
|
|
const val = el.getAttribute(attr);
|
|
if (val && !val.startsWith('#') && !val.startsWith('javascript:')) {
|
|
el.setAttribute(attr, wrapUrl(val));
|
|
el.setAttribute('data-proxied', 'true');
|
|
}
|
|
});
|
|
document.querySelectorAll('img:not([data-proxied]), script:not([data-proxied]), link:not([data-proxied])').forEach(el => {
|
|
const attr = el.tagName === 'LINK' ? 'href' : 'src';
|
|
const val = el.getAttribute(attr);
|
|
if (val && !val.startsWith('data:') && !val.startsWith('blob:')) {
|
|
el.setAttribute(attr, wrapUrl(val));
|
|
el.setAttribute('data-proxied', 'true');
|
|
}
|
|
});
|
|
});
|
|
observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
|
|
// --- Form Interception ---
|
|
window.addEventListener('submit', e => {
|
|
const form = e.target;
|
|
if (form.method.toLowerCase() === 'get') {
|
|
e.preventDefault();
|
|
const action = new URL(form.getAttribute('action') || '', TARGET_URL).href;
|
|
const params = new URLSearchParams(new FormData(form)).toString();
|
|
window.location.href = wrapUrl(action + (action.includes('?') ? '&' : '?') + params);
|
|
}
|
|
}, true);
|
|
|
|
})();
|
|
</script>
|
|
`;
|
|
html = html.replace(/<head>/i, `<head>${injection}`);
|
|
|
|
// Initial static rewrite
|
|
html = html.replace(/(href|src|action)=["']((?!data:|javascript:|#|blob:).*?)["']/gi, (m, a, v) => {
|
|
try {
|
|
return `${a}="${PROXY_BASE}${encodeURIComponent(new URL(v, finalUrl).href)}"`;
|
|
} catch(e) { return m; }
|
|
});
|
|
|
|
res.send(html);
|
|
} else {
|
|
res.send(response.data);
|
|
}
|
|
} catch (e) {
|
|
res.status(500).send("Proxy Bridge Failure: " + e.message);
|
|
}
|
|
});
|
|
|
|
router.all('/', proxyHandler);
|
|
router.all('/:any*', proxyHandler);
|
|
|
|
module.exports = router;
|