<?php
/**
 * APP 文件API
 * 复用前端文件API的实现，包含完整的安全防护
 */

require_once __DIR__ . '/../MPHX/common.php';

class AppFileAPI {
    protected $user;
    protected $bt_api;
    protected $bt_info;
    protected $site_root;
    protected $user_name;
    protected $DB;
    protected $conf;
    
    // 允许上传的文件扩展名白名单
    protected $allowed_extensions = array(
        'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico',
        'tiff', 'tif', 'raw', 'cr2', 'nef', 'arw', 'dng',
        'psd', 'ai', 'eps',
        'heic', 'heif',
        'mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm',
        'mp3', 'wav', 'ogg', 'flac', 'aac', 'wma', 'm4a',
        'txt', 'md', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
        'odt', 'ods', 'odp', 'rtf',
        'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz',
        'html', 'htm', 'css', 'js', 'json', 'xml', 'yaml', 'yml',
        'php', 'php3', 'php4', 'php5', 'php7', 'phtml',
        'asp', 'aspx', 'jsp', 'py', 'rb', 'pl',
        'sql', 'db', 'sqlite', 'mdb', 'accdb',
        'ttf', 'otf', 'woff', 'woff2', 'eot',
        'log', 'csv', 'htaccess', 'htpasswd'
    );
    
    // 危险扩展名黑名单（绝对禁止的可执行文件）
    protected $dangerous_extensions = array(
        'exe', 'com', 'bat', 'cmd', 'scr', 'msi',
        'dll', 'sys', 'drv', 'bin',
        'sh', 'bash', 'zsh', 'csh', 'tcsh',
        'reg', 'vbs', 'vbe', 'jse', 'wsf', 'wsh',
        'cgi'
    );
    
    public function __construct($user, $bt_api, $bt_info) {
        global $DB, $conf;
        $this->DB = $DB;
        $this->conf = $conf;
        $this->user = $user;
        $this->bt_api = $bt_api;
        $this->bt_info = $bt_info;
        
        // 获取用户名
        $username = isset($this->user['user']) ? $this->user['user'] : null;
        
        if (!$username) {
            $this->user_name = null;
            $this->site_root = null;
            return;
        }
        $this->user_name = $username;
        
        // 获取系统配置中的建站目录
        $linux_path = isset($this->conf['hxi']) && !empty($this->conf['hxi']) ? $this->conf['hxi'] : '/www/wwwroot';
        $windows_path = isset($this->conf['hxo']) && !empty($this->conf['hxo']) ? $this->conf['hxo'] : 'D:/wwwroot';
        
        // 根据操作系统选择建站目录
        $os_type = isset($this->bt_info['czzt']) ? $this->bt_info['czzt'] : 'Linux';
        $this->site_root = ($os_type == 'Linux' ? $linux_path : $windows_path) . '/' . $username;
    }
    
    /**
     * 验证文件扩展名是否允许上传
     */
    protected function isAllowedFile($filename) {
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        
        // 1. 检查是否在危险扩展名黑名单中
        if (in_array($ext, $this->dangerous_extensions)) {
            return false;
        }
        
        // 2. 检查是否在允许的白名单中
        if (!in_array($ext, $this->allowed_extensions)) {
            return false;
        }
        
        return true;
    }
    
    /**
     * 安全清理和验证路径
     */
    protected function sanitizePath($path) {
        // 1. 统一使用正斜杠
        $path = str_replace('\\', '/', $path);
        
        // 2. 解码URL编码（防止%2e%2e%2f绕过）
        $path = urldecode($path);
        
        // 3. 移除空字节（防止空字节截断）
        $path = str_replace("\0", '', $path);
        
        // 4. 移除双写绕过（....// -> ../）
        while (strpos($path, '../') !== false || strpos($path, '..\\') !== false) {
            $path = str_replace('../', '/', $path);
            $path = str_replace('..\\', '/', $path);
        }
        
        // 5. 移除 .. 结尾
        $path = preg_replace('#\.\.$#', '', $path);
        
        // 6. 规范化路径（移除多余的斜杠）
        $path = preg_replace('#/{2,}#', '/', $path);
        
        // 7. 确保以/开头
        $path = '/' . ltrim($path, '/');
        
        return $path;
    }
    
    /**
     * 验证路径是否在用户根目录下
     */
    protected function validatePath($full_path) {
        // 直接使用字符串比较，不使用realpath
        // 这样可以处理不存在的目录
        
        // 检查路径是否以用户根目录开头
        if (strpos($full_path, $this->site_root) === 0) {
            return true;
        }
        
        // 如果路径不以用户根目录开头，检查是否是相对路径
        if (strpos($full_path, '/') === 0) {
            // 绝对路径，但不以用户根目录开头
            return false;
        }
        
        // 相对路径，认为是合法的（在用户根目录下）
        return true;
    }
    
