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

com.redhat.insights.jars.JarAnalyzer 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.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

/**
 * Analyzes individual jars, computes SHA hashes to fingerprint jars. Attempts to open individual
 * jars and obtain more detail from manifests.
 */
public final class JarAnalyzer {

  public static final String SHA1_CHECKSUM_KEY = "sha1Checksum";
  public static final String SHA256_CHECKSUM_KEY = "sha256Checksum";
  public static final String SHA512_CHECKSUM_KEY = "sha512Checksum";
  private static final String JAR_EXTENSION = ".jar";
  static final String UNKNOWN_VERSION = " ";

  @SuppressWarnings("deprecation") // Implementation-Vendor-Id is deprecated
  private static final String[] ATTRIBUTES_TO_COLLECT =
      new String[] {
        Attributes.Name.IMPLEMENTATION_VENDOR.toString(),
        Attributes.Name.IMPLEMENTATION_VENDOR_ID.toString()
      };

  private final InsightsLogger logger;
  private final boolean skipTempJars;
  private final List ignoreJars;

  public JarAnalyzer(InsightsLogger logger, boolean skipTempJars) {
    this.logger = logger;
    this.skipTempJars = skipTempJars;
    if (!skipTempJars) {
      logger.debug("Temporary jars will be transmitted to the host");
    }
    // Config jars to ignore will go here
    ignoreJars = new ArrayList<>();
  }

  public Optional process(URL url) throws URISyntaxException {
    String jarFile = parseJarName(url);
    return process(jarFile, url);
  }

  public Optional process(String file, URL url) throws URISyntaxException {
    if (skipTempJars && isTempFile(url)) {
      logger.debug(url + " Skipping temp jar file");
      return Optional.empty();
    }
    if (isModularJdkJar(url)) {
      logger.debug(url + " Skipping JDK jar file");
      return Optional.empty();
    }
    String archive = file.toLowerCase(Locale.ROOT);
    if (!archive.endsWith(".zip")
        && !archive.endsWith(".jar")
        && !archive.endsWith(".war")
        && !archive.endsWith(".rar")
        && !archive.endsWith(".ear")
        && !"content".equals(archive)) {
      logger.debug(url + " Skipping file with non-jar extension");
      return Optional.empty();
    }

    if (shouldAttemptAdd(file)) {
      logger.debug(url + "  Adding the file " + archive + " with version");
      return Optional.of(getJarInfoSafe(file, url));
    }

    return Optional.empty();
  }

  /**
   * Returns true if the address protocol is "file" and the file resides within the temp directory.
   */
  static boolean isTempFile(URL address) throws URISyntaxException {
    if (!"file".equals(address.getProtocol())) {
      return false;
    }

    return isTempFile(new File(address.toURI()));
  }

  private static final File TEMP_DIRECTORY = new File(System.getProperty("java.io.tmpdir"));

  static boolean isTempFile(File fileParam) {
    File file = fileParam;
    while (file != null) {
      file = file.getParentFile();
      if (TEMP_DIRECTORY.equals(file)) {
        return true;
      }
    }

    return false;
  }

  JarInfo getJarInfoSafe(String jarFile, URL url) {
    Map attributes = new HashMap<>();

    setChecksumsPerf(attributes, url);

    JarInfo jarInfo;
    try {
      jarInfo = getJarInfo(jarFile, url, attributes);
    } catch (Exception e) {
      logger.debug(url + " Trouble getting version from jar: adding jar without version");
      jarInfo = new JarInfo(jarFile, UNKNOWN_VERSION, attributes);
    }

    return jarInfo;
  }

  void setChecksumsPerf(Map attributes, URL url) {

    try {
      String[] shaChecksums = JarUtils.computeSha(url);
      attributes.put(SHA1_CHECKSUM_KEY, shaChecksums[0]);
      attributes.put(SHA256_CHECKSUM_KEY, shaChecksums[1]);
      attributes.put(SHA512_CHECKSUM_KEY, shaChecksums[2]);
    } catch (Exception ex) {
      logger.error(url + " Error getting jar file sha checksum", ex);
    }
  }

  private JarInfo getJarInfo(String jarFilename, URL url, Map attributes)
      throws IOException {
    try (JarInputStream jarInputStream = JarUtils.getJarInputStream(url)) {
      try {
        getExtraAttributes(jarInputStream, attributes);

        Map pom = getPom(jarInputStream);

        // if we find exactly one pom, use it
        if (pom != null) {
          attributes.putAll(pom);
          return new JarInfo(jarFilename, pom.get("version"), attributes);
        }
      } catch (Exception ex) {
        logger.error(url + "Exception getting extra attributes or pom", ex);
      }

      String version = getVersion(jarInputStream);
      if (version == null || "".equals(version)) {
        version = UNKNOWN_VERSION;
      }

      return new JarInfo(jarFilename, version, attributes);
    }
  }

  /**
   * Returns the values from pom.properties if this file is found. If multiple pom.properties files
   * are found, return null.
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  private static Map getPom(JarInputStream jarFile) throws IOException {
    Map pom = null;

    for (JarEntry entry = jarFile.getNextJarEntry();
        entry != null;
        entry = jarFile.getNextJarEntry()) {
      if (entry.getName().startsWith("META-INF/maven")
          && entry.getName().endsWith("pom.properties")) {
        if (pom != null) {
          // we've found multiple pom files. bail!
          return null;
        }
        Properties props = new Properties();
        props.load(jarFile);

        pom = (Map) props;
      }
    }

    return pom;
  }

  static void getExtraAttributes(JarInputStream jarFile, Map map) {
    Manifest manifest = jarFile.getManifest();
    if (manifest == null) {
      return;
    }

    Attributes attributes = manifest.getMainAttributes();
    for (String name : ATTRIBUTES_TO_COLLECT) {
      String value = attributes.getValue(name);
      if (null != value) {
        map.put(name, value);
      }
    }
  }

  static String getVersion(JarInputStream jarFile) {
    Manifest manifest = jarFile.getManifest();
    if (manifest == null) {
      return null;
    }

    return JarUtils.getVersionFromManifest(manifest);
  }

  /**
   * Removes the full package from the jar name and also gets rid of spaces. This only needs to be
   * called from addJarAndVersion.
   *
   * @param url The name of the jar. This can be full path.
   * @return The name of the jar to put in the internal map.
   */
  String parseJarName(final URL url) throws URISyntaxException {
    // Skip JDK jars
    if (isModularJdkJar(url)) {
      return "";
    }

    if ("file".equals(url.getProtocol())) {
      File file = new File(url.toURI());
      return file.getName().trim();
    }

    logger.debug("Parsing jar file name " + url);
    String path = url.getFile();
    int end = path.lastIndexOf(JAR_EXTENSION);
    if (end > 0) {
      path = path.substring(0, end);
      int start = path.lastIndexOf(File.separator);
      if (start > -1) {
        return path.substring(start + 1) + JAR_EXTENSION;
      }

      return path + JAR_EXTENSION;
    }

    throw new URISyntaxException(url.getPath(), "Unable to parse the jar file name from a URL");
  }

  private boolean isModularJdkJar(URL url) {
    return "jrt".equals(url.getProtocol());
  }

  /**
   * Returns true if the jar file should be sent to the collector.
   *
   * @param jarFile The name of the jar file.
   * @return True if the jar file should be added, else false.
   */
  private boolean shouldAttemptAdd(final String jarFile) {
    return !ignoreJars.contains(jarFile);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy