<?php
declare(strict_types=1);

/**
 * UC 网盘上传测试版
 *
 * PHP 8.2+
 * 需要 cURL 扩展
 *
 * 功能：
 * 1. 保存 UC Cookie 到 PHP Session
 * 2. 检查 UC 登录状态
 * 3. 调用 upload/pre
 * 4. 调用 upload/auth
 * 5. 使用临时 OSS 授权上传文件
 * 6. 调用 upload/finish
 * 7. 返回上传结果
 *
 * 注意：
 * - 不保存 OTP
 * - 不把 Authorization 写死
 * - 不把 upload_id / task_id / auth_key 写死
 * - 不把你的真实 Cookie 写进源码
 */

session_start();

header('X-Content-Type-Options: nosniff');

const UC_API = 'https://pc-api.uc.cn';
const UC_DRIVE = 'https://drive.uc.cn';

function jsonResponse(array $data, int $status = 200): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');

    echo json_encode(
        $data,
        JSON_UNESCAPED_UNICODE |
        JSON_UNESCAPED_SLASHES |
        JSON_PRETTY_PRINT
    );

    exit;
}

function curlRequest(
    string $url,
    string $method = 'GET',
    ?string $body = null,
    array $headers = [],
    ?string $cookie = null
): array {
    $ch = curl_init();

    $defaultHeaders = [
        'Accept: application/json, text/plain, */*',
        'Origin: ' . UC_DRIVE,
        'Referer: ' . UC_DRIVE . '/',
        'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/148 Safari/537.36',
    ];

    if ($cookie !== null && $cookie !== '') {
        $defaultHeaders[] = 'Cookie: ' . $cookie;
    }

    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER => true,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => array_merge($defaultHeaders, $headers),
        CURLOPT_CONNECTTIMEOUT => 20,
        CURLOPT_TIMEOUT => 300,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_ENCODING => '',
    ]);

    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }

    $raw = curl_exec($ch);

    if ($raw === false) {
        $error = curl_error($ch);
        curl_close($ch);

        return [
            'ok' => false,
            'status' => 0,
            'headers' => '',
            'body' => '',
            'error' => $error,
        ];
    }

    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

    $responseHeaders = substr($raw, 0, $headerSize);
    $responseBody = substr($raw, $headerSize);

    curl_close($ch);

    return [
        'ok' => $status >= 200 && $status < 300,
        'status' => $status,
        'headers' => $responseHeaders,
        'body' => $responseBody,
        'error' => null,
    ];
}

function getJsonBody(array $response): ?array
{
    $data = json_decode($response['body'], true);

    return is_array($data) ? $data : null;
}

function getCookieString(): string
{
    return trim((string)($_SESSION['uc_cookie'] ?? ''));
}

function requireCookie(): string
{
    $cookie = getCookieString();

    if ($cookie === '') {
        jsonResponse([
            'success' => false,
            'error' => '当前 PHP Session 没有 UC Cookie',
            'hint' => '请先通过页面设置当前有效的 UC Cookie。',
        ], 401);
    }

    return $cookie;
}

/**
 * 安全设置 Cookie。
 *
 * 页面不会把 Cookie 输出到日志或 JSON。
 */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $action = $_POST['action'] ?? '';

    /**
     * 设置当前 Session 的 UC Cookie
     */
    if ($action === 'set_cookie') {

        $cookie = trim((string)($_POST['cookie'] ?? ''));

        if ($cookie === '') {
            jsonResponse([
                'success' => false,
                'error' => 'Cookie 不能为空',
            ], 400);
        }

        /*
         * 基础检查。
         * 不要求固定 Cookie 名称，因为 UC 的 Cookie 可能发生变化。
         */
        if (
            !str_contains($cookie, '=') ||
            strlen($cookie) < 20
        ) {
            jsonResponse([
                'success' => false,
                'error' => 'Cookie 格式看起来不正确',
            ], 400);
        }

        $_SESSION['uc_cookie'] = $cookie;

        jsonResponse([
            'success' => true,
            'message' => 'UC 会话已保存到当前 PHP Session',
        ]);
    }

    /**
     * 清除 Session
     */
    if ($action === 'logout') {

        unset($_SESSION['uc_cookie']);

        jsonResponse([
            'success' => true,
            'message' => '当前 PHP Session 已清除',
        ]);
    }

    /**
     * 检查 UC 用户状态
     */
    if ($action === 'member') {

        $cookie = requireCookie();

        $url =
            UC_API .
            '/1/clouddrive/member' .
            '?pr=UCBrowser' .
            '&fr=pc' .
            '&fetch_subscribe=true' .
            '&_ch=home';

        $response = curlRequest(
            $url,
            'GET',
            null,
            [],
            $cookie
        );

        $json = getJsonBody($response);

        if ($json === null) {
            jsonResponse([
                'success' => false,
                'http_status' => $response['status'],
                'error' => 'UC 返回的不是有效 JSON',
            ], 502);
        }

        jsonResponse([
            'success' => $response['ok'],
            'http_status' => $response['status'],
            'data' => $json,
        ]);
    }

    /**
     * 获取文件列表
     */
    if ($action === 'list') {

        $cookie = requireCookie();

        $query = http_build_query([
            'pr' => 'UCBrowser',
            'fr' => 'pc',
            'pdir_fid' => '0',
            '_page' => '1',
            '_size' => '50',
            '_fetch_total' => '1',
            '_fetch_sub_dirs' => '0',
            '_sort' => 'file_type:asc,updated_at:desc',
        ]);

        $url = UC_API . '/1/clouddrive/file/sort?' . $query;

        $response = curlRequest(
            $url,
            'GET',
            null,
            [],
            $cookie
        );

        $json = getJsonBody($response);

        jsonResponse([
            'success' => $response['ok'],
            'http_status' => $response['status'],
            'data' => $json,
        ]);
    }

    /**
     * 上传文件
     */
    if ($action === 'upload') {

        $cookie = requireCookie();

        if (
            !isset($_FILES['file']) ||
            !is_array($_FILES['file'])
        ) {
            jsonResponse([
                'success' => false,
                'error' => '没有收到文件',
            ], 400);
        }

        $file = $_FILES['file'];

        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
            jsonResponse([
                'success' => false,
                'error' => 'PHP 文件上传失败',
                'upload_error' => $file['error'] ?? -1,
            ], 400);
        }

        $tmpPath = $file['tmp_name'];
        $fileName = basename((string)$file['name']);
        $fileSize = (int)$file['size'];

        if (!is_file($tmpPath)) {
            jsonResponse([
                'success' => false,
                'error' => '临时文件不存在',
            ], 400);
        }

        if ($fileSize <= 0) {
            jsonResponse([
                'success' => false,
                'error' => '文件大小为 0',
            ], 400);
        }

        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = $finfo->file($tmpPath) ?: 'application/octet-stream';

        /*
         * UC upload/pre
         */
        $preData = [
            'ccp_hash_update' => true,
            'parallel_upload' => true,
            'pdir_fid' => '0',
            'dir_name' => '',
            'size' => $fileSize,
            'file_name' => $fileName,
            'format_type' => $mime,
            'l_updated_at' => (int)(microtime(true) * 1000),
            'l_created_at' => (int)(microtime(true) * 1000),
        ];

        $preResponse = curlRequest(
            UC_API . '/1/clouddrive/file/upload/pre?pr=UCBrowser&fr=pc',
            'POST',
            json_encode(
                $preData,
                JSON_UNESCAPED_UNICODE |
                JSON_UNESCAPED_SLASHES
            ),
            [
                'Content-Type: application/json',
            ],
            $cookie
        );

        $preJson = getJsonBody($preResponse);

        if (!$preResponse['ok'] || !is_array($preJson)) {
            jsonResponse([
                'success' => false,
                'stage' => 'upload/pre',
                'http_status' => $preResponse['status'],
                'response' => $preJson ?? $preResponse['body'],
            ], 502);
        }

        /*
         * 不把完整 pre 响应直接原样输出。
         * 尝试兼容常见 data 结构。
         */
        $pre = $preJson['data'] ?? $preJson;

        if (!is_array($pre)) {
            jsonResponse([
                'success' => false,
                'stage' => 'upload/pre',
                'error' => '无法解析 upload/pre data',
            ], 502);
        }

        $taskId = (string)($pre['task_id'] ?? '');
        $uploadId = (string)($pre['upload_id'] ?? '');
        $objKey = (string)($pre['obj_key'] ?? '');
        $uploadUrl = (string)($pre['upload_url'] ?? '');
        $partSize = (int)($pre['part_size'] ?? 4194304);
        $authInfo = $pre['auth_info'] ?? null;

        if (
            $taskId === '' ||
            $uploadId === '' ||
            $objKey === '' ||
            $uploadUrl === '' ||
            $authInfo === null
        ) {
            jsonResponse([
                'success' => false,
                'stage' => 'upload/pre',
                'error' => 'upload/pre 缺少必要字段',
                'fields_found' => [
                    'task_id' => $taskId !== '',
                    'upload_id' => $uploadId !== '',
                    'obj_key' => $objKey !== '',
                    'upload_url' => $uploadUrl !== '',
                    'auth_info' => $authInfo !== null,
                ],
            ], 502);
        }

        /*
         * 当前版本先处理单文件上传。
         *
         * 对于大文件：
         * partSize 通常来自 UC。
         * 后续需要按照 upload/pre 实际返回的分片信息循环处理。
         */

        $fileHandle = fopen($tmpPath, 'rb');

        if ($fileHandle === false) {
            jsonResponse([
                'success' => false,
                'stage' => 'local',
                'error' => '无法打开上传文件',
            ], 500);
        }

        /*
         * 读取第一个 Part
         */
        $partData = fread($fileHandle, $partSize);

        fclose($fileHandle);

        if ($partData === false || $partData === '') {
            jsonResponse([
                'success' => false,
                'stage' => 'local',
                'error' => '无法读取文件内容',
            ], 500);
        }

        /*
         * upload/auth
         *
         * 这里不伪造授权。
         * 直接把 UC upload/pre 返回的动态 auth_info
         * 交给 UC auth 接口。
         */
        $authPayload = [
            'task_id' => $taskId,
            'auth_info' => $authInfo,
        ];

        $authUrl =
            UC_API .
            '/1/clouddrive/file/upload/auth' .
            '?__partNumber=1' .
            '&pr=UCBrowser' .
            '&fr=pc';

        $authResponse = curlRequest(
            $authUrl,
            'POST',
            json_encode(
                $authPayload,
                JSON_UNESCAPED_UNICODE |
                JSON_UNESCAPED_SLASHES
            ),
            [
                'Content-Type: application/json',
            ],
            $cookie
        );

        $authJson = getJsonBody($authResponse);

        if (!$authResponse['ok'] || !is_array($authJson)) {
            jsonResponse([
                'success' => false,
                'stage' => 'upload/auth',
                'http_status' => $authResponse['status'],
                'response' => $authJson ?? $authResponse['body'],
            ], 502);
        }

        $auth = $authJson['data'] ?? $authJson;

        if (!is_array($auth)) {
            jsonResponse([
                'success' => false,
                'stage' => 'upload/auth',
                'error' => '无法解析 auth data',
            ], 502);
        }

        /*
         * 不假设授权字段一定叫同一个名字。
         */
        $authKey = (string)($auth['auth_key'] ?? '');
        $authMeta = (string)($auth['auth_meta'] ?? '');

        if ($authKey === '') {
            jsonResponse([
                'success' => false,
                'stage' => 'upload/auth',
                'error' => 'UC 没有返回 auth_key',
            ], 502);
        }

        /*
         * uploadUrl 可能是：
         * http://pds.uc.cn
         * 或其它 UC OSS 地址。
         *
         * 真正的 host/路径以 upload/pre 实际返回值为准。
         */
        $baseUploadUrl = rtrim($uploadUrl, '/');

        /*
         * 根据浏览器抓包结构构造 Part URL。
         */
        $partUrl =
            $baseUploadUrl .
            '/' .
            ltrim($objKey, '/') .
            '?partNumber=1&uploadId=' .
            rawurlencode($uploadId);

        /*
         * OSS 上传。
         *
         * 注意：
         * 这里使用 UC upload/auth 返回的动态授权。
         */
        $date = gmdate('D, d M Y H:i:s \G\M\T');

        $ossHeaders = [
            'Content-Type: ' . $mime,
            'Content-Length: ' . strlen($partData),
            'Authorization: ' . $authKey,
            'x-oss-date: ' . $date,
            'x-oss-user-agent: aliyun-sdk-js/1.0.3 PHP',
        ];

        $ossResponse = curlRequest(
            $partUrl,
            'PUT',
            $partData,
            $ossHeaders,
            null
        );

        if (!$ossResponse['ok']) {
            jsonResponse([
                'success' => false,
                'stage' => 'oss-upload',
                'http_status' => $ossResponse['status'],
                'error' => 'OSS 分片上传失败',
                'response' => $ossResponse['body'],
            ], 502);
        }

        /*
         * 从 OSS Response Header 获取 ETag。
         */
        $etag = '';

        if (
            preg_match(
                '/^ETag:\s*"?(.*?)"?\s*$/mi',
                $ossResponse['headers'],
                $match
            )
        ) {
            $etag = trim($match[1], "\" \t\r\n");
        }

        if ($etag === '') {
            jsonResponse([
                'success' => false,
                'stage' => 'oss-upload',
                'error' => 'OSS 没有返回 ETag',
            ], 502);
        }

        /*
         * OSS Multipart Complete
         */
        $completeXml =
            '<?xml version="1.0" encoding="UTF-8"?>' .
            '<CompleteMultipartUpload>' .
            '<Part>' .
            '<PartNumber>1</PartNumber>' .
            '<ETag>"' .
            htmlspecialchars($etag, ENT_XML1) .
            '"</ETag>' .
            '</Part>' .
            '</CompleteMultipartUpload>';

        $completeUrl =
            $baseUploadUrl .
            '/' .
            ltrim($objKey, '/') .
            '?uploadId=' .
            rawurlencode($uploadId);

        $completeResponse = curlRequest(
            $completeUrl,
            'POST',
            $completeXml,
            [
                'Content-Type: application/xml',
                'Content-MD5: ' . base64_encode(md5($completeXml, true)),
                'Authorization: ' . $authKey,
                'x-oss-date: ' . $date,
            ],
            null
        );

        if (!$completeResponse['ok']) {
            jsonResponse([
                'success' => false,
                'stage' => 'oss-complete',
                'http_status' => $completeResponse['status'],
                'error' => 'OSS Multipart Complete 失败',
                'response' => $completeResponse['body'],
            ], 502);
        }

        /*
         * UC upload/finish
         */
        $finishPayload = [
            'obj_key' => $objKey,
            'task_id' => $taskId,
        ];

        $finishResponse = curlRequest(
            UC_API . '/1/clouddrive/file/upload/finish?pr=UCBrowser&fr=pc',
            'POST',
            json_encode(
                $finishPayload,
                JSON_UNESCAPED_UNICODE |
                JSON_UNESCAPED_SLASHES
            ),
            [
                'Content-Type: application/json',
            ],
            $cookie
        );

        $finishJson = getJsonBody($finishResponse);

        if (!$finishResponse['ok'] || !is_array($finishJson)) {
            jsonResponse([
                'success' => false,
                'stage' => 'upload/finish',
                'http_status' => $finishResponse['status'],
                'response' => $finishJson ?? $finishResponse['body'],
            ], 502);
        }

        $finishData = $finishJson['data'] ?? [];

        jsonResponse([
            'success' => true,
            'message' => '上传流程完成',
            'result' => [
                'finish' => $finishData['finish'] ?? null,
                'task_id' => $finishData['task_id'] ?? null,
                'fid' => $finishData['fid'] ?? null,
                'pdir_fid' => $finishData['pdir_fid'] ?? null,
                'file_name' => $finishData['file_name'] ?? $fileName,
                'size' => $finishData['size'] ?? $fileSize,
                'format_type' => $finishData['format_type'] ?? $mime,
                'md5' => $finishData['md5'] ?? null,
                'sha1' => $finishData['sha1'] ?? null,
                'preview_url' => $finishData['preview_url'] ?? null,
            ],
        ]);

    }

    jsonResponse([
        'success' => false,
        'error' => '未知 action',
    ], 400);
}
?>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>UC 网盘上传测试</title>

<style>
*{
    box-sizing:border-box;
}

body{
    margin:0;
    min-height:100vh;
    background:#0b0f17;
    color:#f4f6fa;
    font-family:
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        Roboto,
        "PingFang SC",
        "Microsoft YaHei",
        sans-serif;
}

.wrap{
    width:min(680px,calc(100% - 28px));
    margin:30px auto;
}

.panel{
    background:rgba(255,255,255,.055);
    border:1px solid rgba(255,255,255,.10);
    border-radius:20px;
    padding:20px;
    margin-bottom:16px;
    backdrop-filter:blur(18px);
}

h1{
    margin:0 0 8px;
    font-size:22px;
}

.sub{
    color:#8992a4;
    font-size:13px;
    margin-bottom:20px;
}

label{
    display:block;
    margin:14px 0 7px;
    color:#aeb7c7;
    font-size:13px;
}

input,
textarea{
    width:100%;
    border:1px solid rgba(255,255,255,.10);
    border-radius:12px;
    background:#111722;
    color:#fff;
    outline:none;
    padding:12px;
}

textarea{
    min-height:110px;
    resize:vertical;
}

button{
    width:100%;
    border:0;
    border-radius:12px;
    padding:13px;
    margin-top:12px;
    background:#ffffff;
    color:#111;
    font-weight:700;
    cursor:pointer;
}

button.secondary{
    background:#1b2230;
    color:#fff;
}

button:active{
    transform:scale(.99);
}

.actions{
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:10px;
}

#result{
    white-space:pre-wrap;
    word-break:break-word;
    background:#080b11;
    border-radius:12px;
    padding:14px;
    color:#b9c2d2;
    font-size:12px;
    min-height:80px;
}

.warning{
    color:#e9b96e;
    font-size:12px;
    line-height:1.6;
}
</style>
</head>

<body>

<div class="wrap">

<div class="panel">

<h1>UC 网盘上传测试</h1>

<div class="sub">
当前版本：登录会话 + 上传链测试
</div>

<div class="warning">
Cookie 只保存到当前 PHP Session，不要把真实 Cookie 分享给别人。
</div>

<label>当前 UC Cookie</label>

<textarea
    id="cookie"
    placeholder="粘贴当前浏览器有效 Cookie，仅保存在当前 Session"
></textarea>

<button onclick="setCookie()">
保存登录会话
</button>

</div>

<div class="panel">

<div class="actions">

<button class="secondary" onclick="member()">
检查登录
</button>

<button class="secondary" onclick="listFiles()">
读取文件列表
</button>

</div>

</div>

<div class="panel">

<label>选择文件</label>

<input
    type="file"
    id="file"
