<?php

/*
ET-Chat Copyright by SEDesign | ET-Chat v3.x.x Lizenz: CCPL | Autor: Evgeni Tcherkasski (info@s-e-d.de)
ET-Chat_T-FISH-MOD Weiterentwicklungen, Optimierungen und Ergänzungen am Code sind das geistige Eigentum von Tommy (Thomas Kuhn) und Harlekin (Bernd Witte).

Die darauf folgende Optimierungen und Ergänzungen am Code sind das Geistige Eigentum von Powermen (Christian K.) 
PHP 8/9 Ready, XML Parser, PDO Prepared Statements Get Set sicherheit usw.
*/

class CheckUserName extends DbConectionMaker
{
    public object $lang;
    protected bool $user_application;
    protected string $username = '';
    protected string $gender = 'n';

    // ========== VALIDIERUNGSMETHODEN ==========
	private function validateUsernameFormat(string $username): bool {
		// Erlaubte Zeichen: Buchstaben, Zahlen, Leerzeichen (innen), & @ . _ - + = # ~ € $ % §
		if (preg_match('/^[\p{L}\p{N}\s&@\._\-+=#~€$%§]+$/u', $username) !== 1) {
			return false;
		}
    
		// Prüfung auf maximal 3 Sonderzeichen aus der erlaubten Gruppe
		if (preg_match_all('/[&@\._\-+=#~€$%§]/u', $username, $matches) > 3) {
			return false;
		}
    
		return true;
	}

    private function hasDangerousPatterns(string $username): bool {
        $dangerous = [
            '../', '..\\', './', '.\\',
            '<!--', '-->', '<script', '</script',
            '<?php', '?>', '<%', '%>',
            'javascript:', 'vbscript:', 'onclick=', 'onload=',
            'union select', 'or 1=1', 'drop table', 'delete from',
            '--', '/*', '*/', ';--', "' or '", '" or "'
        ];
        
        $lowerUser = strtolower($username);
        foreach ($dangerous as $pattern) {
            if (strpos($lowerUser, strtolower($pattern)) !== false) {
                return true;
            }
        }
        return false;
    }

    private function loadBannedWordsFromJson(): array {
        $jsonFile = dirname(__DIR__) . '/cache/verbotene_user_namen.json';
        
        if (!file_exists($jsonFile)) {
            return [];
        }
        
        $content = file_get_contents($jsonFile);
        $words = json_decode($content, true);
        
        if (json_last_error() !== JSON_ERROR_NONE || !is_array($words)) {
            return [];
        }
        
        return $words;
    }

    private function isUsernameAllowed(string $username, array $bannedWords): bool {
        $usernameNorm = mb_strtolower($username, 'UTF-8');
        
        foreach ($bannedWords as $banned) {
            if (empty(trim($banned))) {
                continue;
            }
            
            $bannedNorm = mb_strtolower($banned, 'UTF-8');
            
            // Exakter Match
            if ($usernameNorm === $bannedNorm) {
                return false;
            }
            
            // Teilwort-Match (multibyte-sicher)
            if (mb_strpos($usernameNorm, $bannedNorm, 0, 'UTF-8') !== false) {
                return false;
            }
        }
        return true;
    }
    // ========== ENDE VALIDIERUNGSMETHODEN ==========

    public function __construct(bool $user_application = false, string $username = '', string $gender = '')
    {
        parent::__construct();

        // Session starten
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }

        // HTTP Header
        if (!headers_sent()) {
            header('Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0');
            if (!$user_application) {
                header('Content-Type: application/json; charset=utf-8');
            }
        }

        $this->user_application = $user_application;

        // POST-Werte nur nehmen, wenn keine Anwendung
        if (!$user_application) {
            $this->username = trim($_POST['username'] ?? '');
            $this->gender = trim($_POST['gender'] ?? 'n');
        } else {
            $this->username = $username;
            $this->gender = $gender ?: 'n';
        }

        // Config in Session setzen, alte Logs bereinigen
        if ($user_application) {
            $this->configTabData2Session();

            $this->dbObj->sqlSet("DELETE FROM {$this->_prefix}etchat_messages WHERE etchat_timestamp < ?", [
                time() - (($_SESSION['etchat_'.$this->_prefix.'loeschen_nach'] ?? 0) * 3600 * 24)
            ]);

            $this->dbObj->sqlSet("DELETE FROM {$this->_prefix}etchat_blacklist WHERE etchat_blacklist_time < ?", [time()]);
            $this->dbObj->sqlSet("DELETE FROM {$this->_prefix}etchat_kick_user WHERE etchat_kicked_user_time < ?", [time()]);
            $this->dbObj->sqlSet("DELETE FROM {$this->_prefix}etchat_user 
                                  WHERE etchat_userprivilegien = 'gast' 
                                  AND etchat_logintime < ?", 
                                  [time() - (($_SESSION['etchat_'.$this->_prefix.'loeschen_nach'] ?? 0) * 3600 * 24 * 2)]);
        }

