#!/usr/bin/env python3
"""
Proxy2 - IPFS Server (с полной пересылкой данных)
"""

import socket
import threading
import binascii
import subprocess
import time
import re

# ==================== КОНФИГУРАЦИЯ ====================
LISTEN_PORT = 8222
EXTERNAL_IP = "192.168.1.160"  # IP вашей VMware

HTTP_PREFIX = b"GET /wiki/ HTTP/1.1\r\nHost: ru.euwiki.ru\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\nAccept: text/html\r\nAccept-Language: ru-RU,ru;q=0.9\r\nAccept-Encoding: gzip, deflate, br\r\nConnection: keep-alive\r\n\r\n"
SEPARATOR = b"\n---HEX---\n"

# ==================== ФУНКЦИИ ====================

def parse_request(data: bytes) -> tuple:
    """Парсит HTTP-запрос, возвращает (host, port, is_connect)"""
    try:
        text = data.decode('ascii', errors='ignore')
        
        # CONNECT запрос
        match = re.search(r'CONNECT\s+([^\s:]+)(?::(\d+))?', text, re.IGNORECASE)
        if match:
            host = match.group(1)
            port = int(match.group(2)) if match.group(2) else 443
            return (host, port, True)
        
        # Обычный HTTP запрос
        match = re.search(r'Host:\s*([^\r\n]+)', text, re.IGNORECASE)
        if match:
            host = match.group(1).strip()
            return (host, 80, False)
        
        return (None, None, False)
    except:
        return (None, None, False)

def extract_hex_data(data: bytes) -> bytes:
    """Извлекает и декодирует HEX данные из пакета с префиксом"""
    if data.startswith(HTTP_PREFIX):
        data = data[len(HTTP_PREFIX) + len(SEPARATOR):]
    
    # Декодируем все HEX строки
    result = b""
    lines = data.split(b'\n')
    for line in lines:
        line = line.strip()
        if line:
            try:
                result += binascii.unhexlify(line)
            except:
                pass
    return result

def encode_data(data: bytes) -> bytes:
    """Кодирует данные в HEX с префиксом"""
    hex_data = binascii.hexlify(data) + b"\n"
    return HTTP_PREFIX + SEPARATOR + hex_data

def forward_data(src, dst, prefix_expected=True):
    """Пересылает данные из src в dst с декодированием/кодированием"""
    try:
        while True:
            chunk = src.recv(8192)
            if not chunk:
                break
            
            if prefix_expected:
                # Данные от клиента (с префиксом) > интернет (без префикса)
                decoded = extract_hex_data(chunk)
                if decoded:
                    dst.send(decoded)
            else:
                # Данные от интернета (без префикса) > клиент (с префиксом)
                encoded = encode_data(chunk)
                dst.send(encoded)
    except:
        pass

def handle_tunnel(client_sock, addr):
    """Обрабатывает P2P-соединение от IPFS"""
    internet_sock = None
    try:
        # Получаем первый запрос
        data = client_sock.recv(8192)
        if not data:
            return
        
        print(f"[*] {addr} Received {len(data)} bytes")
        
        # Извлекаем и декодируем запрос
        request = extract_hex_data(data)
        if not request:
            print(f"[!] {addr} Failed to decode request")
            return
        
        # Парсим запрос
        host, port, is_connect = parse_request(request)
        if not host:
            print(f"[!] {addr} Failed to parse request")
            return
        
        print(f"[*] {addr} REQUEST > {host}:{port} (CONNECT={is_connect})")
        
        # Подключаемся к целевому серверу
        internet_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        internet_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        if EXTERNAL_IP:
            internet_sock.bind((EXTERNAL_IP, 0))
        internet_sock.settimeout(30)
        internet_sock.connect((host, port))
        internet_sock.settimeout(None)
        
        # Если это CONNECT, отправляем ответ браузеру
        if is_connect:
            response = b"HTTP/1.1 200 Connection established\r\n\r\n"
            encoded_response = encode_data(response)
            client_sock.send(encoded_response)
            print(f"[*] {addr} Sent 200 CONNECT response")
            
            # Теперь пересылаем данные в обе стороны
            # Данные от клиента (TLS) > интернет
            t1 = threading.Thread(target=forward_data, args=(client_sock, internet_sock, True))
            # Данные от интернета > клиенту (TLS)
            t2 = threading.Thread(target=forward_data, args=(internet_sock, client_sock, False))
            t1.daemon = True
            t2.daemon = True
            t1.start()
            t2.start()
            
            t1.join()
            t2.join()
        else:
            # Обычный HTTP запрос
            # Отправляем запрос в интернет
            internet_sock.send(request)
            
            # Получаем ответ и отправляем клиенту
            response = b""
            while True:
                chunk = internet_sock.recv(8192)
                if not chunk:
                    break
                response += chunk
            
            encoded_response = encode_data(response)
            client_sock.send(encoded_response)
        
    except Exception as e:
        print(f"[!] {addr} Error: {e}")
    finally:
        try:
            client_sock.close()
        except:
            pass
        try:
            if internet_sock:
                internet_sock.close()
        except:
            pass

def main():
    print("=" * 60)
    print("PROXY2 (Server) - IPFS P2P Mode (FIXED)")
    print("=" * 60)
    print(f"[*] External IP: {EXTERNAL_IP}")
    print(f"[*] Listening port: {LISTEN_PORT}")
    
    # Запускаем ipfs p2p listen
    cmd = ['ipfs', 'p2p', 'listen', '/x/http-proxy/1.0.0', f'/ip4/127.0.0.1/tcp/{LISTEN_PORT}']
    print(f"[*] Starting: {' '.join(cmd)}")
    listener_process = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    time.sleep(2)
    
    # Создаём TCP сервер для приёма соединений от IPFS
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('127.0.0.1', LISTEN_PORT))
    server.listen(100)
    print(f"[*] Waiting for P2P connections...")
    
    try:
        while True:
            client, addr = server.accept()
            client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            print(f"[+] P2P connection from {addr}")
            t = threading.Thread(target=handle_tunnel, args=(client, addr))
            t.daemon = True
            t.start()
    except KeyboardInterrupt:
        print("\n[*] Shutting down...")
    finally:
        server.close()
        listener_process.terminate()

if __name__ == "__main__":
    main()

# (c) by Valery Shmelev (Deutsche: Valery Shmeleff)  https://oflameron.com

