Skip to content

Commit dcb2dbf

Browse files
zipengwuzipeng-wu-sonarsource
authored andcommitted
SONAR-17183 Fix SSF-207
1 parent 85b0f80 commit dcb2dbf

File tree

3 files changed

+89
-5
lines changed

3 files changed

+89
-5
lines changed

sonar-plugin-api/src/main/java/org/sonar/api/utils/ZipUtils.java

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@
3434
import java.util.zip.ZipFile;
3535
import java.util.zip.ZipInputStream;
3636
import java.util.zip.ZipOutputStream;
37+
import javax.annotation.Nullable;
3738
import org.apache.commons.io.FileUtils;
3839
import org.apache.commons.io.IOUtils;
3940

41+
import static org.apache.commons.io.IOUtils.EOF;
42+
4043
/**
4144
* Utility to zip directories and unzip files.
4245
*
@@ -59,10 +62,31 @@ public static File unzip(File zip, File toDir) throws IOException {
5962
return unzip(zip, toDir, ze -> true);
6063
}
6164

65+
/**
66+
* Unzip a file input stream to a directory.
67+
*
68+
* @param zip the zip file input stream
69+
* @param toDir the target directory. It is created if needed.
70+
* @return the parameter {@code toDir}
71+
* @since 6.2
72+
*/
6273
public static File unzip(InputStream zip, File toDir) throws IOException {
6374
return unzip(zip, toDir, ze -> true);
6475
}
6576

77+
/**
78+
* Unzip a file input stream to a directory.
79+
*
80+
* @param zip the zip file input stream
81+
* @param toDir the target directory. It is created if needed.
82+
* @param unzipSizeThreshold The parameter to prevent unzip size to exceed threshold(in Bytes)
83+
* @return the parameter {@code toDir}
84+
* @since 9.10
85+
*/
86+
public static File unzip(InputStream zip, File toDir, long unzipSizeThreshold) throws IOException {
87+
return unzip(zip, toDir, unzipSizeThreshold, ze -> true);
88+
}
89+
6690
/**
6791
* Unzip a file to a directory.
6892
*
@@ -74,23 +98,63 @@ public static File unzip(InputStream zip, File toDir) throws IOException {
7498
* @since 6.2
7599
*/
76100
public static File unzip(InputStream stream, File toDir, Predicate<ZipEntry> filter) throws IOException {
101+
return unzip(stream, toDir, null, filter);
102+
}
103+
104+
/**
105+
* Unzip a file to a directory.
106+
*
107+
* @param stream the zip input file
108+
* @param toDir the target directory. It is created if needed.
109+
* @param unzipSizeThreshold optional parameter to prevent unzip size to exceed threshold(in Bytes)
110+
* @param filter filter zip entries so that only a subset of directories/files can be
111+
* extracted to target directory.
112+
* @return the parameter {@code toDir}
113+
* @since 9.10
114+
*/
115+
public static File unzip(InputStream stream, File toDir, @Nullable Long unzipSizeThreshold, Predicate<ZipEntry> filter) throws IOException {
77116
if (!toDir.exists()) {
78117
FileUtils.forceMkdir(toDir);
79118
}
80119

120+
long totalSizeArchive = 0;
121+
81122
Path targetDirNormalizedPath = toDir.toPath().normalize();
82123
try (ZipInputStream zipStream = new ZipInputStream(stream)) {
83124
ZipEntry entry;
84125
while ((entry = zipStream.getNextEntry()) != null) {
85126
if (filter.test(entry)) {
86-
unzipEntry(entry, zipStream, targetDirNormalizedPath);
127+
File target = getTargetFile(entry, targetDirNormalizedPath);
128+
if (target.isDirectory()) {
129+
continue;
130+
}
131+
if (unzipSizeThreshold != null) {
132+
totalSizeArchive = extractZipEntry(zipStream, target, totalSizeArchive, unzipSizeThreshold);
133+
} else {
134+
copy(zipStream, target);
135+
}
87136
}
88137
}
89138
return toDir;
90139
}
91140
}
92141