        // Cookie-Check
        if (!$user_application && !isset($_COOKIE[$this->_prefix.'cookie_test'])) {
            $this->errorMessage("Please activate your cookies.");
            return;
        }

        // Anti-Hack-Check
        if (!$user_application) {
            $setCheckKey = $_SESSION[$this->_prefix.'set_check'] ?? null;
            if (!$setCheckKey || !isset($_POST[$setCheckKey])) {
                $this->errorMessage("no hacks and bots");
                return;
            }
        }

        // Sprachobjekt laden
        $langObj = new LangXml();
        $this->lang = $langObj->getLang()->checkusername_php[0];

        // Login-Zähler
        if (!$user_application && !$this->loginCounter()) {
            return;
        }

        // Style-Dateien
        $style = basename($_SESSION['etchat_'.$this->_prefix.'style'] ?? 'default');
        $stylePath = "styles/{$style}/";
        $variablesFile = $stylePath . "variables.css";
        $styleFile = $stylePath . "style.css";

        $textcolor = null;
        $syscolor = null;

        if (file_exists($variablesFile)) {
            $content = file_get_contents($variablesFile);
            if (preg_match('/--\s*Textfarbe\s*:\s*#?([0-9a-f]{3,6})\s*;/i', $content, $m1)) $textcolor = strtolower($m1[1]);
            if (preg_match('/--\s*Systemfarbe\s*:\s*#?([0-9a-f]{3,6})\s*;/i', $content, $m2)) $syscolor = strtolower($m2[1]);
        }

        if (($textcolor === null || $syscolor === null) && file_exists($styleFile)) {
            foreach (file($styleFile) as $line) {
                $line = trim($line);
                if (preg_match('/^Textfarbe\s*:\s*#?([0-9a-f]{3,6})/i', $line, $m1)) $textcolor = strtolower($m1[1]);
                if (preg_match('/^Systemfarbe\s*:\s*#?([0-9a-f]{3,6})/i', $line, $m2)) $syscolor = strtolower($m2[1]);
            }
        }

        if ($textcolor !== null) $_SESSION['etchat_'.$this->_prefix.'textcolor'] = $textcolor;
        if ($syscolor !== null) $_SESSION['etchat_'.$this->_prefix.'syscolor'] = $syscolor;

        // Blacklist prüfen
        $blackListObj = new Blacklist($this->dbObj);
        if ($blackListObj->userInBlacklist()) {
            setcookie("cookie_anzahl_logins_in_XX_sek",1,["samesite"=>"lax"]);
            if (!$user_application) $this->errorMessage("blacklist");
            $blackListObj->killUserSession();
            if ($user_application) header('Location: ./?AfterBlacklistInsertion');
            return;
        }

        // Alte UserOnline Einträge löschen
        $reloadSeq = $_SESSION['etchat_'.$this->_prefix.'config_reloadsequenz'] ?? 1000;
        $this->dbObj->sqlSet("DELETE FROM {$this->_prefix}etchat_useronline WHERE etchat_onlinetimestamp < ?", [
            time() - ($reloadSeq / 1000 * 4)
        ]);

        // ========== ERWEITERTE USERNAME-VALIDIERUNG ==========
        // Grundlegende Prüfungen
        if (empty($this->username) || strlen($this->username) > 100) {
            $this->errorMessage("invalid username");
            return;
        }
        
        // Längenprüfung (4-25 Zeichen)
        if (strlen($this->username) < 4 || strlen($this->username) > 25) {
            $this->errorMessage("Der Benutzername muss zwischen 4 und 25 Zeichen lang sein.");
            return;
        }
        
		// Prüfung auf erlaubte Zeichen
		if (!$this->validateUsernameFormat($this->username)) {
			$this->errorMessage("Der Benutzername enthält nicht erlaubte Zeichen ODER mehr als 3 Sonderzeichen.\n\nErlaubt sind:\n- Buchstaben (A-Z, a-z, ÄÖÜäöüß)\n- Zahlen (0-9)\n- Leerzeichen (nur innerhalb)\n- Sonderzeichen: & @ . _ - + = # ~ € $ % § (maximal 3 davon)");
			return;
		}
        
