<commit_before>/*
 * This file is part of Dependency-Check.
 *
 * Dependency-Check is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * Dependency-Check is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * Dependency-Check. If not, see http://www.gnu.org/licenses/.
 *
 * Copyright (c) 2012 <NAME>. All Rights Reserved.
 */
package org.owasp.dependencycheck.analyzer;

import java.io.File;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBException;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import org.jsoup.Jsoup;
import org.owasp.dependencycheck.analyzer.pom.generated.License;
import org.owasp.dependencycheck.analyzer.pom.generated.Model;
import org.owasp.dependencycheck.analyzer.pom.generated.Organization;
import org.owasp.dependencycheck.utils.NonClosingStream;
import org.owasp.dependencycheck.utils.Settings;

/**
 *
 * Used to load a JAR file and collect information that can be used to determine
 * the associated CPE.
 *
 * @author <NAME> (<EMAIL>)
 */
public class JarAnalyzer extends AbstractAnalyzer implements Analyzer {

    /**
     * The system independent newline character.
     */
    private static final String NEWLINE = System.getProperty("line.separator");
    /**
     * The name of the analyzer.
     */
    private static final String ANALYZER_NAME = "Jar Analyzer";
    /**
     * The phase that this analyzer is intended to run in.
     */
    private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
    /**
     * A list of elements in the manifest to ignore.
     */
    private static final Set<String> IGNORE_LIST = newHashSet(
            "built-by",
            "created-by",
            "builtby",
            "createdby",
            "build-jdk",
            "buildjdk",
            "ant-version",
            "antversion",
            "import-package",
            "export-package",
            "importpackage",
            "exportpackage",
            "sealed",
            "manifest-version",
            "archiver-version",
            "manifestversion",
            "archiverversion",
            "classpath",
            "class-path",
            "tool",
            "bundle-manifestversion",
            "bundlemanifestversion",
            "include-resource");
    /**
     * The set of file extensions supported by this analyzer.
     */
    private static final Set<String> EXTENSIONS = newHashSet("jar");
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_VERSION = "Bundle-Version"; //: 2.1.2
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_DESCRIPTION = "Bundle-Description"; //: Apache Struts 2
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_NAME = "Bundle-Name"; //: Struts 2 Core
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_VENDOR = "Bundle-Vendor"; //: Apache Software Foundation
    /**
     * The unmarshaller used to parse the pom.xml from a JAR file.
     */
    private Unmarshaller pomUnmarshaller;

