import http from 'http';
const PORT = process.env.PORT || 7860;
// Anti-crash global obligatoire
process.on('uncaughtException', (err) => console.error('CRASH ÉVITÉ:', err));
process.on('unhandledRejection', (err) => console.error('PROMESSE REFUSÉE:', err));
const LANDING_PAGE = `
Universal Smart Proxy
Proxy Universel
Entre n'importe quelle URL (Ex: http://gamingserver.fr:25578 ou https://google.com)
`;
function parseCookies(cookieHeader) {
const list = {};
if (!cookieHeader) return list;
cookieHeader.split(';').forEach(cookie => {
const parts = cookie.split('=');
list[parts.shift().trim()] = decodeURIComponent(parts.join('='));
});
return list;
}
function resolveTarget(req, reqUrl) {
let target = reqUrl.searchParams.get('url');
if (target) return { source: 'query', url: target };
if (req.headers.referer) {
try {
const refUrl = new URL(req.headers.referer);
let refTarget = refUrl.searchParams.get('url');
if (refTarget) {
const base = new URL(refTarget);
return { source: 'referer', url: new URL(req.url, base.origin).href };
}
} catch (e) {}
}
const cookies = parseCookies(req.headers.cookie);
if (cookies.proxy_last_origin) {
try {
const baseOrigin = decodeURIComponent(cookies.proxy_last_origin);
return { source: 'cookie', url: new URL(req.url, baseOrigin).href };
} catch (e) {}
}
return null;
}
const server = http.createServer(async (req, res) => {
const host = req.headers.host || '127.0.0.1';
if (req.url === '/favicon.ico') {
res.writeHead(204);
return res.end();
}
const reqUrl = new URL(req.url, `http://${host}`);
const targetData = resolveTarget(req, reqUrl);
if (!targetData) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return res.end(LANDING_PAGE);
}
let targetUrlStr = targetData.url;
try {
if (!targetUrlStr.startsWith('http://') && !targetUrlStr.startsWith('https://')) {
targetUrlStr = 'https://' + targetUrlStr;
}
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_last_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);
}
body = Buffer.concat(buffers);
}
const response = await fetch(targetUrl.href, {
method: req.method,
headers: headers,
body: body,
redirect: 'manual'
});
const contentType = response.headers.get('content-type') || '';
const isHtml = contentType.includes('text/html');
// Base des en-têtes système à nettoyer
const stripHeaders = [
'connection', 'keep-alive', 'transfer-encoding',
'content-encoding', 'set-cookie', 'location',
'content-security-policy', 'strict-transport-security'
];
const resHeaders = {};
response.headers.forEach((value, key) => {
const lowerKey = key.toLowerCase();
if (!stripHeaders.includes(lowerKey)) {
// CORRECTION FIX TÉLÉCHARGEMENT : On ne supprime la taille QUE si c'est du HTML modifié
if (lowerKey === 'content-length' && isHtml) return;
resHeaders[key] = value;
}
});
const setCookies = [`proxy_last_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).href;
resHeaders['Location'] = `/?url=${encodeURIComponent(absoluteLocation)}`;
}
res.writeHead(response.status, resHeaders);
return res.end();
}
if (isHtml) {
let text = await response.text();
text = text.replace(/(href|src)=["'](https?:\/\/[^"']+)["']/g, (match, attr, url) => {
return `${attr}="/?url=${encodeURIComponent(url)}"`;
});
const bodyBuffer = Buffer.from(text, 'utf-8');
resHeaders['content-length'] = bodyBuffer.length;
res.writeHead(response.status, resHeaders);
return res.end(bodyBuffer);
}
// CORRECTION FLUX : Boucle d'écriture directe paquet par paquet pour éviter les pertes sur les fichiers/téléchargements
res.writeHead(response.status, resHeaders);
if (response.body) {
try {
for await (const chunk of response.body) {
res.write(chunk);
}
} catch (streamErr) {
console.error("Erreur durant le transfert du fichier:", streamErr.message);
}
}
res.end();
} catch (err) {
console.error(`[Erreur Cible] ${targetUrlStr}:`, err.message);
if (!res.headersSent) {
res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(`Erreur Proxy : Impossible de joindre la cible.`);
}
}
});
server.on('upgrade', (req, socket, head) => {
const host = req.headers.host || '127.0.0.1';
const reqUrl = new URL(req.url, `http://${host}`);
const targetData = resolveTarget(req, reqUrl);
if (!targetData) {
socket.end();
return;
}
const target = new URL(targetData.url);
console.log(`[WS Universel] Connexion injectée vers : ${target.host}`);
const options = {
hostname: target.hostname,
port: target.port || (target.protocol === 'https:' ? 443 : 80),
method: req.method,
path: req.url,
headers: { ...req.headers }
};
options.headers['host'] = target.host;
if (options.headers['origin']) options.headers['origin'] = target.origin;
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(`Proxy Multi-Site Intelligent & Téléchargements corrigés sur le port ${PORT}`);
});