今天突然遇到一个现场提出的问题:说导入文件导入不成功,咋突然有这个问题
2023-05-23 14:19:11,174 ERROR [http-nio-9104-exec-5] o.a.c.c.C.[.[.[.[dispatcherServlet] [DirectJDKLog.java : 175] Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.apache.poi.ooxml.POIXMLException: Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.
This may indicate that the file is used to inflate memory usage and thus could pose a security risk.
You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit.
Uncompressed size: 2048444, Raw/compressed size: 20480, ratio: 0.009998
Limits: MIN_INFLATE_RATIO: 0.010000, Entry: xl/drawings/drawing1.xml] with root cause
java.io.IOException: Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.
This may indicate that the file is used to inflate memory usage and thus could pose a security risk.
You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit.
Uncompressed size: 2048444, Raw/compressed size: 20480, ratio: 0.009998
Limits: MIN_INFLATE_RATIO: 0.010000, Entry: xl/drawings/drawing1.xml
定位代码,源头是new XSSFWorkbook(文件) 爆出的错
上面的翻译过来大概是这样:
在提示我们调整比例。
下面我们来模拟和解决
代码:
public static void main(String[] args) throws Exception {
//ZipSecureFile.setMinInflateRatio(0.001);
File file = new File("C:/Users/Lenovo/Desktop/a/1.xlsx");
InputStream inputStream = new FileInputStream(file);
XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
FileOutputStream outputStream = new FileOutputStream(new File("C:/Users/Lenovo/Desktop/a/76543.xlsx"));
workbook.write(outputStream);
workbook.close();
outputStream.close();
}
这里的文件 记住
只有sheet1有使用,是导入的文件内容,Sheet2,Sheet3是空白,也没有使用,下载模板不知道为什么要下载下来。再说sheet1不一样写一个标识名称吗? 哎,新手上路,主要安全啊。规范点,专业点。
你看看,这不是报错了,而且导入sheet1中只有50条记录,也不是很多XSSFWorkbook话说支持百万,只要内存支持,咋可能40几条就干倒了。
看提示什么压缩爆炸,第一次遇到,然后就排查,源头是里面有一个压缩爆炸的判断
package org.apache.poi.openxml4j.util;
import static org.apache.poi.openxml4j.util.ZipSecureFile.MAX_ENTRY_SIZE;
import static org.apache.poi.openxml4j.util.ZipSecureFile.MIN_INFLATE_RATIO;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.zip.ZipException;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.utils.InputStreamStatistics;
import org.apache.poi.openxml4j.exceptions.NotOfficeXmlFileException;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
@Internal
public class ZipArchiveThresholdInputStream extends FilterInputStream {
// don't alert for expanded sizes smaller than 100k
private static final long GRACE_ENTRY_SIZE = 100*1024L;
private static final String MAX_ENTRY_SIZE_MSG =
"Zip bomb detected! The file would exceed the max size of the expanded data in the zip-file.\n" +
"This may indicates that the file is used to inflate memory usage and thus could pose a security risk.\n" +
"You can adjust this limit via ZipSecureFile.setMaxEntrySize() if you need to work with files which are very large.\n" +
"Uncompressed size: %d, Raw/compressed size: %d\n" +
"Limits: MAX_ENTRY_SIZE: %d, Entry: %s";
private static final String MIN_INFLATE_RATIO_MSG =
"Zip bomb detected! The file would exceed the max. ratio of compressed file size to the size of the expanded data.\n" +
"This may indicate that the file is used to inflate memory usage and thus could pose a security risk.\n" +
"You can adjust this limit via ZipSecureFile.setMinInflateRatio() if you need to work with files which exceed this limit.\n" +
"Uncompressed size: %d, Raw/compressed size: %d, ratio: %f\n" +
"Limits: MIN_INFLATE_RATIO: %f, Entry: %s";
/**
* the reference to the current entry is only used for a more detailed log message in case of an error
*/
private ZipArchiveEntry entry;
private boolean guardState = true;
public ZipArchiveThresholdInputStream(InputStream is) {
super(is);
if (!(is instanceof InputStreamStatistics)) {
throw new IllegalArgumentException("InputStream of class "+is.getClass()+" is not implementing InputStreamStatistics.");
}
}
@Override
public int read() throws IOException {
int b = super.read();
if (b > -1) {
checkThreshold();
}
return b;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int cnt = super.read(b, off, len);
if (cnt > -1) {
checkThreshold();
}
return cnt;
}
@Override
public long skip(long n) throws IOException {
long cnt = IOUtils.skipFully(super.in, n);
if (cnt > 0) {
checkThreshold();
}
return cnt;
}
/**
* De-/activate threshold check.
* A disabled guard might make sense, when POI is processing its own temporary data (see #59743)
*
* @param guardState {@code true} (= default) enables the threshold check
*/
public void setGuardState(boolean guardState) {
this.guardState = guardState;
}
private void checkThreshold() throws IOException {
if (!guardState) {
return;
}
final InputStreamStatistics stats = (InputStreamStatistics)in;
final long payloadSize = stats.getUncompressedCount();
long rawSize;
try {
rawSize = stats.getCompressedCount();
} catch (NullPointerException e) {
// this can happen with a very specially crafted file
// see https://issues.apache.org/jira/browse/COMPRESS-598 for a related bug-report
// therefore we try to handle this gracefully for now
// this try/catch can be removed when COMPRESS-598 is fixed
rawSize = 0;
}
final String entryName = entry == null ? "not set" : entry.getName();
// check the file size first, in case we are working on uncompressed streams
if(payloadSize > MAX_ENTRY_SIZE) {
throw new IOException(String.format(Locale.ROOT, MAX_ENTRY_SIZE_MSG, payloadSize, rawSize, MAX_ENTRY_SIZE, entryName));
}
// don't alert for small expanded size
if (payloadSize <= GRACE_ENTRY_SIZE) {
return;
}
double ratio = rawSize / (double)payloadSize;
if (ratio >= MIN_INFLATE_RATIO) {
return;
}
// one of the limits was reached, report it
throw new IOException(String.format(Locale.ROOT, MIN_INFLATE_RATIO_MSG, payloadSize, rawSize, ratio, MIN_INFLATE_RATIO, entryName));
}
ZipArchiveEntry getNextEntry() throws IOException {
if (!(in instanceof ZipArchiveInputStream)) {
throw new IllegalStateException("getNextEntry() is only allowed for stream based zip processing.");
}
try {
entry = ((ZipArchiveInputStream) in).getNextZipEntry();
return entry;
} catch (ZipException ze) {
if (ze.getMessage().startsWith("Unexpected record signature")) {
throw new NotOfficeXmlFileException(
"No valid entries or contents found, this is not a valid OOXML (Office Open XML) file", ze);
}
throw ze;
} catch (EOFException e) {
return null;
}
}
/**
* Sets the zip entry for a detailed logging
* @param entry the entry
*/
void setEntry(ZipArchiveEntry entry) {
this.entry = entry;
}
}
文件也不大啊,40几条,咋就爆炸了,看提示调整参数值,也就是触发爆炸比例,让不报错
ZipSecureFile.setMinInflateRatio(0.001);
主要要加载报错代码执行
如下:
public static void main(String[] args) throws Exception {
ZipSecureFile.setMinInflateRatio(0.001);
File file = new File("C:/Users/Lenovo/Desktop/a/1.xlsx");
InputStream inputStream = new FileInputStream(file);
XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
FileOutputStream outputStream = new FileOutputStream(new File("C:/Users/Lenovo/Desktop/a/76543.xlsx"));
workbook.write(outputStream);
workbook.close();
outputStream.close();
}
运行后正常,确实没有报爆炸的错误,对应文件也可以查看
准备和对应开发人,说先试一下
与此同时,我觉得,以前并没有遇到这样的问题啊,这文件也不大,在看看文件里面的Sheet2,Sheet3都是空白的,也会进入压缩,抱着试一试的心态,就去掉多余模板的sheet2,sheet3,也不是道复制哪边的,太不认真了,再次执行原来的代码
public static void main(String[] args) throws Exception {
File file = new File("C:/Users/Lenovo/Desktop/a/2.xlsx");
InputStream inputStream = new FileInputStream(file);
XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
FileOutputStream outputStream = new FileOutputStream(new File("C:/Users/Lenovo/Desktop/a/76543.xlsx"));
workbook.write(outputStream);
workbook.close();
outputStream.close();
}
运行竟然没有报错,注意2.xlsx中只有Sheet1,要导入的内容。
我然后再50条基础上,增加了10倍,500,再次运行,还是正常的。
所有最终的方案,是去掉多余的sheet2,sheet3,调整导入模板,减少压缩占比。
但是后来,又新增Sheet2,sheet3,再次调用代码,发现又可以使用
这时候只能定位是模板中Sheet2,sheet3格式有问题,最终的解决方案也是调整模板,但是了解了一个压缩爆炸的思想,还是挺有意思的。
总结:
1. 查看模板是不是有问题
2.调整触发压缩爆炸的参数ZipSecureFile.setMinInflateRatio(0.001);