mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-09-13 16:34:53 +00:00
Compare commits
1 Commits
v5.0.1
...
codex/add-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7323447468 |
@@ -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.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"]
|
||||
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"]
|
||||
|
||||
23
README.cn.md
23
README.cn.md
@@ -149,29 +149,6 @@ 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,29 +65,6 @@ 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
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>cn.keking</groupId>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<version>5.0.1</version>
|
||||
<version>5.0.0</version>
|
||||
|
||||
<properties>
|
||||
<!-- ========== Java 和编译配置 ========== -->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>kkFileView-parent</artifactId>
|
||||
<groupId>cn.keking</groupId>
|
||||
<version>5.0.1</version>
|
||||
<version>5.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>kkFileView</artifactId>
|
||||
|
||||
@@ -29,7 +29,6 @@ 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;
|
||||
@@ -342,23 +341,13 @@ public class FileController {
|
||||
}
|
||||
|
||||
// ==================== 2. 构建路径和验证 ====================
|
||||
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;
|
||||
String basePath = fileDir + demoPath;
|
||||
if (!ObjectUtils.isEmpty(path)) {
|
||||
basePath += path + File.separator;
|
||||
}
|
||||
|
||||
if (!Files.isDirectory(currentDir)) {
|
||||
File currentDir = new File(basePath);
|
||||
if (!currentDir.exists() || !currentDir.isDirectory()) {
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
@@ -368,13 +357,13 @@ public class FileController {
|
||||
List<Path> allPaths = new ArrayList<>();
|
||||
long collectStartTime = System.currentTimeMillis();
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(currentDir)) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(basePath))) {
|
||||
for (Path entry : stream) {
|
||||
allPaths.add(entry);
|
||||
stats.incrementFileCount();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("读取目录失败: {}", currentDir, e);
|
||||
logger.error("读取目录失败: {}", basePath, e);
|
||||
result.put("total", 0);
|
||||
result.put("data", Collections.emptyList());
|
||||
return result;
|
||||
@@ -503,46 +492,6 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建性能统计信息
|
||||
*/
|
||||
@@ -811,4 +760,4 @@ public class FileController {
|
||||
File file = new File(fullPath + fileName);
|
||||
return file.exists();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,10 @@
|
||||
你可以先看最新版本的升级重点,再顺着时间轴继续了解历史版本细节。
|
||||
</p>
|
||||
<div class="release-badge-row">
|
||||
<span class="tag highlight">最新版本 v5.0.1</span>
|
||||
<span class="tag brand">发布日期 2026-07-13</span>
|
||||
<span class="tag highlight">最新版本 v5.0.0</span>
|
||||
<span class="tag brand">发布日期 2026-04-14</span>
|
||||
<span class="tag warn">JDK 21+ 强制要求</span>
|
||||
<span class="tag">安全补丁 / PDF、Redis、XLSX 修复</span>
|
||||
<span class="tag">压缩包工作区预览 / PDF 默认模式</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -52,53 +52,11 @@
|
||||
<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">5.0 功能版本</span>
|
||||
<span class="tag highlight">最新稳定版本</span>
|
||||
<span class="tag warn">升级需 JDK 21+</span>
|
||||
</div>
|
||||
<div class="release-columns">
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
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