    /**
     * 获取文件列表
     */
    public function getFiles($dir) {
        // 检查 site_root 是否设置
        if (!$this->site_root) {
            return $this->error('无法获取网站路径，请检查用户配置');
        }
        
        // 1. 清理和验证输入路径
        $dir = $this->sanitizePath($dir);
        
        // 2. 计算完整路径
        $full_path = $dir === '/' ? $this->site_root : $this->site_root . $dir;
        $full_path = preg_replace('#/{2,}#', '/', $full_path);
        
        // 3. 严格验证路径是否在用户根目录下
        if (!$this->validatePath($full_path)) {
            $full_path = $this->site_root;
            $dir = '/';
        }
        
        // 4. 使用宝塔API获取文件列表
        $result = $this->bt_api->GetHostDirectory($full_path);
        
        // 处理文件列表 - 参考前端实现
        $processed_files = array();
        $dirs = isset($result['DIR']) ? $result['DIR'] : array();
        $file_items = isset($result['FILES']) ? $result['FILES'] : array();
        
        // 处理目录列表（宝塔返回的 name 为当前目录下的子项名，不含路径）
        foreach ($dirs as $item) {
            if (is_string($item)) {
                $parts = explode(';', $item);
                $name = isset($parts[0]) ? trim($parts[0]) : '';
            } elseif (is_array($item)) {
                $name = isset($item['name']) ? $item['name'] : '';
            } else {
                $name = '';
            }
            
            if (empty($name) || $name == '.' || $name == '..') continue;
            
            $size = 0;
            $mtime = time();
            $perm = '';
            $owner = 'www';
            if (is_string($item)) {
                $parts = explode(';', $item);
                if (isset($parts[1])) {
                    if (is_numeric($parts[1])) {
                        $size = intval($parts[1]);
                    }
                }
                if (isset($parts[2]) && is_numeric($parts[2])) $mtime = intval($parts[2]);
                if (isset($parts[3])) $perm = trim($parts[3]);
                if (isset($parts[4])) $owner = trim($parts[4]);
            } elseif (is_array($item)) {
                if (isset($item['size'])) {
                    if (is_numeric($item['size'])) {
                        $size = intval($item['size']);
                    }
                }
                if (isset($item['mtime'])) $mtime = $item['mtime'];
                elseif (isset($item['time'])) $mtime = $item['time'];
                if (isset($item['access'])) $perm = $item['access'];
                if (isset($item['owner'])) $owner = $item['owner'];
            }
            
            $processed_files[] = array(
                'name' => $name,
                'type' => 'directory',
                'size' => $size,
                'mtime' => $mtime,
                'permission' => $perm,
                'owner' => $owner
            );
        }
        
        // 处理文件列表
        foreach ($file_items as $item) {
            if (is_string($item)) {
                $parts = explode(';', $item);
                $name = isset($parts[0]) ? trim($parts[0]) : '';
            } elseif (is_array($item)) {
                $name = isset($item['name']) ? $item['name'] : '';
            } else {
                $name = '';
            }
            
            if (empty($name) || $name == '.' || $name == '..') continue;
            
            $size = 0;
            $mtime = time();
            $perm = '';
            $owner = 'www';
            if (is_string($item)) {
                $parts = explode(';', $item);
                if (isset($parts[1]) && is_numeric($parts[1])) $size = intval($parts[1]);
                if (isset($parts[2]) && is_numeric($parts[2])) $mtime = intval($parts[2]);
                if (isset($parts[3])) $perm = trim($parts[3]);
                if (isset($parts[4])) $owner = trim($parts[4]);
            } elseif (is_array($item)) {
                if (isset($item['size']) && is_numeric($item['size'])) $size = intval($item['size']);
                if (isset($item['mtime'])) $mtime = $item['mtime'];
                elseif (isset($item['time'])) $mtime = $item['time'];
                if (isset($item['access'])) $perm = $item['access'];
                if (isset($item['owner'])) $owner = $item['owner'];
            }
            
            $processed_files[] = array(
                'name' => $name,
                'type' => 'file',
                'size' => $size,
                'mtime' => $mtime,
                'permission' => $perm,
                'owner' => $owner
            );
        }
        
        // 排序：目录在前，文件在后
        usort($processed_files, function($a, $b) {
            $aIsDir = $a['type'] === 'directory' || $a['type'] === 'dir';
            $bIsDir = $b['type'] === 'directory' || $b['type'] === 'dir';
            if ($aIsDir && !$bIsDir) return -1;
            if (!$aIsDir && $bIsDir) return 1;
            return strcasecmp($a['name'], $b['name']);
        });
        
        return $this->success(array(
            'files' => $processed_files,
            'path' => $dir,
            'basePath' => $this->site_root
        ));
    }
    
    /**
     * 解析文件项 - 宝塔API返回格式: "name;size;mtime;chmod;chown;group;isdir;..."
     */
    protected function parseFileItem($item) {
        // 如果是数组，直接返回
        if (is_array($item)) {
            return array(
                'name' => isset($item['name']) ? $item['name'] : '',
                'size' => isset($item['size']) ? $item['size'] : 0,
                'mtime' => isset($item['mtime']) ? $item['mtime'] : 0,
                'ext' => isset($item['ext']) ? $item['ext'] : ''
            );
        }
        
        // 如果是字符串，按分号分割解析
        if (is_string($item)) {
            $parts = explode(';', $item);
            if (count($parts) >= 1) {
                return array(
                    'name' => $parts[0],
                    'size' => isset($parts[1]) ? $parts[1] : 0,
                    'mtime' => isset($parts[2]) ? $parts[2] : 0,
                    'ext' => ''
                );
            }
        }
        
        return null;
    }
    
    /**
     * 成功响应
     */
    protected function success($data = array()) {
        return array_merge(array('status' => true, 'code' => 'success'), $data);
    }
    
    /**
     * 错误响应
     */
    protected function error($message) {
        return array('status' => false, 'code' => $message, 'msg' => $message);
    }
}
