Import error: Limits: MIN_INFLATE_RATIO: 0.010000, Entry: xl/drawings/drawing1.xml

Today, I suddenly encountered a question raised on the spot: it said that the import file failed to import, why did I suddenly have this problem?

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

Locate the code, the source is the error of new XSSFWorkbook (file)

The translation of the above is roughly like this:

Prompt us to adjust the scale.

Let’s simulate and solve
code:

 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();
    }

Remember the files here

Only sheet1 is used, which is the content of the imported file. Sheet2 and Sheet3 are blank and not used. I don’t know why the download template should be downloaded. Besides, sheet1 is not the same as writing an identification name? Hey, beginners on the road, the main safety ah. Standardize, be professional.

You see, this is not an error, and there are only 50 records in the imported sheet1, and there are not many XSSFWorkbooks that support millions. As long as the memory supports it, maybe 40 or so records will fail.

Look at the hint about the compression explosion, the first time you encounter it, and then check it out, the source is that there is a compression explosion inside

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.\\
" +
        "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.setMaxEntrySize() if you need to work with files which are very large.\\
" +
        "Uncompressed size: %d, Raw/compressed size: %d\\
" +
        "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.\\
" +
        "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: %d, Raw/compressed size: %d, ratio: %f\\
" +
        "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 record
     * @param entry the entry
     */
    void setEntry(ZipArchiveEntry entry) {
        this.entry = entry;
    }
}

The file is not too big, there are more than 40 items, and it exploded. Look at the prompt to adjust the parameter value, that is, the trigger explosion ratio, so that no error is reported

ZipSecureFile.setMinInflateRatio(0.001);

Mainly to load error code execution

as follows:

 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();
    }

After running normally, there is indeed no error of explosion, and the corresponding files can also be viewed

Prepare and correspond to the developer, say to try it first

At the same time, I think that I haven’t encountered such a problem before, and the file is not too big. Looking at the Sheet2 and Sheet3 in the file, they are all blank, and they will also enter the compression. With the mentality of giving it a try, Just remove the sheet2 and sheet3 of the redundant template, and it’s not about copying which side, it’s too careless, execute the original code again

 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();
    }

The operation did not report an error. Note that there is only Sheet1 in 2.xlsx, the content to be imported.

I then added 10 times to 500 on the basis of 50, and ran it again, and it was still normal.

The final solution is to remove redundant sheet2 and sheet3, adjust the import template, and reduce the compression ratio.

But later, add Sheet2, sheet3, call the code again, and find that it can be used again

At this time, it can only be located that there is a problem with the format of Sheet2 and Sheet3 in the template. The final solution is to adjust the template, but it is quite interesting to understand the idea of a compression explosion.

Summarize:

1. Check if there is a problem with the template

2. Adjust the parameter ZipSecureFile.setMinInflateRatio(0.001) that triggers the compression explosion;