import http from 'http';
import { Readable } from 'stream';
const PORT = process.env.PORT || 7860;
process.on('uncaughtException', (err) => console.error('CRASH ÉVITÉ:', err));
process.on('unhandledRejection', (err) => console.error('PROMESSE REFUSÉE:', err));
const trafficStats = {};
function getClientStats(req) {
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress || '127.0.0.1';
if (!trafficStats[ip]) {
trafficStats[ip] = { up: 0, down: 0 };
}
return trafficStats[ip];
}
const BROWSER_HOOK_SCRIPT = `
`;
const LANDING_PAGE = `
CyberProxy Pro
`;
function rewriteCss(css, targetUrl) {
const proto = targetUrl.protocol.replace(':', '');
let rewritten = css.replace(/url\(['"]?(https?:\/\/[^'")]+)['"]?\)/g, (match, url) => {
try {
const u = new URL(url);
return `url(/p/${u.protocol.replace(':', '')}/${u.host}${u.pathname}${u.search})`;
} catch(e) { return match; }
});
rewritten = rewritten.replace(/url\(['"]?\/((?!p\/)[^'")]+)['"]?\)/g, (match, path) => {
return `url(/p/${proto}/${targetUrl.host}/${path})`;
});
return rewritten;
}
const server = http.createServer(async (req, res) => {
const clientStats = getClientStats(req);
// --- PRIORITÉ ABSOLUE AUX ROUTES INTERNES ---
if (req.url === '/' || req.url === '') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return res.end(LANDING_PAGE);
}
if (req.url === '/proxy-stats') {
res.writeHead(200, {
'Content-Type': 'application/json',
'Cache-Control': 'no-store, max-age=0'
});
return res.end(JSON.stringify({
up: (clientStats.up / (1024 * 1024)).toFixed(2),
down: (clientStats.down / (1024 * 1024)).toFixed(2)
}));
}
// --- LOGIQUE DU PROXY ---
let targetUrlStr = null;
if (req.url.startsWith('/p/')) {
const match = req.url.match(/^\/p\/([^/]+)\/([^/]+)(.*)$/);
if (match) {
const [_, proto, domain, path] = match;
targetUrlStr = `${proto}://${domain}${path}`;
}
} else if (req.headers.referer) {
try {
const refUrl = new URL(req.headers.referer);
if (refUrl.pathname.startsWith('/p/')) {
const match = refUrl.pathname.match(/^\/p\/([^/]+)\/([^/]+)/);
if (match) {
targetUrlStr = `${match[1]}://${match[2]}${req.url}`;
}
}
} catch (e) {}
}
if (!targetUrlStr && req.headers.cookie) {
const match = req.headers.cookie.match(/proxy_fallback_origin=([^;]+)/);
if (match) targetUrlStr = decodeURIComponent(match[1]) + req.url;
}
if (!targetUrlStr) {
res.writeHead(404);
return res.end();
}
try {
const targetUrl = new URL(targetUrlStr);
const headers = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
if (!['host', 'cookie', 'accept-encoding', 'connection', 'origin', 'referer'].includes(key.toLowerCase())) {
headers.set(key, value);
}
}
headers.set('Host', targetUrl.host);
headers.set('User-Agent', req.headers['user-agent'] || 'Mozilla/5.0');
if (req.headers['origin']) headers.set('Origin', targetUrl.origin);
if (req.headers['referer']) headers.set('Referer', targetUrl.origin);
if (req.headers.cookie) {
const cleanCookies = req.headers.cookie.split(';').map(c => c.trim()).filter(c => !c.startsWith('proxy_fallback_origin=')).join('; ');
if (cleanCookies) headers.set('Cookie', cleanCookies);
}
let body = null;
if (req.method !== 'GET' && req.method !== 'HEAD') {
const buffers = [];
for await (const chunk of req) {
buffers.push(chunk);
clientStats.up += chunk.length;
}
body = Buffer.concat(buffers);
}
const response = await fetch(targetUrl.href, {
method: req.method,
headers: headers,
body: body,
redirect: 'manual'
});
const stripHeaders = ['connection', 'keep-alive', 'transfer-encoding', 'content-encoding', 'location', 'content-security-policy', 'strict-transport-security', 'content-length'];
const resHeaders = {};
response.headers.forEach((value, key) => {
if (!stripHeaders.includes(key.toLowerCase())) resHeaders[key] = value;
});
const setCookies = [`proxy_fallback_origin=${encodeURIComponent(targetUrl.origin)}; Path=/; HttpOnly; SameSite=Lax`];
const originalSetCookies = response.headers.getSetCookie ? response.headers.getSetCookie() : [];
originalSetCookies.forEach(c => setCookies.push(c));
resHeaders['Set-Cookie'] = setCookies;
if (response.status >= 300 && response.status < 400) {
let location = response.headers.get('location');
if (location) {
const absoluteLocation = new URL(location, targetUrl.href);
resHeaders['Location'] = `/p/${absoluteLocation.protocol.replace(':', '')}/${absoluteLocation.host}${absoluteLocation.pathname}${absoluteLocation.search}`;
}
res.writeHead(response.status, resHeaders);
return res.end();
}
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('text/html')) {
let text = await response.text();
text = text.replace('', '' + BROWSER_HOOK_SCRIPT);
text = text.replace(/(href|src)=["'](https?:\/\/)([^"']+)["']/g, (m, attr, proto, rest) => `${attr}="/p/${proto.replace('://', '')}/${rest}"`);
text = text.replace(/(href|src)=["']\/((?!p\/)[^"']+)["']/g, (m, attr, path) => `${attr}="/p/${targetUrl.protocol.replace(':', '')}/${targetUrl.host}/${path}"`);
const bodyBuffer = Buffer.from(text, 'utf-8');
clientStats.down += bodyBuffer.length;
resHeaders['content-length'] = bodyBuffer.length;
resHeaders['content-type'] = 'text/html; charset=utf-8';
res.writeHead(response.status, resHeaders);
return res.end(bodyBuffer);
}
if (contentType.includes('text/css')) {
let text = await response.text();
text = rewriteCss(text, targetUrl);
const bodyBuffer = Buffer.from(text, 'utf-8');
clientStats.down += bodyBuffer.length;
resHeaders['content-length'] = bodyBuffer.length;
res.writeHead(response.status, resHeaders);
return res.end(bodyBuffer);
}
res.writeHead(response.status, resHeaders);
if (response.body) {
const stream = Readable.fromWeb(response.body);
stream.on('data', (chunk) => { clientStats.down += chunk.length; });
stream.on('error', () => res.end());
stream.pipe(res);
} else { res.end(); }
} catch (err) {
if (!res.headersSent) {
res.writeHead(502);
res.end(`Erreur de communication.`);
}
}
});
server.on('upgrade', (req, socket, head) => {
let targetUrlStr = null;
if (req.url.startsWith('/p/')) {
const match = req.url.match(/^\/p\/([^/]+)\/([^/]+)(.*)$/);
if (match) targetUrlStr = `${match[1] === 'https' ? 'wss' : 'ws'}://${match[2]}${match[3]}`;
}
if (!targetUrlStr) return socket.end();
const target = new URL(targetUrlStr);
const options = {
hostname: target.hostname,
port: target.port || (target.protocol === 'wss:' ? 443 : 80),
method: req.method,
path: req.url,
headers: { ...req.headers, host: target.host }
};
const proxyReq = http.request(options);
proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => {
socket.write('HTTP/1.1 101 Switching Protocols\r\n');
for (const [key, value] of Object.entries(proxyRes.headers)) socket.write(`${key}: ${value}\r\n`);
socket.write('\r\n');
proxySocket.write(proxyHead);
proxySocket.pipe(socket);
socket.pipe(proxySocket);
});
proxyReq.on('error', () => socket.end());
proxyReq.end();
});
server.listen(PORT, '0.0.0.0', () => console.log(`CyberProxy Pro actif sur le port ${PORT}`));