>

<button onclick="upload()">
上传到 UC 网盘
</button>

</div>

<div class="panel">

<div id="result">等待操作...</div>

</div>

</div>

<script>
function show(data){
    document.getElementById('result').textContent =
        typeof data === 'string'
            ? data
            : JSON.stringify(data,null,2);
}

async function request(formData){

    const response = await fetch(location.href,{
        method:'POST',
        body:formData
    });

    const text = await response.text();

    try{
        return JSON.parse(text);
    }catch{
        return {
            success:false,
            raw:text
        };
    }
}

async function setCookie(){

    const cookie =
        document.getElementById('cookie').value.trim();

    if(!cookie){
        show('请先输入 Cookie');
        return;
    }

    const fd = new FormData();

    fd.append('action','set_cookie');
    fd.append('cookie',cookie);

    show('正在保存...');

    const result = await request(fd);

    /*
     * 保存成功后清空输入框，
     * 避免 Cookie 长时间留在页面 DOM。
     */
    if(result.success){
        document.getElementById('cookie').value='';
    }

    show(result);
}

async function member(){

    const fd = new FormData();

    fd.append('action','member');

    show('正在检查 UC 登录状态...');

    const result = await request(fd);

    show(result);
}

async function listFiles(){

    const fd = new FormData();

    fd.append('action','list');

    show('正在读取 UC 文件列表...');

    const result = await request(fd);

    show(result);
}

async function upload(){

    const fileInput =
        document.getElementById('file');

    if(!fileInput.files.length){
        show('请选择文件');
        return;
    }

    const file =
        fileInput.files[0];

    const fd = new FormData();

    fd.append('action','upload');
    fd.append('file',file);

    show(
        '开始上传：' +
        file.name +
        '\n大小：' +
        file.size +
        ' bytes'
    );

    try{

        const result = await request(fd);

        show(result);

    }catch(error){

        show({
            success:false,
            error:String(error)
        });

    }
}
</script>

</body>
</html>