com.sap.cloud.security.ams.dcl.archivetools.ArchiveExtractorBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of archive-tools Show documentation
Show all versions of archive-tools Show documentation
Tools for handling AMS bundles.
The newest version!
/************************************************************************
* © 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cloud.security.ams.dcl.archivetools;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.InvalidPathException;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
public abstract class ArchiveExtractorBase {
private final File baseDir;
private final Predicate filter;
private int extractedEntries = 0;
private long extractedBytes = 0;
private long maxEntries = -1;
private long maxBytes = -1;
private int maxFileNameLength = -1;
private byte[] buffer;
protected ArchiveExtractorBase(File baseFolder, Predicate filter) throws IOException {
this(baseFolder, filter, false);
}
protected ArchiveExtractorBase(File baseFolder, Predicate filter, boolean isCanonic)
throws IOException {
this.baseDir = isCanonic ? baseFolder : baseFolder.getCanonicalFile();
this.filter = filter;
}
public void setLimits(long maxEntries, long maxBytes, int maxFileNameLength) {
this.maxEntries = maxEntries;
this.maxBytes = maxBytes;
this.maxFileNameLength = maxFileNameLength;
}
File getBaseDir() {
return baseDir;
}
public void extract(ArchiveInputStream zis) throws IOException {
InputStream is = prepareInputStream(zis);
ArchiveEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (filter == null || filter.test(entry)) {
doExtractEntry(entry, is);
}
}
}
protected InputStream prepareInputStream(ArchiveInputStream zis) {
return zis;
}
private byte[] getBuffer() {
if (buffer == null) {
buffer = new byte[8 * 1024];
}
return buffer;
}
protected final void copyStream(InputStream in, OutputStream out) throws IOException {
long max = maxBytes >= 0 ? (maxBytes - extractedBytes + 1) : Long.MAX_VALUE; // + 1 to see, if the limit was exceeded
int length;
byte[] buf = getBuffer();
while ((length = in.read(buf, 0, (int) Math.min(buf.length, max))) > 0) {
out.write(buf, 0, length);
max -= length;
extractedBytes += length;
}
if (maxBytes >= 0 && extractedBytes > maxBytes) {
throw new ArchiveLimitExceededException("Max extracted bytes exceeds limit");
}
}
private static final char OTHER_SEPARATOR = File.separatorChar == '\\' ? '/' : '\\';
private static final String QSEP = Pattern.quote(File.separator);
private static final String FILENAME_PATTERN_STR = "[a-zA-Z0-9_\\-]*+(\\.[a-zA-Z0-9_\\-]++)?";
private static final Pattern FILE_PATH_REGEX = Pattern.compile("" + //
"^" + QSEP + "?" + //
"(?!" + QSEP + "|$|\\.$|\\." + QSEP + ")" + // Prevent separator, '.' or "./"
FILENAME_PATTERN_STR + //
"(" + //
QSEP + //
"(?!" + QSEP + "|$|\\.$|\\." + QSEP + ")" + // Prevent separator, '.' or "./"
FILENAME_PATTERN_STR + //
")*+" + //
"$");
private boolean pathStartsWithBaseDir(String pathToCheck) {
String root = baseDir.getPath();
return pathToCheck.length() > root.length()
&& pathToCheck.startsWith(root)
&& pathToCheck.charAt(root.length()) == File.separatorChar;
}
private File toCanonicalFile(String name) throws IOException {
if (maxFileNameLength != -1 && name.length() > maxFileNameLength) {
throw new ArchiveLimitExceededException("File name size limit exceeded.");
}
if (name.isEmpty()) {
throw new ArchiveLimitExceededException("File name is Empty.");
}
File file;
String n2 = name.replace(OTHER_SEPARATOR, File.separatorChar);
if (FILE_PATH_REGEX.matcher(n2).matches()) {
file = new File(baseDir, n2);
} else {
try {
file = new File(baseDir, name).getCanonicalFile();
} catch (IOException e) {
throw new ArchiveMalformedException(
"Corrupt archive format. File name contain illegal charactes (or potentially path traversals).",
e);
}
if (!pathStartsWithBaseDir(file.getPath())) {
throw new ArchiveMalformedException("Corrupt archive format. Path traversal detected.");
}
try {
file.toPath();
} catch (InvalidPathException e) {
throw new ArchiveMalformedException(
"Corrupt archive format. File name contain illegal charactes (or potentially path traversals).",
e);
}
}
return file;
}
private void doExtractEntry(ArchiveEntry entry, InputStream is) throws IOException {
extractedEntries++;
if (maxEntries >= 0 && extractedEntries > maxEntries) {
throw new ArchiveLimitExceededException("Max extries to extract exceeded");
}
File file = toCanonicalFile(entry.getName());
doExtractEntry(entry, is, file);
}
protected abstract void doExtractEntry(ArchiveEntry entry, InputStream is, File file) throws IOException;
}