        // Prüfung auf gefährliche Muster
        if ($this->hasDangerousPatterns($this->username)) {
            $this->errorMessage("Der Benutzername enthält ungültige Zeichenkombinationen.");
            return;
        }
        
        // Verbotene Wörter prüfen
        $bannedWords = $this->loadBannedWordsFromJson();
        if (!$this->isUsernameAllowed($this->username, $bannedWords)) {
            $this->errorMessage("Dieser Benutzername ist nicht erlaubt. Bitte wähle einen anderen Namen.");
            return;
        }
        
        // Prüfung auf mindestens einen Buchstaben oder Zahl (verhindert reinen Sonderzeichen-Namen)
        if (preg_match('/^[\s&@\._\-+=#~€$%§]+$/u', $this->username)) {
            $this->errorMessage("Der Benutzername muss mindestens einen Buchstaben oder eine Zahl enthalten.");
            return;
        }
        
        // Bereinigung
        $this->username = htmlspecialchars(str_replace("\\","/",preg_replace('/[\x00-\x1F\x90\x8F\x81\xA0\x9D]/', '', $this->username)), ENT_QUOTES, "UTF-8");
        // ========== ENDE ERWEITERTE VALIDIERUNG ==========

        if (!$user_application && $this->userInChatNow($this->username)) {
            $this->errorMessage($this->lang->name_busy[0]->tagData);
            return;
        }

        // User aus DB holen
        $user_exists = $this->dbObj->sqlGet(
            "SELECT etchat_user_id, etchat_username, etchat_userpw, etchat_userprivilegien 
             FROM {$this->_prefix}etchat_user 
             WHERE etchat_username = ? 
             ORDER BY etchat_userpw DESC",
            [$this->username]
        );

        $pw = $_POST['pw'] ?? '';
        $userCheckerAndInserterObj = new UserCheckerAndInserter(
            $this->dbObj,
            $user_exists,
            $this->username,
            $pw,
            preg_replace("/[^a-z]/i", "n", $this->gender),
            $this->lang
        );

        if ($userCheckerAndInserterObj->status == 1) {
            $this->messageOnEnter();
        } else {
            $this->errorMessage($userCheckerAndInserterObj->status);
        }
    }

    private function loginCounter(): bool
    {
        $cookieLast = $_COOKIE['cookie_last_login'] ?? null;
        $cookieCount = (int)($_COOKIE['cookie_anzahl_logins_in_XX_sek'] ?? 0);

        if ($cookieLast === null) {
            setcookie("cookie_last_login", time(), ["samesite" => "lax"]);
            setcookie("cookie_anzahl_logins_in_XX_sek", 1, ["samesite" => "lax"]);
            return true;
        }

        if (time() - (int)$cookieLast < 180) {
            $cookieCount++;
            setcookie("cookie_anzahl_logins_in_XX_sek", $cookieCount, ["samesite" => "lax"]);
            if ($cookieCount > ($this->_limit_logins_in_three_minutes - 1)) {
                setcookie("cookie_last_login", time(), ["samesite" => "lax"]);
                $this->errorMessage($this->lang->many_logins[0]->tagData);
                return false;
            }
            return true;
        }

        setcookie("cookie_last_login", time(), ["samesite" => "lax"]);
        setcookie("cookie_anzahl_logins_in_XX_sek", 1, ["samesite" => "lax"]);
        return true;
    }

    private function userInChatNow(string $user): bool
    {
        $res = $this->dbObj->sqlGet(
            "SELECT etchat_username 
             FROM {$this->_prefix}etchat_useronline uo
             JOIN {$this->_prefix}etchat_user u ON u.etchat_user_id = uo.etchat_onlineuser_fid
             WHERE u.etchat_username = ?
             LIMIT 1",
            [$user]
        );
        if (is_array($res) && count($res) > 0) {
            setcookie("cookie_anzahl_logins_in_XX_sek",1,["samesite"=>"lax"]);
            return true;
        }
        return false;
    }

    private function errorMessage(string $message): bool
    {
        echo $message;
        $this->dbObj->close();
        return false;
    }

    private function messageOnEnter(): bool
    {
        new RoomEntrance($this->dbObj, $this->lang);
        unset($_SESSION[$this->_prefix.'set_check']);
        $this->dbObj->close();

        if (!$this->user_application) {
            echo "1";
        } else {
            header('Location: ./?Chat');
        }

        return true;
    }
}
?>