All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.security.ams.dcl.archivetools.ArchiveExtractorBase Maven / Gradle / Ivy

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;

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy