Files
Leantime/dev/DWGViewer/api/upload.php

110 lines
4.7 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* DWG/DXF 查看器 - 上传接口
* 接收原始字节流 + X-Filename 头gzip 后异或加密存成 uploads/xxx.enc。
* 落盘就是密文,即使目录被翻到也拿不到可用的图纸。
* SECRET 必须与 token.php、model.php 一致。
*/
header('Content-Type: application/json; charset=utf-8');
$SECRET = 'be323d1b90bd2fda624083d4d1d716b7';
$MAX_SIZE = 200 * 1024 * 1024;
// 私有目录Web 目录外的绝对路径图纸只存这里web 目录里根本没有图纸文件可下载。
// 群晖 File Station 在 web 目录外建 /volume1/dwgviewer_private 并给 http 用户组读写权限;
// 路径不同就改成你的实际路径。
$PRIVATE_DIR = '/volume1/dwgviewer_private';
function json_out($code, $obj) {
http_response_code($code);
echo json_encode($obj, JSON_UNESCAPED_UNICODE);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') json_out(405, ['error' => 'method not allowed']);
// 只允许本站页面发起上传(与 token.php 同一策略Sec-Fetch-Site 优先、Referer 兜底,
// 父站点的 Referrer-Policy 剥不掉 Sec-Fetch-Site跨站 iframe 嵌入也能上传)
$fetchSite = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$host = $_SERVER['HTTP_HOST'] ?? '';
if ($fetchSite !== '') {
if ($fetchSite !== 'same-origin' && $fetchSite !== 'same-site') json_out(403, ['error' => 'forbidden']);
} elseif (!$host || strpos($referer, $host) === false) {
json_out(403, ['error' => 'forbidden']);
}
$len = (int)($_SERVER['CONTENT_LENGTH'] ?? 0);
if ($len <= 0) json_out(400, ['error' => 'empty body']);
if ($len > $MAX_SIZE) json_out(413, ['error' => 'file too large (max 200MB)']);
$raw = urldecode($_SERVER['HTTP_X_FILENAME'] ?? 'drawing.dwg');
$base = basename($raw);
$safe = preg_replace('/[^\w.\-\x{4e00}-\x{9fff}]/u', '_', $base);
if (!$safe || !preg_match('/\.(dwg|dxf)$/i', $safe)) json_out(400, ['error' => '只支持 .dwg / .dxf']);
if (strlen($safe) > 200) $safe = substr($safe, 0, 200);
// 落盘名是纯随机的,不带原文件名也不带真实后缀:
// 嵌入链接里就看不出这是哪张图、属于哪个项目,也没法按名字猜其它图纸。
// 原文件名放进加密体里,读取时由 model.php 通过 X-Filename 头带回来显示。
$name = bin2hex(random_bytes(12)) . '.enc';
$data = file_get_contents('php://input');
if ($data === false || strlen($data) !== $len) json_out(400, ['error' => 'incomplete upload']);
// 存储层压缩DWG 本身已压过DXF 文本能压到 1/8
$rawSize = strlen($data);
$data = gzencode($data, 6);
if ($data === false) json_out(500, ['error' => 'gzip failed']);
// 加密体 = 2 字节名字长度 + 原文件名 + gzip 数据
$data = pack('n', strlen($safe)) . $safe . $data;
$dir = $PRIVATE_DIR !== '' ? $PRIVATE_DIR : (__DIR__ . '/../uploads');
if (!is_dir($dir)) @mkdir($dir, 0755, true);
if (!is_dir($dir)) json_out(500, ['error' => 'upload dir not writable']);
$nonce = random_bytes(16);
$key = hash('sha256', $SECRET . $nonce, true);
$enc = '';
for ($i = 0; $i < strlen($data); $i++) {
$enc .= $data[$i] ^ $key[$i % strlen($key)];
}
if (file_put_contents($dir . '/' . $name, $nonce . $enc) === false) {
json_out(500, ['error' => 'write failed']);
}
// 私有目录模式下返回裸文件名:嵌入链接就是 ?file=xxx.enc读取端按 basename 在私有目录找
$entryPath = ($PRIVATE_DIR !== '' ? '' : 'uploads/') . $name;
// 记入首页数模库(入口目录的 library.json2D/3D 共享);库写失败不影响上传本身
try {
add_to_library($entryPath, $safe, '2d');
} catch (Throwable $e) { /* 忽略 */ }
json_out(200, ['path' => $entryPath, 'name' => $safe, 'size' => $rawSize]);
/**
* 把刚上传的文件追加进首页数模库(../../library.json
* 同路径已有记录就刷新名称和时间,否则插到最前。
*/
function add_to_library($path, $name, $type) {
$libFile = dirname(__DIR__, 2) . '/library.json';
$records = [];
if (is_file($libFile)) {
$old = json_decode((string)file_get_contents($libFile), true);
if (is_array($old) && isset($old['records']) && is_array($old['records'])) $records = $old['records'];
}
$found = false;
foreach ($records as $i => $rec) {
if (isset($rec['path']) && $rec['path'] === $path) {
$records[$i]['name'] = $name;
$records[$i]['time'] = time();
$found = true;
break;
}
}
if (!$found) {
array_unshift($records, ['path' => $path, 'name' => $name, 'type' => $type, 'note' => '', 'time' => time()]);
}
@file_put_contents($libFile, json_encode(['records' => $records], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
}