93-
private static void unzipEntry(ZipEntry entry, ZipInputStream zipStream, Path targetDirNormalized) throws IOException {
142+
private static long extractZipEntry(ZipInputStream zipStream, File target, long totalSizeArchive, long threshold) throws IOException {
143+
int nBytes = -1;
144+
byte[] buffer = new byte[8192];
145+
try (OutputStream outputStream = new FileOutputStream(target)) {
146+
while (EOF != (nBytes = zipStream.read(buffer))) {
147+
outputStream.write(buffer, 0, nBytes);
148+
totalSizeArchive += nBytes;
149+
if (totalSizeArchive > threshold) {
150+
throw new IllegalStateException(String.format("Decompression failed because unzipped size reached threshold: %s bytes", threshold));
151+
}
152+
}
153+
}
154+
return totalSizeArchive;
155+
}
156+
157+
private static File getTargetFile(ZipEntry entry, Path targetDirNormalized) throws IOException {
94158
File to = targetDirNormalized.resolve(entry.getName()).toFile();
95159
verifyInsideTargetDirectory(entry, to.toPath(), targetDirNormalized);
96160

@@ -99,8 +163,8 @@ private static void unzipEntry(ZipEntry entry, ZipInputStream zipStream, Path ta
99163
} else {
100164
File parent = to.getParentFile();
101165
throwExceptionIfDirectoryIsNotCreatable(parent);
102-
copy(zipStream, to);
103166
}
167+
return to;
104168
}
105169

106170
private static void throwExceptionIfDirectoryIsNotCreatable(File to) throws IOException {
@@ -161,7 +225,7 @@ private static void copy(ZipFile zipFile, ZipEntry entry, File to) throws IOExce
161225

162226
public static void zipDir(File dir, File zip) throws IOException {
163227
try (OutputStream out = Files.newOutputStream(zip.toPath());
164-
ZipOutputStream zout = new ZipOutputStream(out)) {
228+
ZipOutputStream zout = new ZipOutputStream(out)) {
165229
doZipDir(dir, zout);
166230
}
167231
}

sonar-plugin-api/src/test/java/org/sonar/api/utils/ZipUtilsTest.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ public void unzip_stream() throws Exception {
8989
assertThat(toDir.list()).hasSize(3);
9090
}
9191

92+
@Test
93+
public void should_throw_exception_when_size_exceed_limit() throws IOException {
94+
InputStream zip = zipBomb().openStream();
95+
File toDir = temp.newFolder();
96+
assertThatThrownBy(() -> ZipUtils.unzip(zip, toDir, 1_000_000_000))
97+
.isInstanceOf(IllegalStateException.class)
98+
.hasMessage("Decompression failed because unzipped size reached threshold: 1000000000 bytes");
99+
}
100+
101+
@Test
102+
public void unzip_stream_under_threshold() throws Exception {
103+
InputStream zip = urlToZip().openStream();
104+
File toDir = temp.newFolder();
105+
ZipUtils.unzip(zip, toDir, 1_000_000_000);
106+
assertThat(toDir.list()).hasSize(3);
107+
}
108+
92109
@Test
93110
public void fail_if_unzipping_file_outside_target_directory() throws Exception {
94111
File zip = new File(getClass().getResource("ZipUtilsTest/zip-slip.zip").toURI());
@@ -105,7 +122,6 @@ public void fail_if_unzipping_stream_outside_target_directory() throws Exception
105122
File zip = new File(getClass().getResource("ZipUtilsTest/zip-slip.zip").toURI());
106123
File toDir = temp.newFolder();
107124

108-
109125
try (InputStream input = new FileInputStream(zip)) {
110126
assertThatThrownBy(() -> ZipUtils.unzip(input, toDir))
111127
.isInstanceOf(IllegalStateException.class)
@@ -114,6 +130,10 @@ public void fail_if_unzipping_stream_outside_target_directory() throws Exception
114130
}
115131
}
116132

133+
private URL zipBomb() {
134+
return getClass().getResource("/org/sonar/api/utils/ZipUtilsTest/zip-bomb.zip");
135+
}
136+
117137
private URL urlToZip() {
118138
return getClass().getResource("/org/sonar/api/utils/ZipUtilsTest/shouldUnzipFile.zip");
119139
}
Binary file not shown.

0 commit comments

Comments
 (0)