    /**
     * Constructs a new JarAnalyzer.
     */
    public JarAnalyzer() {
        try {
            final JAXBContext jaxbContext = JAXBContext.newInstance("org.owasp.dependencycheck.analyzer.pom.generated");
            pomUnmarshaller = jaxbContext.createUnmarshaller();
        } catch (JAXBException ex) { //guess we will just have a null pointer exception later...
            Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Returns a list of file EXTENSIONS supported by this analyzer.
     *
     * @return a list of file EXTENSIONS supported by this analyzer.
     */
    public Set<String> getSupportedExtensions() {
        return EXTENSIONS;
    }

    /**
     * Returns the name of the analyzer.
     *
     * @return the name of the analyzer.
     */
    public String getName() {
        return ANALYZER_NAME;
    }

    /**
     * Returns whether or not this analyzer can process the given extension.
     *
     * @param extension the file extension to test for support.
     * @return whether or not the specified file extension is supported by this
     * analyzer.
     */
    public boolean supportsExtension(String extension) {
        return EXTENSIONS.contains(extension);
    }

    /**
     * Returns the phase that the analyzer is intended to run in.
     *
     * @return the phase that the analyzer is intended to run in.
     */
    public AnalysisPhase getAnalysisPhase() {
        return ANALYSIS_PHASE;
    }

    /**
     * Loads a specified JAR file and collects information from the manifest and
     * checksums to identify the correct CPE information.
     *
     * @param dependency the dependency to analyze.
     * @param engine the engine that is scanning the dependencies
     * @throws AnalysisException is thrown if there is an error reading the JAR
     * file.
     */
    public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
        boolean addPackagesAsEvidence = false;
        //todo - catch should be more granular here, one for each call likely
        //todo - think about sources/javadoc jars, should we remove or move to related dependency?
        try {
            final boolean hasManifest = parseManifest(dependency);
            final boolean hasPOM = analyzePOM(dependency);
            final boolean deepScan = Settings.getBoolean(Settings.KEYS.PERFORM_DEEP_SCAN);
            if ((!hasManifest && !hasPOM) || deepScan) {
                addPackagesAsEvidence = true;
            }
            final boolean hasClasses = analyzePackageNames(dependency, addPackagesAsEvidence);
            if (!hasClasses
                    && (dependency.getFileName().toLowerCase().endsWith("-sources.jar")
                    || dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
                    || dependency.getFileName().toLowerCase().endsWith("-src.jar")
                    || dependency.getFileName().toLowerCase().endsWith("-doc.jar"))) {
                engine.getDependencies().remove(dependency);
            }
        } catch (IOException ex) {
            throw new AnalysisException("Exception occurred reading the JAR file.", ex);
        }
    }
    /**
     * A pattern to detect HTML within text.
     */
    final private Pattern htmlDetection = Pattern.compile("\\<[a-z]+.*/?\\>", Pattern.CASE_INSENSITIVE);

    /**
     * Attempts to find a pom.xml within the JAR file. If found it extracts
     * information and adds it to the evidence. This will attempt to interpolate
     * the strings contained within the pom.properties if one exists.
     *
     * @param dependency the dependency being analyzed.
     * @throws AnalysisException is thrown if there is an exception parsing the pom.
     * @return whether or not evidence was added to the dependency
     */
    protected boolean analyzePOM(Dependency dependency) throws AnalysisException {
        boolean foundSomething = false;
        final JarFile jar;
        try {
            jar = new JarFile(dependency.getActualFilePath());
        } catch (IOException ex) {
            final String msg = String.format("Unable to read JarFile '%s'.", dependency.getActualFilePath());
            final AnalysisException ax = new AnalysisException(msg, ex);
            dependency.getAnalysisExceptions().add(ax);
            Logger.getLogger(JarAnalyzer.class.getName()).log(Level.WARNING, msg, ex);
            return foundSomething;
        }
        List<String> pomEntries;
        try {
            pomEntries = retrievePomListing(jar);
        } catch (IOException ex) {
            final String msg = String.format("Unable to read JarEntries in '%s'.", dependency.getActualFilePath());
            final AnalysisException ax = new AnalysisException(msg, ex);
            dependency.getAnalysisExceptions().add(ax);
            Logger.getLogger(JarAnalyzer.class.getName()).log(Level.WARNING, msg, ex);
            return foundSomething;
        }

        for (String path : pomEntries) {
            Properties pomProperties = null;
            try {
                pomProperties = retrievePomProperties(path, jar);
            } catch (IOException ex) {
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINEST, "ignore this, failed reading a non-existent pom.properties", ex);
            }
            Model pom = null;
            try {
                pom = retrievePom(path, jar);
            } catch (JAXBException ex) {
                final String msg = String.format("Unable to parse POM '%s' in '%s'",
                        path, dependency.getFilePath());
                final AnalysisException ax = new AnalysisException(msg, ex);
                dependency.getAnalysisExceptions().add(ax);
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.WARNING, msg);
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, msg, ax);
            } catch (IOException ex) {
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
            }
            foundSomething = setPomEvidence(dependency, pom, pomProperties) || foundSomething;
        }
        return foundSomething;
    }

    /**
     * Given a path to a pom.xml within a JarFile, this method attempts to load
     * a sibling pom.properties if one exists.
     * @param path the path to the pom.xml within the JarFile
     * @param jar the JarFile to load the pom.properties from
     * @return a Properties object or null if no pom.properties was found
     * @throws IOException thrown if there is an exception reading the pom.properties
     */
    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "OS_OPEN_STREAM",
    justification = "The reader is closed by closing the zipEntry")
    private Properties retrievePomProperties(String path, final JarFile jar) throws IOException {
        Properties pomProperties = null;
        final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
        final ZipEntry propEntry = jar.getEntry(propPath);
        if (propEntry != null) {
            final Reader reader = new InputStreamReader(jar.getInputStream(propEntry), "UTF-8");
            pomProperties = new Properties();
            pomProperties.load(reader);
        }
        return pomProperties;
    }
    /**
     * Searches a JarFile for pom.xml entries and returns a listing of these entries.
     * @param jar the JarFile to search
     * @return a list of pom.xml entries
     * @throws IOException thrown if there is an exception reading a JarEntry
     */
    private List<String> retrievePomListing(final JarFile jar) throws IOException {
        final List<String> pomEntries = new ArrayList<String>();
        final Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            final String entryName = (new File(entry.getName())).getName().toLowerCase();
                if (!entry.isDirectory() && "pom.xml".equals(entryName)) {
                    pomEntries.add(entry.getName());
                }
        }
        return pomEntries;
    }
    /**
     * Retrieves the specified POM from a jar file and converts it to a Model.
     * @param path the path to the pom.xml file within the jar file
     * @param jar the jar file to extract the pom from
     * @return returns a {@link org.owasp.dependencycheck.analyzer.pom.generated.Model} object
     * @throws JAXBException is thrown if there is an exception parsing the pom
     * @throws IOException is thrown if there is an exception reading the jar
     */
    private Model retrievePom(String path, JarFile jar) throws JAXBException, IOException {
        ZipEntry entry = jar.getEntry(path);
        if (entry != null) { //should never be null
            final NonClosingStream stream = new NonClosingStream(jar.getInputStream(entry));
            final JAXBElement obj = (JAXBElement) pomUnmarshaller.unmarshal(stream);
            return (Model) obj.getValue();<commit_msg>added filter to add the correct Maven namespace to the POM if it is missing<commit_after>/*
 * This file is part of Dependency-Check.
 *
 * Dependency-Check is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * Dependency-Check is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * Dependency-Check. If not, see http://www.gnu.org/licenses/.
 *
 * Copyright (c) 2012 <NAME>. All Rights Reserved.
 */
package org.owasp.dependencycheck.analyzer;

import java.io.File;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.ParserConfigurationException;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.dependency.Evidence;
import org.owasp.dependencycheck.dependency.EvidenceCollection;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.UnmarshallerHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.sax.SAXSource;
import org.jsoup.Jsoup;
import org.owasp.dependencycheck.analyzer.pom.MavenNamespaceFilter;
import org.owasp.dependencycheck.analyzer.pom.generated.License;
import org.owasp.dependencycheck.analyzer.pom.generated.Model;
import org.owasp.dependencycheck.analyzer.pom.generated.Organization;
import org.owasp.dependencycheck.utils.NonClosingStream;
import org.owasp.dependencycheck.utils.Settings;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLFilter;
import org.xml.sax.XMLReader;

/**
 *
 * Used to load a JAR file and collect information that can be used to determine
 * the associated CPE.
 *
 * @author <NAME> (<EMAIL>)
 */
public class JarAnalyzer extends AbstractAnalyzer implements Analyzer {

    /**
     * The system independent newline character.
     */
    private static final String NEWLINE = System.getProperty("line.separator");
    /**
     * The name of the analyzer.
     */
    private static final String ANALYZER_NAME = "Jar Analyzer";
    /**
     * The phase that this analyzer is intended to run in.
     */
    private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
    /**
     * A list of elements in the manifest to ignore.
     */
    private static final Set<String> IGNORE_LIST = newHashSet(
            "built-by",
            "created-by",
            "builtby",
            "createdby",
            "build-jdk",
            "buildjdk",
            "ant-version",
            "antversion",
            "import-package",
            "export-package",
            "importpackage",
            "exportpackage",
            "sealed",
            "manifest-version",
            "archiver-version",
            "manifestversion",
            "archiverversion",
            "classpath",
            "class-path",
            "tool",
            "bundle-manifestversion",
            "bundlemanifestversion",
            "include-resource");
    /**
     * The set of file extensions supported by this analyzer.
     */
    private static final Set<String> EXTENSIONS = newHashSet("jar");
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_VERSION = "Bundle-Version"; //: 2.1.2
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_DESCRIPTION = "Bundle-Description"; //: Apache Struts 2
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_NAME = "Bundle-Name"; //: Struts 2 Core
    /**
     * item in some manifest, should be considered medium confidence.
     */
    private static final String BUNDLE_VENDOR = "Bundle-Vendor"; //: Apache Software Foundation
    /**
     * The unmarshaller used to parse the pom.xml from a JAR file.
     */
    private Unmarshaller pomUnmarshaller;

    /**
     * Constructs a new JarAnalyzer.
     */
    public JarAnalyzer() {
        try {
            final JAXBContext jaxbContext = JAXBContext.newInstance("org.owasp.dependencycheck.analyzer.pom.generated");
            pomUnmarshaller = jaxbContext.createUnmarshaller();
        } catch (JAXBException ex) { //guess we will just have a null pointer exception later...
            Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Returns a list of file EXTENSIONS supported by this analyzer.
     *
     * @return a list of file EXTENSIONS supported by this analyzer.
     */
    public Set<String> getSupportedExtensions() {
        return EXTENSIONS;
    }

    /**
     * Returns the name of the analyzer.
     *
     * @return the name of the analyzer.
     */
    public String getName() {
        return ANALYZER_NAME;
    }

    /**
     * Returns whether or not this analyzer can process the given extension.
     *
     * @param extension the file extension to test for support.
     * @return whether or not the specified file extension is supported by this
     * analyzer.
     */
    public boolean supportsExtension(String extension) {
        return EXTENSIONS.contains(extension);
    }

    /**
     * Returns the phase that the analyzer is intended to run in.
     *
     * @return the phase that the analyzer is intended to run in.
     */
    public AnalysisPhase getAnalysisPhase() {
        return ANALYSIS_PHASE;
    }

    /**
     * Loads a specified JAR file and collects information from the manifest and
     * checksums to identify the correct CPE information.
     *
     * @param dependency the dependency to analyze.
     * @param engine the engine that is scanning the dependencies
     * @throws AnalysisException is thrown if there is an error reading the JAR
     * file.
     */
    public void analyze(Dependency dependency, Engine engine) throws AnalysisException {
        boolean addPackagesAsEvidence = false;
        //todo - catch should be more granular here, one for each call likely
        //todo - think about sources/javadoc jars, should we remove or move to related dependency?
        try {
            final boolean hasManifest = parseManifest(dependency);
            final boolean hasPOM = analyzePOM(dependency);
            final boolean deepScan = Settings.getBoolean(Settings.KEYS.PERFORM_DEEP_SCAN);
            if ((!hasManifest && !hasPOM) || deepScan) {
                addPackagesAsEvidence = true;
            }
            final boolean hasClasses = analyzePackageNames(dependency, addPackagesAsEvidence);
            if (!hasClasses
                    && (dependency.getFileName().toLowerCase().endsWith("-sources.jar")
                    || dependency.getFileName().toLowerCase().endsWith("-javadoc.jar")
                    || dependency.getFileName().toLowerCase().endsWith("-src.jar")
                    || dependency.getFileName().toLowerCase().endsWith("-doc.jar"))) {
                engine.getDependencies().remove(dependency);
            }
        } catch (IOException ex) {
            throw new AnalysisException("Exception occurred reading the JAR file.", ex);
        }
    }
    /**
     * A pattern to detect HTML within text.
     */
    final private Pattern htmlDetection = Pattern.compile("\\<[a-z]+.*/?\\>", Pattern.CASE_INSENSITIVE);

    /**
     * Attempts to find a pom.xml within the JAR file. If found it extracts
     * information and adds it to the evidence. This will attempt to interpolate
     * the strings contained within the pom.properties if one exists.
     *
     * @param dependency the dependency being analyzed.
     * @throws AnalysisException is thrown if there is an exception parsing the pom.
     * @return whether or not evidence was added to the dependency
     */
    protected boolean analyzePOM(Dependency dependency) throws AnalysisException {
        boolean foundSomething = false;
        final JarFile jar;
        try {
            jar = new JarFile(dependency.getActualFilePath());
        } catch (IOException ex) {
            final String msg = String.format("Unable to read JarFile '%s'.", dependency.getActualFilePath());
            final AnalysisException ax = new AnalysisException(msg, ex);
            dependency.getAnalysisExceptions().add(ax);
            Logger.getLogger(JarAnalyzer.class.getName()).log(Level.WARNING, msg, ex);
            return foundSomething;
        }
        List<String> pomEntries;
        try {
            pomEntries = retrievePomListing(jar);
        } catch (IOException ex) {
            final String msg = String.format("Unable to read JarEntries in '%s'.", dependency.getActualFilePath());
            final AnalysisException ax = new AnalysisException(msg, ex);
            dependency.getAnalysisExceptions().add(ax);
            Logger.getLogger(JarAnalyzer.class.getName()).log(Level.WARNING, msg, ex);
            return foundSomething;
        }

        for (String path : pomEntries) {
            Properties pomProperties = null;
            try {
                pomProperties = retrievePomProperties(path, jar);
            } catch (IOException ex) {
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINEST, "ignore this, failed reading a non-existent pom.properties", ex);
            }
            Model pom = null;
            try {
                pom = retrievePom(path, jar);
            } catch (JAXBException ex) {
                final String msg = String.format("Unable to parse POM '%s' in '%s'",
                        path, dependency.getFilePath());
                final AnalysisException ax = new AnalysisException(msg, ex);
                dependency.getAnalysisExceptions().add(ax);
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.WARNING, msg);
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, msg, ax);
            } catch (IOException ex) {
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
            }
            foundSomething = setPomEvidence(dependency, pom, pomProperties) || foundSomething;
        }
        return foundSomething;
    }

    /**
     * Given a path to a pom.xml within a JarFile, this method attempts to load
     * a sibling pom.properties if one exists.
     * @param path the path to the pom.xml within the JarFile
     * @param jar the JarFile to load the pom.properties from
     * @return a Properties object or null if no pom.properties was found
     * @throws IOException thrown if there is an exception reading the pom.properties
     */
    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "OS_OPEN_STREAM",
    justification = "The reader is closed by closing the zipEntry")
    private Properties retrievePomProperties(String path, final JarFile jar) throws IOException {
        Properties pomProperties = null;
        final String propPath = path.substring(0, path.length() - 7) + "pom.properies";
        final ZipEntry propEntry = jar.getEntry(propPath);
        if (propEntry != null) {
            final Reader reader = new InputStreamReader(jar.getInputStream(propEntry), "UTF-8");
            pomProperties = new Properties();
            pomProperties.load(reader);
        }
        return pomProperties;
    }
    /**
     * Searches a JarFile for pom.xml entries and returns a listing of these entries.
     * @param jar the JarFile to search
     * @return a list of pom.xml entries
     * @throws IOException thrown if there is an exception reading a JarEntry
     */
    private List<String> retrievePomListing(final JarFile jar) throws IOException {
        final List<String> pomEntries = new ArrayList<String>();
        final Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            final String entryName = (new File(entry.getName())).getName().toLowerCase();
                if (!entry.isDirectory() && "pom.xml".equals(entryName)) {
                    pomEntries.add(entry.getName());
                }
        }
        return pomEntries;
    }
    /**
     * Retrieves the specified POM from a jar file and converts it to a Model.
     * @param path the path to the pom.xml file within the jar file
     * @param jar the jar file to extract the pom from
     * @return returns a {@link org.owasp.dependencycheck.analyzer.pom.generated.Model} object
     * @throws JAXBException is thrown if there is an exception parsing the pom
     * @throws IOException is thrown if there is an exception reading the jar
     */
    private Model retrievePom(String path, JarFile jar) throws JAXBException, IOException {
        ZipEntry entry = jar.getEntry(path);
        if (entry != null) { //should never be null
            Model m = null;
            try {
                XMLFilter filter = new MavenNamespaceFilter();
                SAXParserFactory spf = SAXParserFactory.newInstance();
                SAXParser sp = spf.newSAXParser();
                XMLReader xr = sp.getXMLReader();
                filter.setParent(xr);
                NonClosingStream stream = new NonClosingStream(jar.getInputStream(entry));
                InputStreamReader reader = new InputStreamReader(stream);
                InputSource xml = new InputSource(reader);
                SAXSource source = new SAXSource(filter, xml);
                JAXBElement<Model> el = pomUnmarshaller.unmarshal(source, Model.class);
                m = el.getValue();
            } catch (ParserConfigurationException ex) {
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
            } catch (SAXException ex) {
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
            } catch (JAXBException ex) {
                Logger.getLogger(JarAnalyzer.class.getName()).log(Level.FINEST, "failure reading pom via jaxb path:'"
                        + path + "' jar:'" + jar.getName() + "'", ex);
            }

            return m;
{'fraction_non_alphanumeric': 0.09339145597210113, 'fraction_numerical': 0.0009067131647776809, 'mean_word_length': 2.538936196470443, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 35}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6000064', 'n_tokens_mistral': 7606, 'n_tokens_neox': 7439, 'n_words': 2365}