From 421a2760d58ccaba4426b5e104938ca06cc49778 Mon Sep 17 00:00:00 2001 From: kl Date: Tue, 16 Apr 2024 20:04:50 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=A7=A3=E5=8E=8B=E7=BC=A9?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8Cfix=20=20#553?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/keking/service/CompressFileReader.java | 114 +++++++++--------- .../service/impl/CompressFilePreviewImpl.java | 23 ++-- 2 files changed, 71 insertions(+), 66 deletions(-) diff --git a/server/src/main/java/cn/keking/service/CompressFileReader.java b/server/src/main/java/cn/keking/service/CompressFileReader.java index 6f8e6e08..0581f169 100644 --- a/server/src/main/java/cn/keking/service/CompressFileReader.java +++ b/server/src/main/java/cn/keking/service/CompressFileReader.java @@ -12,12 +12,18 @@ import net.sf.sevenzipjbinding.SevenZipException; import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; import net.sf.sevenzipjbinding.simple.ISimpleInArchive; import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; -import org.apache.commons.io.IOUtils; import org.springframework.stereotype.Component; -import org.springframework.util.ObjectUtils; -import java.io.*; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -37,73 +43,67 @@ public class CompressFileReader { public String unRar(String filePath, String filePassword, String fileName, FileAttribute fileAttribute) throws Exception { List imgUrls = new ArrayList<>(); String baseUrl = BaseUrlFilter.getBaseUrl(); - String packagePath = "_"; //防止文件名重复 压缩包统一生成文件添加_符号 + String packagePath = "_"; String folderName = filePath.replace(fileDir, ""); //修复压缩包 多重目录获取路径错误 - if (fileAttribute.isCompressFile()) { //压缩包文件 直接赋予路径 不予下载 - folderName = "_decompression" + folderName; //重新修改多重压缩包 生成文件路径 + if (fileAttribute.isCompressFile()) { + folderName = "_decompression" + folderName; } - RandomAccessFile randomAccessFile = null; - IInArchive inArchive = null; - try { - randomAccessFile = new RandomAccessFile(filePath, "r"); - inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile)); + + Path folderPath = Paths.get(fileDir, folderName + packagePath); + Files.createDirectories(folderPath); + + try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r"); + IInArchive inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile))) { + ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface(); - final String[] str = {null}; for (final ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { if (!item.isFolder()) { - ExtractOperationResult result; - String finalFolderName = folderName; - result = item.extractSlow(data -> { - try { - str[0] = RarUtils.getUtf8String(item.getPath()); - if (RarUtils.isMessyCode(str[0])) { - str[0] = new String(item.getPath().getBytes(StandardCharsets.ISO_8859_1), "gbk"); - } - str[0] = str[0].replace("\\", File.separator); //Linux 下路径错误 - String str1 = str[0].substring(0, str[0].lastIndexOf(File.separator) + 1); - File file = new File(fileDir, finalFolderName + packagePath + File.separator + str1); - if (!file.exists()) { - file.mkdirs(); - } - OutputStream out = new FileOutputStream(fileDir + finalFolderName + packagePath + File.separator + str[0], true); - IOUtils.write(data, out); - out.close(); - } catch (Exception e) { - e.printStackTrace(); - return Integer.parseInt(null); + final Path filePathInsideArchive = getFilePathInsideArchive(item, folderPath); + ExtractOperationResult result = item.extractSlow(data -> { + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(filePathInsideArchive.toFile(), true))) { + out.write(data); + } catch (IOException e) { + throw new RuntimeException(e); } return data.length; }, filePassword); - if (result == ExtractOperationResult.OK) { - FileType type = FileType.typeFromUrl(str[0]); - if (type.equals(FileType.PICTURE)) { - imgUrls.add(baseUrl + folderName + packagePath + "/" + str[0].replace("\\", "/")); - } - fileHandlerService.putImgCache(fileName + packagePath, imgUrls); - } else { - return null; + + if (result != ExtractOperationResult.OK) { + throw new Exception("Failed to extract RAR file."); + } + + FileType type = FileType.typeFromUrl(filePathInsideArchive.toString()); + if (type.equals(FileType.PICTURE)) { + imgUrls.add(baseUrl + folderPath.relativize(filePathInsideArchive).toString().replace("\\", "/")); } } } - return folderName + packagePath; + fileHandlerService.putImgCache(fileName + packagePath, imgUrls); } catch (Exception e) { - throw new Exception(e); - } finally { - if (inArchive != null) { - try { - inArchive.close(); - } catch (SevenZipException e) { - System.err.println("Error closing archive: " + e); - } - } - if (randomAccessFile != null) { - try { - randomAccessFile.close(); - } catch (IOException e) { - System.err.println("Error closing file: " + e); - } - } + throw new Exception("Error processing RAR file: " + e.getMessage(), e); } + return folderName + packagePath; } + private Path getFilePathInsideArchive(ISimpleInArchiveItem item, Path folderPath) throws SevenZipException, UnsupportedEncodingException { + String insideFileName = RarUtils.getUtf8String(item.getPath()); + if (RarUtils.isMessyCode(insideFileName)) { + insideFileName = new String(item.getPath().getBytes(StandardCharsets.ISO_8859_1), "gbk"); + } + + // 正规化路径并验证是否安全 + Path normalizedPath = folderPath.resolve(insideFileName).normalize(); + if (!normalizedPath.startsWith(folderPath)) { + throw new SecurityException("Unsafe path detected: " + insideFileName); + } + + try { + Files.createDirectories(normalizedPath.getParent()); + } catch (IOException e) { + throw new RuntimeException("Failed to create directory: " + normalizedPath.getParent(), e); + } + return normalizedPath; + } + + } diff --git a/server/src/main/java/cn/keking/service/impl/CompressFilePreviewImpl.java b/server/src/main/java/cn/keking/service/impl/CompressFilePreviewImpl.java index 16c5522d..e73fb7b2 100644 --- a/server/src/main/java/cn/keking/service/impl/CompressFilePreviewImpl.java +++ b/server/src/main/java/cn/keking/service/impl/CompressFilePreviewImpl.java @@ -10,6 +10,7 @@ import cn.keking.service.CompressFileReader; import cn.keking.utils.KkFileUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.poi.EncryptedDocumentException; +import org.slf4j.Logger; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import org.springframework.util.ObjectUtils; @@ -28,6 +29,9 @@ public class CompressFilePreviewImpl implements FilePreview { private final CompressFileReader compressFileReader; private final OtherFilePreviewImpl otherFilePreview; private static final String Rar_PASSWORD_MSG = "password"; + private static final Logger logger = org.slf4j.LoggerFactory.getLogger(CompressFileReader.class); + + public CompressFilePreviewImpl(FileHandlerService fileHandlerService, CompressFileReader compressFileReader, OtherFilePreviewImpl otherFilePreview) { this.fileHandlerService = fileHandlerService; this.compressFileReader = compressFileReader; @@ -36,20 +40,21 @@ public class CompressFilePreviewImpl implements FilePreview { @Override public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) { - String fileName=fileAttribute.getName(); + String fileName = fileAttribute.getName(); String filePassword = fileAttribute.getFilePassword(); - boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); + boolean forceUpdatedCache = fileAttribute.forceUpdatedCache(); String fileTree = null; // 判断文件名是否存在(redis缓存读取) - if (forceUpdatedCache || !StringUtils.hasText(fileHandlerService.getConvertedFile(fileName)) || !ConfigConstants.isCacheEnabled()) { + if (forceUpdatedCache || !StringUtils.hasText(fileHandlerService.getConvertedFile(fileName)) || !ConfigConstants.isCacheEnabled()) { ReturnResponse response = DownloadUtils.downLoad(fileAttribute, fileName); if (response.isFailure()) { return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg()); } String filePath = response.getContent(); try { - fileTree = compressFileReader.unRar(filePath, filePassword,fileName, fileAttribute); + fileTree = compressFileReader.unRar(filePath, filePassword, fileName, fileAttribute); } catch (Exception e) { + logger.error("Error processing RAR file: " + e.getMessage(), e); Throwable[] throwableArray = ExceptionUtils.getThrowables(e); for (Throwable throwable : throwableArray) { if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) { @@ -69,14 +74,14 @@ public class CompressFilePreviewImpl implements FilePreview { // 加入缓存 fileHandlerService.addConvertedFile(fileName, fileTree); } - }else { - return otherFilePreview.notSupportedFile(model, fileAttribute, "压缩文件密码错误! 压缩文件损坏! 压缩文件类型不受支持!"); + } else { + return otherFilePreview.notSupportedFile(model, fileAttribute, "该压缩包文件无法处理!"); } } else { fileTree = fileHandlerService.getConvertedFile(fileName); } - model.addAttribute("fileName", fileName); - model.addAttribute("fileTree", fileTree); - return COMPRESS_FILE_PREVIEW_PAGE; + model.addAttribute("fileName", fileName); + model.addAttribute("fileTree", fileTree); + return COMPRESS_FILE_PREVIEW_PAGE; } }