81 lines
3.2 KiB
PHP
81 lines
3.2 KiB
PHP
<?php
|
||
/**
|
||
* DWG/DXF 查看器 - 图纸读取接口
|
||
* 校验令牌 → .enc 解密 / 原文件原样 → 流式返回。
|
||
* SECRET 必须与 token.php、upload.php 一致。
|
||
*/
|
||
$SECRET = 'be323d1b90bd2fda624083d4d1d716b7';
|
||
// 与 upload.php 保持一致的私有目录配置:图纸放 Web 目录外,web 里根本没有文件可下载
|
||
$PRIVATE_DIR = '/volume1/dwgviewer_private';
|
||
|
||
function json_out($code, $obj) {
|
||
http_response_code($code);
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
echo json_encode($obj, JSON_UNESCAPED_UNICODE);
|
||
exit;
|
||
}
|
||
|
||
$f = $_GET['f'] ?? '';
|
||
$t = $_GET['t'] ?? '';
|
||
$e = (int)($_GET['e'] ?? 0);
|
||
if (!$f || !$t || !preg_match('/\.(dwg|dxf|enc)$/i', $f)) json_out(400, ['error' => 'bad params']);
|
||
if (strpos($f, '..') !== false) json_out(400, ['error' => 'bad path']);
|
||
|
||
$sig = substr(hash_hmac('sha256', $f . '|' . $e, $SECRET), 0, 16);
|
||
if (!hash_equals($sig, $t) || time() > $e) json_out(403, ['error' => 'invalid or expired token']);
|
||
|
||
$root = realpath(__DIR__ . '/..');
|
||
if ($PRIVATE_DIR !== '') {
|
||
// 私有目录优先(嵌入链接写纯文件名即可);目录不存在时退回站内路径,
|
||
// 让留在 web 目录里的样例图纸按原路径继续可用(这些文件本来就允许直链,
|
||
// 退回不新增暴露面)。要保护的图纸必须移出 web 目录放进私有目录。
|
||
$full = realpath($PRIVATE_DIR . '/' . basename($f));
|
||
$ok = $full && strpos($full, realpath($PRIVATE_DIR)) === 0 && is_file($full);
|
||
if (!$ok) {
|
||
$full = realpath($root . '/' . $f);
|
||
$ok = $full && strpos($full, $root) === 0 && is_file($full);
|
||
}
|
||
} else {
|
||
$full = realpath($root . '/' . $f);
|
||
$ok = $full && strpos($full, $root) === 0 && is_file($full);
|
||
}
|
||
if (!$ok) json_out(404, ['error' => 'not found']);
|
||
|
||
$data = file_get_contents($full);
|
||
$origName = basename($f);
|
||
if (preg_match('/\.enc$/i', $f)) {
|
||
$nonce = substr($data, 0, 16);
|
||
$key = hash('sha256', $SECRET . $nonce, true);
|
||
$body = substr($data, 16);
|
||
$out = '';
|
||
for ($i = 0; $i < strlen($body); $i++) {
|
||
$out .= $body[$i] ^ $key[$i % strlen($key)];
|
||
}
|
||
// 加密体头部藏着原文件名(落盘名是随机的,链接里看不出是哪张图)
|
||
if (strlen($out) > 2) {
|
||
$nl = unpack('n', substr($out, 0, 2))[1];
|
||
if ($nl > 0 && $nl <= 255 && strlen($out) > 2 + $nl) {
|
||
$origName = substr($out, 2, $nl);
|
||
$out = substr($out, 2 + $nl);
|
||
}
|
||
}
|
||
$data = $out;
|
||
}
|
||
header('X-Filename: ' . rawurlencode($origName));
|
||
header('Access-Control-Expose-Headers: X-Filename');
|
||
|
||
// 上传时已经 gzip 过,解密后就是 gzip 流,直接标 Content-Encoding 让浏览器解;
|
||
// 明文文件(如 dxf)在线压一次,DXF 文本能压到 1/8 左右
|
||
$send = $data;
|
||
if (substr($send, 0, 2) === "\x1f\x8b") {
|
||
header('Content-Encoding: gzip');
|
||
} elseif (!ini_get('zlib.output_compression') && strlen($data) > 1024) {
|
||
$gz = gzencode($data, 6);
|
||
if ($gz !== false) { $send = $gz; header('Content-Encoding: gzip'); }
|
||
}
|
||
header('Content-Type: application/octet-stream');
|
||
header('Content-Length: ' . strlen($send));
|
||
header('Cache-Control: no-store');
|
||
header('Content-Disposition: inline');
|
||
echo $send;
|