PHP实现指定来源可在线预览PDF的问题求助
问题分析与修复方案
核心问题
- Referer验证逻辑错误:当前代码将
parse_url返回的完整数组(包含scheme、path等字段)与允许的域名列表对比,逻辑完全错误,应该仅提取并验证host字段。 - Inline预览时Referer丢失:部分浏览器在处理
inline类型的PDF预览时,可能因安全策略或请求方式不携带HTTP_REFERER;另外,当HTTP_REFERER为空时,parse_url会抛出警告,导致后续验证失败。 - 文件路径处理不安全:直接使用
REQUEST_URI拼接路径存在路径遍历风险,且路径计算逻辑错误。
具体修复步骤
1. 修正Referer验证逻辑
只提取Referer的host部分,去除www.前缀后再与允许列表对比,同时处理HTTP_REFERER为空的情况:
// 初始化验证标记 $isAllowed = false; // 先检查HTTP_REFERER是否存在 if (!empty($_SERVER['HTTP_REFERER'])) { $refererData = parse_url($_SERVER['HTTP_REFERER']); // 确保host字段存在 if (isset($refererData['host'])) { $refererHost = $refererData['host']; // 去除www.前缀 if (str_starts_with($refererHost, 'www.')) { $refererHost = ltrim($refererHost, 'www.'); } // 验证是否在允许列表中 $referrals = array("website1.com", "website2.com", "website3.com"); $isAllowed = in_array($refererHost, $referrals); } }
2. 修复文件路径计算逻辑
避免使用REQUEST_URI拼接路径,直接从请求中提取文件名,拼接安全的相对路径:
// 从REQUEST_URI中提取文件名(避免路径遍历) $pathParts = explode('/', $_SERVER['REQUEST_URI']); $fileName = end($pathParts); // 只保留合法的文件名(过滤非法字符) $fileName = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $fileName); // 正确的文件路径:check.php在sub1/sub2,PDF在sub1/sub2/sub3,所以用./sub3/ $file = './sub3/' . $fileName;
3. 调整响应头与处理Referer丢失的兼容
如果需要允许同域内的预览请求(比如从sub1/sub2下的页面打开PDF),可以额外检查请求是否来自同域;同时设置正确的响应头:
if ($isAllowed) { if (!file_exists($file)) { echo 'error1'; exit; } // 设置响应头,支持inline预览 header("Content-Type: application/pdf"); // 对于inline预览,filename可选,但建议保留 header("Content-Disposition: inline; filename=\"{$fileDescription}.pdf\""); header("Content-Length: " . filesize($file)); // 可选:添加缓存头,提升体验 header("Cache-Control: public, max-age=3600"); readfile($file); exit; } else { echo 'error2'; exit; }
4. 优化浏览器Referer发送策略
在放置PDF链接的页面中添加Referrer-Policy头,确保浏览器发送Referer:
<!-- 在页面的<head>中添加 --> <meta name="referrer" content="strict-origin-when-cross-origin">
或者通过服务器配置(比如.htaccess)添加:
Header set Referrer-Policy "strict-origin-when-cross-origin"
完整修复后的check.php代码
<?php $fileDescription = 'funnyFileName'; $isAllowed = false; // 验证Referer if (!empty($_SERVER['HTTP_REFERER'])) { $refererData = parse_url($_SERVER['HTTP_REFERER']); if (isset($refererData['host'])) { $refererHost = $refererData['host']; if (str_starts_with($refererHost, 'www.')) { $refererHost = ltrim($refererHost, 'www.'); } $referrals = array("website1.com", "website2.com", "website3.com"); $isAllowed = in_array($refererHost, $referrals); } } // 处理文件路径 $pathParts = explode('/', $_SERVER['REQUEST_URI']); $fileName = end($pathParts); $fileName = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $fileName); $file = './sub3/' . $fileName; // 验证与输出 if ($isAllowed) { if (!file_exists($file)) { echo 'error1'; exit; } header("Content-Type: application/pdf"); header("Content-Disposition: inline; filename=\"{$fileDescription}.pdf\""); header("Content-Length: " . filesize($file)); header("Cache-Control: public, max-age=3600"); readfile($file); } else { echo 'error2'; } ?>
内容的提问来源于stack exchange,提问作者robinw99
相关产品推荐
相关产品推荐

