重构解压缩逻辑,fix #553

This commit is contained in:
kl 2024-04-16 20:04:50 +08:00
parent 42f0e079f0
commit 421a2760d5
2 changed files with 71 additions and 66 deletions

View File

@ -12,12 +12,18 @@ import net.sf.sevenzipjbinding.SevenZipException;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream; import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive; import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem; import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component; 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.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -37,73 +43,67 @@ public class CompressFileReader {
public String unRar(String filePath, String filePassword, String fileName, FileAttribute fileAttribute) throws Exception { public String unRar(String filePath, String filePassword, String fileName, FileAttribute fileAttribute) throws Exception {
List<String> imgUrls = new ArrayList<>(); List<String> imgUrls = new ArrayList<>();
String baseUrl = BaseUrlFilter.getBaseUrl(); String baseUrl = BaseUrlFilter.getBaseUrl();
String packagePath = "_"; //防止文件名重复 压缩包统一生成文件添加_符号 String packagePath = "_";
String folderName = filePath.replace(fileDir, ""); //修复压缩包 多重目录获取路径错误 String folderName = filePath.replace(fileDir, ""); //修复压缩包 多重目录获取路径错误
if (fileAttribute.isCompressFile()) { //压缩包文件 直接赋予路径 不予下载 if (fileAttribute.isCompressFile()) {
folderName = "_decompression" + folderName; //重新修改多重压缩包 生成文件路径 folderName = "_decompression" + folderName;
} }
RandomAccessFile randomAccessFile = null;
IInArchive inArchive = null; Path folderPath = Paths.get(fileDir, folderName + packagePath);
try { Files.createDirectories(folderPath);
randomAccessFile = new RandomAccessFile(filePath, "r");
inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile)); try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r");
IInArchive inArchive = SevenZip.openInArchive(null, new RandomAccessFileInStream(randomAccessFile))) {
ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface(); ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
final String[] str = {null};
for (final ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) { for (final ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
if (!item.isFolder()) { if (!item.isFolder()) {
ExtractOperationResult result; final Path filePathInsideArchive = getFilePathInsideArchive(item, folderPath);
String finalFolderName = folderName; ExtractOperationResult result = item.extractSlow(data -> {
result = item.extractSlow(data -> { try (OutputStream out = new BufferedOutputStream(new FileOutputStream(filePathInsideArchive.toFile(), true))) {
try { out.write(data);
str[0] = RarUtils.getUtf8String(item.getPath()); } catch (IOException e) {
if (RarUtils.isMessyCode(str[0])) { throw new RuntimeException(e);
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);
} }
return data.length; return data.length;
}, filePassword); }, filePassword);
if (result == ExtractOperationResult.OK) {
FileType type = FileType.typeFromUrl(str[0]); if (result != ExtractOperationResult.OK) {
if (type.equals(FileType.PICTURE)) { throw new Exception("Failed to extract RAR file.");
imgUrls.add(baseUrl + folderName + packagePath + "/" + str[0].replace("\\", "/")); }
}
fileHandlerService.putImgCache(fileName + packagePath, imgUrls); FileType type = FileType.typeFromUrl(filePathInsideArchive.toString());
} else { if (type.equals(FileType.PICTURE)) {
return null; imgUrls.add(baseUrl + folderPath.relativize(filePathInsideArchive).toString().replace("\\", "/"));
} }
} }
} }
return folderName + packagePath; fileHandlerService.putImgCache(fileName + packagePath, imgUrls);
} catch (Exception e) { } catch (Exception e) {
throw new Exception(e); throw new Exception("Error processing RAR file: " + e.getMessage(), 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);
}
}
} }
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;
}
} }

View File

@ -10,6 +10,7 @@ import cn.keking.service.CompressFileReader;
import cn.keking.utils.KkFileUtils; import cn.keking.utils.KkFileUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.poi.EncryptedDocumentException; import org.apache.poi.EncryptedDocumentException;
import org.slf4j.Logger;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
@ -28,6 +29,9 @@ public class CompressFilePreviewImpl implements FilePreview {
private final CompressFileReader compressFileReader; private final CompressFileReader compressFileReader;
private final OtherFilePreviewImpl otherFilePreview; private final OtherFilePreviewImpl otherFilePreview;
private static final String Rar_PASSWORD_MSG = "password"; 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) { public CompressFilePreviewImpl(FileHandlerService fileHandlerService, CompressFileReader compressFileReader, OtherFilePreviewImpl otherFilePreview) {
this.fileHandlerService = fileHandlerService; this.fileHandlerService = fileHandlerService;
this.compressFileReader = compressFileReader; this.compressFileReader = compressFileReader;
@ -36,20 +40,21 @@ public class CompressFilePreviewImpl implements FilePreview {
@Override @Override
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) { public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
String fileName=fileAttribute.getName(); String fileName = fileAttribute.getName();
String filePassword = fileAttribute.getFilePassword(); String filePassword = fileAttribute.getFilePassword();
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
String fileTree = null; String fileTree = null;
// 判断文件名是否存在(redis缓存读取) // 判断文件名是否存在(redis缓存读取)
if (forceUpdatedCache || !StringUtils.hasText(fileHandlerService.getConvertedFile(fileName)) || !ConfigConstants.isCacheEnabled()) { if (forceUpdatedCache || !StringUtils.hasText(fileHandlerService.getConvertedFile(fileName)) || !ConfigConstants.isCacheEnabled()) {
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName); ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
if (response.isFailure()) { if (response.isFailure()) {
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg()); return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
} }
String filePath = response.getContent(); String filePath = response.getContent();
try { try {
fileTree = compressFileReader.unRar(filePath, filePassword,fileName, fileAttribute); fileTree = compressFileReader.unRar(filePath, filePassword, fileName, fileAttribute);
} catch (Exception e) { } catch (Exception e) {
logger.error("Error processing RAR file: " + e.getMessage(), e);
Throwable[] throwableArray = ExceptionUtils.getThrowables(e); Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
for (Throwable throwable : throwableArray) { for (Throwable throwable : throwableArray) {
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) { if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
@ -69,14 +74,14 @@ public class CompressFilePreviewImpl implements FilePreview {
// 加入缓存 // 加入缓存
fileHandlerService.addConvertedFile(fileName, fileTree); fileHandlerService.addConvertedFile(fileName, fileTree);
} }
}else { } else {
return otherFilePreview.notSupportedFile(model, fileAttribute, "压缩文件密码错误! 压缩文件损坏! 压缩文件类型不受支持!"); return otherFilePreview.notSupportedFile(model, fileAttribute, "该压缩包文件无法处理!");
} }
} else { } else {
fileTree = fileHandlerService.getConvertedFile(fileName); fileTree = fileHandlerService.getConvertedFile(fileName);
} }
model.addAttribute("fileName", fileName); model.addAttribute("fileName", fileName);
model.addAttribute("fileTree", fileTree); model.addAttribute("fileTree", fileTree);
return COMPRESS_FILE_PREVIEW_PAGE; return COMPRESS_FILE_PREVIEW_PAGE;
} }
} }