@@ -23,7 +23,9 @@ This file is part of the iText (R) project.
2323package com .itextpdf .commons .utils ;
2424
2525import com .itextpdf .commons .exceptions .CommonsExceptionMessageConstant ;
26+ import com .itextpdf .commons .logs .CommonsLogMessageConstant ;
2627
28+ import java .io .BufferedInputStream ;
2729import java .io .Closeable ;
2830import java .io .IOException ;
2931import java .io .InputStream ;
@@ -33,14 +35,21 @@ This file is part of the iText (R) project.
3335import java .util .Set ;
3436import java .util .zip .ZipEntry ;
3537import java .util .zip .ZipFile ;
38+ import org .slf4j .Logger ;
39+ import org .slf4j .LoggerFactory ;
3640
3741/**
3842 * Allows reading entries from a zip file.
3943 */
4044public class ZipFileReader implements Closeable {
45+ private static final Logger LOGGER = LoggerFactory .getLogger (ZipFileReader .class );
4146
4247 private final ZipFile zipFile ;
4348
49+ private int thresholdSize = 1_000_000_000 ;
50+ private int thresholdEntries = 10000 ;
51+ private double thresholdRatio = 10 ;
52+
4453 /**
4554 * Creates an instance for zip file reading.
4655 *
@@ -59,13 +68,49 @@ public ZipFileReader(String archivePath) throws IOException {
5968 * Get all file entries paths inside the reading zip file.
6069 *
6170 * @return the {@link Set} of all file entries paths
71+ *
72+ * @throws IOException if some I/O exception occurs
6273 */
63- public Set <String > getFileNames () {
74+ public Set <String > getFileNames () throws IOException {
6475 final Set <String > fileNames = new HashSet <>();
6576
6677 final Enumeration <? extends ZipEntry > entries = zipFile .entries ();
78+
79+ int totalSizeArchive = 0 ;
80+ int totalEntryArchive = 0 ;
6781 while (entries .hasMoreElements ()) {
6882 ZipEntry entry = entries .nextElement ();
83+ boolean zipBombSuspicious = false ;
84+ try (InputStream in = new BufferedInputStream (zipFile .getInputStream (entry ))) {
85+ totalEntryArchive ++;
86+ int nBytes ;
87+ byte [] buffer = new byte [2048 ];
88+ int totalSizeEntry = 0 ;
89+ while ((nBytes = in .read (buffer )) > 0 ) {
90+ totalSizeEntry += nBytes ;
91+ totalSizeArchive += nBytes ;
92+ double compressionRatio = (double ) totalSizeEntry / entry .getCompressedSize ();
93+ if (compressionRatio > thresholdRatio ) {
94+ zipBombSuspicious = true ;
95+ break ;
96+ }
97+ }
98+ if (zipBombSuspicious ) {
99+ LOGGER .warn (MessageFormatUtil .format (CommonsLogMessageConstant .RATIO_IS_HIGHLY_SUSPICIOUS ,
100+ thresholdRatio ));
101+ break ;
102+ }
103+ if (totalSizeArchive > thresholdSize ) {
104+ LOGGER .warn (MessageFormatUtil .format (CommonsLogMessageConstant .UNCOMPRESSED_DATA_SIZE_IS_TOO_MUCH ,
105+ thresholdSize ));
106+ break ;
107+ }
108+ if (totalEntryArchive > thresholdEntries ) {
109+ LOGGER .warn (MessageFormatUtil .format (CommonsLogMessageConstant .TOO_MUCH_ENTRIES_IN_ARCHIVE ,
110+ thresholdEntries ));
111+ break ;
112+ }
113+ }
69114 if (!entry .isDirectory ()) {
70115 fileNames .add (entry .getName ());
71116 }
@@ -94,6 +139,34 @@ public InputStream readFromZip(String fileName) throws IOException {
94139 return zipFile .getInputStream (entry );
95140 }
96141
142+ /**
143+ * Sets the maximum total uncompressed data size to prevent a Zip Bomb Attack. Default value is 1 GB (1000000000).
144+ *
145+ * @param thresholdSize the threshold for maximum total size of the uncompressed data
146+ */
147+ public void setThresholdSize (int thresholdSize ) {
148+ this .thresholdSize = thresholdSize ;
149+ }
150+
151+ /**
152+ * Sets the maximum number of file entries in the archive to prevent a Zip Bomb Attack. Default value is 10000.
153+ *
154+ * @param thresholdEntries maximum number of file entries in the archive
155+ */
156+ public void setThresholdEntries (int thresholdEntries ) {
157+ this .thresholdEntries = thresholdEntries ;
158+ }
159+
160+ /**
161+ * Sets the maximum ratio between compressed and uncompressed data to prevent a Zip Bomb Attack. In general
162+ * the data compression ratio for most of the legit archives is 1 to 3. Default value is 10.
163+ *
164+ * @param thresholdRatio maximum ratio between compressed and uncompressed data
165+ */
166+ public void setThresholdRatio (double thresholdRatio ) {
167+ this .thresholdRatio = thresholdRatio ;
168+ }
169+
97170 @ Override
98171 public void close () throws IOException {
99172 zipFile .close ();
0 commit comments