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

com.redhat.insights.jars.RecursiveJarAnalyzerHelper Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/* Copyright (C) Red Hat 2023 */
package com.redhat.insights.jars;

import com.redhat.insights.logging.InsightsLogger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

/** Helper to analyze JAR files (and EAR / WAR) in a recursive fashion. */
public class RecursiveJarAnalyzerHelper {

  private final InsightsLogger logger;

  public RecursiveJarAnalyzerHelper(InsightsLogger logger) {
    this.logger = logger;
  }

  public final List listDeploymentContent(
      final JarAnalyzer analyzer,
      final Path tempDir,
      final String parentName,
      final Path deployment)
      throws IOException, URISyntaxException {
    final List jarInfos = new ArrayList<>();
    if (isArchive(deployment)) {
      Path target = createTempDirectory(tempDir, "unarchive");
      unzip(deployment, target);
      Files.walkFileTree(
          target,
          new FileVisitor() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException {
              return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException {
              if (Files.isRegularFile(file) && isArchive(file)) {
                try {
                  String path = parentName + '/' + formatPath(target.relativize(file));
                  Optional info = analyzer.process(file.toUri().toURL());
                  if (info.isPresent()) {
                    JarInfo jarInfo = info.get();
                    logger.debug("Adding the info for " + jarInfo);
                    jarInfo.attributes().put("path", path);
                    jarInfos.add(jarInfo);
                  }
                  if (listingRequired(file)) {
                    jarInfos.addAll(listDeploymentContent(analyzer, tempDir, path, file));
                  }
                } catch (URISyntaxException ex) {
                  logger.error("Error reading jar", ex);
                }
              }
              return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
              return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                throws IOException {
              return FileVisitResult.CONTINUE;
            }

            private String formatPath(Path path) {
              return path.toString().replace(File.separatorChar, '/');
            }
          });
    }
    return jarInfos;
  }

  public static final boolean listingRequired(Path zip) throws IOException {
    try (final ZipFile zipFile = new ZipFile(zip.toFile())) {
      final Enumeration entries = zipFile.entries();
      while (entries.hasMoreElements()) {
        final ZipEntry entry = entries.nextElement();
        if (!entry.isDirectory()) {
          if (isArchive(zipFile.getInputStream(entry))) {
            return true;
          }
        }
      }
    } catch (IOException e) {
      // ignore, if we cannot set it, world will not end
    }
    return false;
  }

  /**
   * Create a temporary directory with the same attributes as its parent directory.
   *
   * @param dir the path to directory in which to create the directory
   * @param prefix the prefix string to be used in generating the directory's name; may be {@code
   *     null}
   * @return the path to the newly created directory that did not exist before this method was
   *     invoked
   * @throws IOException
   */
  public static Path createTempDirectory(Path dir, String prefix) throws IOException {
    try {
      return Files.createTempDirectory(dir, prefix);
    } catch (UnsupportedOperationException ignored) {
    }
    return Files.createTempDirectory(dir, prefix);
  }

  /**
   * Test if the target path is an archive.
   *
   * @param path path to the file.
   * @return true if the path points to a zip file - false otherwise.
   * @throws IOException
   */
  public static final boolean isArchive(Path path) throws IOException {
    if (Files.exists(path) && Files.isRegularFile(path)) {
      try (ZipFile zip = new ZipFile(path.toFile())) {
        return true;
      } catch (ZipException e) {
        return false;
      }
    }
    return false;
  }

  /**
   * Test if the target path is an archive.
   *
   * @param in stream that reads the target path
   * @return true if the path points to a zip file - false otherwise.
   * @throws IOException
   */
  public static final boolean isArchive(InputStream in) throws IOException {
    if (in != null) {
      try (ZipInputStream zip = new ZipInputStream(in)) {
        return zip.getNextEntry() != null;
      } catch (ZipException e) {
        return false;
      }
    }
    return false;
  }

  /**
   * Unzip a file to a target directory.
   *
   * @param zip the path to the zip file.
   * @param target the path to the target directory into which the zip file will be unzipped.
   * @throws IOException
   */
  public void unzip(Path zip, Path target) throws IOException {
    try (final ZipFile zipFile = new ZipFile(zip.toFile())) {
      unzip(zipFile, target);
    }
  }

  private void unzip(final ZipFile zip, final Path targetDir) throws IOException {
    final Enumeration entries = zip.entries();
    while (entries.hasMoreElements()) {
      final ZipEntry entry = entries.nextElement();
      final String name = entry.getName();
      final Path current = resolveSecurely(targetDir, name);
      if (entry.isDirectory()) {
        if (!Files.exists(current)) {
          Files.createDirectories(current);
        }
      } else {
        if (Files.notExists(current.getParent())) {
          Files.createDirectories(current.getParent());
        }
        try (final InputStream eis = zip.getInputStream(entry)) {
          Files.copy(eis, current);
        }
      }
      try {
        Files.getFileAttributeView(current, BasicFileAttributeView.class)
            .setTimes(
                entry.getLastModifiedTime(), entry.getLastAccessTime(), entry.getCreationTime());
      } catch (IOException e) {
        // ignore, if we cannot set it, world will not end
      }
    }
  }

  /**
   * Resolve a path from the rootPath checking that it doesn't go out of the rootPath.
   *
   * @param rootPath the starting point for resolution.
   * @param path the path we want to resolve.
   * @return the resolved path.
   * @throws IOException if the resolved path is out of the rootPath or if the resolution failed.
   */
  public final Path resolveSecurely(Path rootPath, String path) throws IOException {
    Path resolvedPath;
    if (path == null || path.isEmpty()) {
      resolvedPath = rootPath.normalize();
    } else {
      String relativePath = removeSuperflousSlashes(path);
      resolvedPath = rootPath.resolve(relativePath).normalize();
    }
    if (!resolvedPath.startsWith(rootPath)) {
      throw new IOException(
          String.format("Access denied to the content at %s in the deployment", resolvedPath));
    }
    return resolvedPath;
  }

  private String removeSuperflousSlashes(String path) {
    if (path.startsWith("/")) {
      return removeSuperflousSlashes(path.substring(1));
    }
    return path;
  }

  /**
   * Delete a path recursively, not throwing Exception if it fails or if the path is null.
   *
   * @param path a Path pointing to a file or a directory that may not exists anymore.
   */
  public void deleteSilentlyRecursively(final Path path) {
    if (path != null) {
      try {
        deleteRecursively(path);
      } catch (IOException ioex) {
        logger.debug(String.format("Error deleting file %s", path), ioex);
      }
    }
  }

  /**
   * Delete a path recursively.
   *
   * @param path a Path pointing to a file or a directory that may not exists anymore.
   * @throws IOException
   */
  public void deleteRecursively(final Path path) throws IOException {
    logger.debug(String.format("Deleting %s recursively", path));
    if (Files.exists(path)) {
      Files.walkFileTree(
          path,
          new FileVisitor() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException {
              return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException {
              Files.delete(file);
              return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
              logger.debug(String.format("Error deleting file %s", path), exc);
              throw exc;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                throws IOException {
              Files.delete(dir);
              return FileVisitResult.CONTINUE;
            }
          });
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy