mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-09-13 00:14:56 +00:00
Compare commits
5 Commits
codex/add-
...
pdf-jbig2-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
257180eb95 | ||
|
|
e134689df3 | ||
|
|
3a08031929 | ||
|
|
47745e4d74 | ||
|
|
332a98b6fa |
@@ -1,4 +1,4 @@
|
||||
FROM keking/kkfileview-base:5.0.0
|
||||
ADD server/target/kkFileView-*.tar.gz /opt/
|
||||
ENV KKFILEVIEW_BIN_FOLDER=/opt/kkFileView-5.0.0/bin
|
||||
ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dspring.config.location=/opt/kkFileView-5.0.0/config/application.properties","-jar","/opt/kkFileView-5.0.0/bin/kkFileView-5.0.0.jar"]
|
||||
ENV KKFILEVIEW_BIN_FOLDER=/opt/kkFileView-5.0.1/bin
|
||||
ENTRYPOINT ["java","-Dfile.encoding=UTF-8","-Dspring.config.location=/opt/kkFileView-5.0.1/config/application.properties","-jar","/opt/kkFileView-5.0.1/bin/kkFileView-5.0.1.jar"]
|
||||
|
||||
23
README.cn.md
23
README.cn.md
@@ -149,6 +149,29 @@ pdf预览模式预览效果如下
|
||||
|
||||
### 历史更新记录
|
||||
|
||||
#### > 2026年07月13日,v5.0.1 补丁版本发布 :
|
||||
|
||||
#### 安全修复
|
||||
1. 修复 `/addTask` 未经过信任主机和本地目录过滤,可能导致服务端请求伪造(SSRF)的问题(GHSA-gwwj-52hv-6g2m)
|
||||
2. 修复 `/listFiles` 的 `directory` 参数可越出演示目录,造成路径遍历和目录信息泄露的问题(GHSA-pmp8-g8p2-p6jq)
|
||||
|
||||
#### 修复问题
|
||||
1. 修复 PDF 跨域、页码定位、文本高亮、打印和打印水印相关问题
|
||||
2. 修复 PDF 在反向代理场景下的绝对路径问题,以及水印和高亮内容包含特殊字符时的解析失败
|
||||
3. 修复 Redis 单机、集群、主从、哨兵模式配置不一致和地址协议缺失问题
|
||||
4. 修复下载 MIME 类型校验失败后仍返回成功、HTTP 错误原因不明确,以及共享 HTTP Client 被错误关闭的问题
|
||||
5. 修复 LuckyExcel 数据校验类型未映射时的 xlsx 解析崩溃
|
||||
|
||||
#### 优化内容
|
||||
1. 大型 xlsx 文件改用 Web Worker 执行 LuckyExcel 解析,并在 Worker 不可用或异常时自动回退主线程
|
||||
2. 新增 `pdf.sidebar.open` 配置,可控制 PDF 预览是否默认打开侧栏
|
||||
3. Maven CI 增加 Linux、Windows、macOS 构建验证
|
||||
4. 新增仓库安全策略和私密漏洞报告入口
|
||||
|
||||
#### 升级说明
|
||||
1. 建议所有 v5.0.0 及更早版本用户尽快升级到 v5.0.1
|
||||
2. 本版本继续要求 JDK 21 及以上,现有 v5.0.0 配置可直接沿用
|
||||
|
||||
#### > 2026年04月14日,v5.0.0 版本发布 :
|
||||
#### 优化内容
|
||||
1. xlsx 前端解析优化 - 提升Excel文件前端渲染性能
|
||||
|
||||
23
README.md
23
README.md
@@ -65,6 +65,29 @@ URL:[https://file.kkview.cn](https://file.kkview.cn)
|
||||
|
||||
## Change History
|
||||
|
||||
### Version 5.0.1 (July 13, 2026)
|
||||
|
||||
#### Security Fixes
|
||||
1. Fixed `/addTask` bypassing trusted-host and local-directory filters, which could allow server-side request forgery (SSRF) (GHSA-gwwj-52hv-6g2m)
|
||||
2. Fixed the `/listFiles` `directory` parameter escaping the demo directory, which could allow path traversal and directory information disclosure (GHSA-pmp8-g8p2-p6jq)
|
||||
|
||||
#### Fixes
|
||||
1. Fixed PDF cross-origin access, page positioning, text highlighting, printing, and print watermark issues
|
||||
2. Fixed PDF absolute paths behind reverse proxies and parsing failures when watermark or highlight text contains special characters
|
||||
3. Fixed inconsistent Redis settings across standalone, cluster, master-replica, and sentinel modes, including missing address protocols
|
||||
4. Fixed successful responses after MIME validation failures, unclear HTTP error reporting, and accidental closure of a shared HTTP client
|
||||
5. Fixed xlsx parsing crashes when LuckyExcel data-validation types have no mapping
|
||||
|
||||
#### Improvements
|
||||
1. Moved LuckyExcel parsing for large xlsx files into a Web Worker, with automatic main-thread fallback when the Worker is unavailable or fails
|
||||
2. Added `pdf.sidebar.open` to control whether the PDF sidebar opens by default
|
||||
3. Added Linux, Windows, and macOS validation to Maven CI
|
||||
4. Added a repository security policy and private vulnerability reporting guidance
|
||||
|
||||
#### Upgrade Notes
|
||||
1. All users running v5.0.0 or earlier are strongly encouraged to upgrade to v5.0.1
|
||||
2. JDK 21 or higher remains required, and existing v5.0.0 configuration can be reused
|
||||
|
||||
### Version 5.0.0 (April 14, 2026)
|
||||
|
||||
#### Improvements
|
||||
|
||||
66
SECURITY.md
Normal file
66
SECURITY.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security fixes are handled for the latest released version of kkFileView and the
|
||||
current `master` branch. Older versions may be evaluated case by case, but users
|
||||
are encouraged to upgrade to the latest release before reporting or verifying a
|
||||
security issue.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security vulnerabilities privately through GitHub Private
|
||||
Vulnerability Reporting:
|
||||
|
||||
https://github.com/kekingcn/kkFileView/security/advisories/new
|
||||
|
||||
Do not publish vulnerability details, proof-of-concept code, exploit steps,
|
||||
sensitive logs, or private deployment information in public GitHub issues,
|
||||
discussions, pull requests, or comments.
|
||||
|
||||
When reporting a vulnerability, please include as much of the following
|
||||
information as you can safely share:
|
||||
|
||||
- Affected kkFileView version or commit
|
||||
- Deployment mode, operating system, JDK version, and related middleware
|
||||
- Clear reproduction steps
|
||||
- Impact assessment and affected feature or endpoint
|
||||
- Sanitized logs, screenshots, or sample files if they are required to reproduce
|
||||
the issue
|
||||
- Whether the issue is already being disclosed elsewhere
|
||||
|
||||
The maintainers will review private reports, ask for additional information when
|
||||
needed, coordinate a fix, and publish disclosure information when appropriate.
|
||||
|
||||
If the private reporting link is unavailable, please open a public issue only to
|
||||
request a private contact channel, without including technical vulnerability
|
||||
details.
|
||||
|
||||
---
|
||||
|
||||
# 安全策略
|
||||
|
||||
## 支持版本
|
||||
|
||||
kkFileView 安全修复主要覆盖最新发布版本和当前 `master` 分支。旧版本问题会视影响范围和维护成本单独评估,但建议用户优先升级到最新版本后再验证或报告安全问题。
|
||||
|
||||
## 报告安全漏洞
|
||||
|
||||
请通过 GitHub Private Vulnerability Reporting 私密提交安全漏洞:
|
||||
|
||||
https://github.com/kekingcn/kkFileView/security/advisories/new
|
||||
|
||||
请不要在公开 GitHub issue、discussion、pull request 或评论中发布漏洞细节、PoC、利用步骤、敏感日志或私有部署信息。
|
||||
|
||||
提交漏洞时,请在可安全分享的前提下尽量提供以下信息:
|
||||
|
||||
- 受影响的 kkFileView 版本或提交
|
||||
- 部署方式、操作系统、JDK 版本和相关中间件信息
|
||||
- 清晰的复现步骤
|
||||
- 影响范围,以及受影响的功能或接口
|
||||
- 复现所需的脱敏日志、截图或样例文件
|
||||
- 该问题是否已在其他渠道披露
|
||||
|
||||
维护者会在私密渠道中评估报告,在需要时继续确认细节,协调修复,并在适当时发布披露信息。
|
||||
|
||||
如果私密报告链接不可用,请只在公开 issue 中请求私密联系方式,不要包含任何技术漏洞细节。
|
||||
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>cn.keking</groupId>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.0.1</version>
|
||||
|
||||
<properties>
|
||||
<!-- ========== Java 和编译配置 ========== -->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<groupId>cn.keking</groupId>
|
||||
<version>5.0.0</version>
|
||||
<version>5.0.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>kkFileView</artifactId>
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -93,6 +94,8 @@ public class PdfToJpgService {
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
refreshImageIoPlugins();
|
||||
|
||||
int maxThreads = ConfigConstants.getPdfMaxThreads();
|
||||
// 使用固定大小的虚拟线程池
|
||||
this.virtualThreadExecutor = Executors.newFixedThreadPool(maxThreads,
|
||||
@@ -104,6 +107,13 @@ public class PdfToJpgService {
|
||||
scheduleCacheCleanup();
|
||||
}
|
||||
|
||||
static void refreshImageIoPlugins() {
|
||||
// ImageIO only scans once automatically. If another launcher or Java agent initializes
|
||||
// it before Spring Boot installs its application class loader, nested JAR providers such
|
||||
// as jbig2-imageio remain invisible until the application class path is scanned again.
|
||||
ImageIO.scanForPlugins();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
logger.info("开始关闭PDF转换服务...");
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
@@ -341,13 +342,23 @@ public class FileController {
|
||||
}
|
||||
|
||||
// ==================== 2. 构建路径和验证 ====================
|
||||
String basePath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
basePath += path + File.separator;
|
||||
Path currentDir;
|
||||
try {
|
||||
currentDir = resolveDirectoryUnderRoot(Paths.get(fileDir, demoDir), path);
|
||||
} catch (InvalidPathException | SecurityException e) {
|
||||
logger.warn("拒绝访问 demo 目录之外的文件列表路径");
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
result.put("error", "非法目录路径");
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
logger.error("解析 demo 目录失败", e);
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
}
|
||||
|
||||
File currentDir = new File(basePath);
|
||||
if (!currentDir.exists() || !currentDir.isDirectory()) {
|
||||
if (!Files.isDirectory(currentDir)) {
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
@@ -357,13 +368,13 @@ public class FileController {
|
||||
List<Path> allPaths = new ArrayList<>();
|
||||
long collectStartTime = System.currentTimeMillis();
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(basePath))) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(currentDir)) {
|
||||
for (Path entry : stream) {
|
||||
allPaths.add(entry);
|
||||
stats.incrementFileCount();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("读取目录失败: {}", basePath, e);
|
||||
logger.error("读取目录失败: {}", currentDir, e);
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
@@ -492,6 +503,46 @@ public class FileController {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an existing directory below the configured demo root.
|
||||
*
|
||||
* <p>Both lexical normalization and real-path checks are required: the
|
||||
* former blocks traversal and absolute paths, while the latter prevents a
|
||||
* symlink inside the demo directory from escaping the configured root.</p>
|
||||
*/
|
||||
static Path resolveDirectoryUnderRoot(Path root, String requestedPath) throws IOException {
|
||||
Path normalizedRoot = root.toAbsolutePath().normalize();
|
||||
String relativePath = requestedPath == null ? "" : requestedPath.replace('\\', '/');
|
||||
|
||||
if (relativePath.indexOf('\0') >= 0
|
||||
|| relativePath.startsWith("/")
|
||||
|| relativePath.matches("^[A-Za-z]:.*")) {
|
||||
throw new SecurityException("Absolute paths are not allowed");
|
||||
}
|
||||
|
||||
Path relative = Paths.get(relativePath);
|
||||
if (relative.isAbsolute()) {
|
||||
throw new SecurityException("Absolute paths are not allowed");
|
||||
}
|
||||
for (Path segment : relative) {
|
||||
if ("..".equals(segment.toString())) {
|
||||
throw new SecurityException("Parent path segments are not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
Path resolved = normalizedRoot.resolve(relative).normalize();
|
||||
if (!resolved.startsWith(normalizedRoot)) {
|
||||
throw new SecurityException("Path escapes the configured root");
|
||||
}
|
||||
|
||||
Path realRoot = normalizedRoot.toRealPath();
|
||||
Path realResolved = resolved.toRealPath();
|
||||
if (!realResolved.startsWith(realRoot)) {
|
||||
throw new SecurityException("Path escapes the configured root through a symbolic link");
|
||||
}
|
||||
return realResolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建性能统计信息
|
||||
*/
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
你可以先看最新版本的升级重点,再顺着时间轴继续了解历史版本细节。
|
||||
</p>
|
||||
<div class="release-badge-row">
|
||||
<span class="tag highlight">最新版本 v5.0.0</span>
|
||||
<span class="tag brand">发布日期 2026-04-14</span>
|
||||
<span class="tag highlight">最新版本 v5.0.1</span>
|
||||
<span class="tag brand">发布日期 2026-07-13</span>
|
||||
<span class="tag warn">JDK 21+ 强制要求</span>
|
||||
<span class="tag">压缩包工作区预览 / PDF 默认模式</span>
|
||||
<span class="tag">安全补丁 / PDF、Redis、XLSX 修复</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -52,11 +52,53 @@
|
||||
<section class="release-section">
|
||||
<div class="timeline-year">2026</div>
|
||||
<div class="timeline-list">
|
||||
<article class="release-card">
|
||||
<h3>v5.0.1</h3>
|
||||
<div class="release-meta">
|
||||
<span class="tag brand">2026-07-13</span>
|
||||
<span class="tag highlight">最新稳定版本</span>
|
||||
<span class="tag warn">建议尽快升级</span>
|
||||
</div>
|
||||
<div class="release-columns">
|
||||
<div class="release-group">
|
||||
<h4>安全修复</h4>
|
||||
<ul class="release-list">
|
||||
<li>修复 <code>/addTask</code> 未覆盖信任主机和本地目录过滤导致的 SSRF 风险。</li>
|
||||
<li>修复 <code>/listFiles</code> 可越出演示目录导致的路径遍历和目录信息泄露。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="release-group">
|
||||
<h4>修复</h4>
|
||||
<ul class="release-list">
|
||||
<li>修复 PDF 跨域、页码、高亮、打印、打印水印及反向代理路径问题。</li>
|
||||
<li>修复 Redis 多种运行模式的配置兼容问题。</li>
|
||||
<li>修复 HTTP 错误处理、共享 Client 生命周期和 xlsx 数据校验解析问题。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="release-group">
|
||||
<h4>优化</h4>
|
||||
<ul class="release-list">
|
||||
<li>大型 xlsx 文件使用 Web Worker 解析,并保留主线程自动回退。</li>
|
||||
<li>新增 <code>pdf.sidebar.open</code>,支持配置 PDF 默认侧栏状态。</li>
|
||||
<li>Maven CI 增加 Linux、Windows、macOS 构建验证。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="release-group">
|
||||
<h4>升级重点</h4>
|
||||
<ul class="release-list">
|
||||
<li>建议所有 v5.0.0 及更早版本用户尽快升级。</li>
|
||||
<li>继续要求 JDK 21 及以上。</li>
|
||||
<li>现有 v5.0.0 配置可直接沿用。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="release-card">
|
||||
<h3>v5.0.0</h3>
|
||||
<div class="release-meta">
|
||||
<span class="tag brand">2026-04-14</span>
|
||||
<span class="tag highlight">最新稳定版本</span>
|
||||
<span class="tag">5.0 功能版本</span>
|
||||
<span class="tag warn">升级需 JDK 21+</span>
|
||||
</div>
|
||||
<div class="release-columns">
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.keking.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PdfToJpgServiceTests {
|
||||
|
||||
@Test
|
||||
void shouldRediscoverJbig2ReaderAfterInitialRegistryMiss() {
|
||||
IIORegistry registry = IIORegistry.getDefaultInstance();
|
||||
List<ImageReaderSpi> providers = findJbig2Providers(registry);
|
||||
assertFalse(providers.isEmpty(), "jbig2-imageio must be present on the test class path");
|
||||
|
||||
try {
|
||||
providers.forEach(registry::deregisterServiceProvider);
|
||||
assertFalse(hasJbig2Reader());
|
||||
|
||||
PdfToJpgService.refreshImageIoPlugins();
|
||||
|
||||
assertTrue(hasJbig2Reader());
|
||||
} finally {
|
||||
providers.forEach(registry::registerServiceProvider);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ImageReaderSpi> findJbig2Providers(IIORegistry registry) {
|
||||
Iterator<ImageReaderSpi> providers = registry.getServiceProviders(
|
||||
ImageReaderSpi.class,
|
||||
provider -> Arrays.stream(((ImageReaderSpi) provider).getFormatNames())
|
||||
.anyMatch("JBIG2"::equalsIgnoreCase),
|
||||
true
|
||||
);
|
||||
List<ImageReaderSpi> result = new ArrayList<>();
|
||||
providers.forEachRemaining(result::add);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean hasJbig2Reader() {
|
||||
return ImageIO.getImageReadersByFormatName("JBIG2").hasNext();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.keking.web.controller;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class FileControllerPathSecurityTests {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
private String originalFileDir;
|
||||
|
||||
@BeforeEach
|
||||
void rememberConfiguredFileDirectory() {
|
||||
originalFileDir = ConfigConstants.getFileDir();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void restoreConfiguredFileDirectory() {
|
||||
ConfigConstants.setFileDirValue(originalFileDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveDirectoriesInsideDemoRoot() throws IOException {
|
||||
Path demoRoot = Files.createDirectory(tempDir.resolve("demo"));
|
||||
Path nested = Files.createDirectories(demoRoot.resolve("folder/subfolder"));
|
||||
|
||||
assertEquals(demoRoot.toRealPath(), FileController.resolveDirectoryUnderRoot(demoRoot, ""));
|
||||
assertEquals(nested.toRealPath(), FileController.resolveDirectoryUnderRoot(demoRoot, "folder/subfolder"));
|
||||
assertEquals(nested.toRealPath(), FileController.resolveDirectoryUnderRoot(demoRoot, "folder\\subfolder"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectParentTraversalWithEitherSeparator() throws IOException {
|
||||
Path demoRoot = Files.createDirectory(tempDir.resolve("demo"));
|
||||
|
||||
assertThrows(SecurityException.class,
|
||||
() -> FileController.resolveDirectoryUnderRoot(demoRoot, "../outside"));
|
||||
assertThrows(SecurityException.class,
|
||||
() -> FileController.resolveDirectoryUnderRoot(demoRoot, "..\\outside"));
|
||||
assertThrows(SecurityException.class,
|
||||
() -> FileController.resolveDirectoryUnderRoot(demoRoot, "folder/../outside"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectAbsoluteDriveAndUncPaths() throws IOException {
|
||||
Path demoRoot = Files.createDirectory(tempDir.resolve("demo"));
|
||||
|
||||
assertThrows(SecurityException.class,
|
||||
() -> FileController.resolveDirectoryUnderRoot(demoRoot, "/etc"));
|
||||
assertThrows(SecurityException.class,
|
||||
() -> FileController.resolveDirectoryUnderRoot(demoRoot, "C:\\Windows"));
|
||||
assertThrows(SecurityException.class,
|
||||
() -> FileController.resolveDirectoryUnderRoot(demoRoot, "\\\\server\\share"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectSymlinkThatEscapesDemoRoot() throws IOException {
|
||||
Path demoRoot = Files.createDirectory(tempDir.resolve("demo"));
|
||||
Path outside = Files.createDirectory(tempDir.resolve("outside"));
|
||||
Path link = demoRoot.resolve("outside-link");
|
||||
try {
|
||||
Files.createSymbolicLink(link, outside);
|
||||
} catch (IOException | UnsupportedOperationException e) {
|
||||
Assumptions.assumeTrue(false, "Symbolic links are unavailable in this environment");
|
||||
}
|
||||
|
||||
assertThrows(SecurityException.class,
|
||||
() -> FileController.resolveDirectoryUnderRoot(demoRoot, "outside-link"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listFilesShouldNotExposeEntriesOutsideDemoRoot() throws IOException {
|
||||
Files.createDirectory(tempDir.resolve("demo"));
|
||||
Files.createFile(tempDir.resolve("outside-secret.txt"));
|
||||
ConfigConstants.setFileDirValue(tempDir.toString());
|
||||
FileController controller = new FileController();
|
||||
|
||||
Map<String, Object> result = controller.getFiles("..", "", 0, 20, null, null);
|
||||
|
||||
assertEquals("非法目录路径", result.get("error"));
|
||||
assertTrue(((List<?>) result.get("data")).isEmpty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user