cleanup and rework of core engine

Former-commit-id: e5bd95da1080429837df5835f28f46542a20fff7
This commit is contained in:
Jeremy Long
2012-09-22 00:31:08 -04:00
parent a9cf6b595d
commit 0643c68da1
43 changed files with 193796 additions and 379 deletions

View File

@@ -29,8 +29,7 @@ import org.codesecure.dependencycheck.data.cpe.CPEQuery;
import org.codesecure.dependencycheck.data.cpe.Index;
import org.codesecure.dependencycheck.data.cpe.xml.Importer;
import org.codesecure.dependencycheck.reporting.ReportGenerator;
import org.codesecure.dependencycheck.scanner.Dependency;
import org.codesecure.dependencycheck.scanner.Scanner;
import org.codesecure.dependencycheck.dependency.Dependency;
import org.codesecure.dependencycheck.utils.CliParser;
import org.xml.sax.SAXException;
@@ -93,7 +92,7 @@ public class App {
if (cli.isAutoUpdate()) {
Index cpeI = new Index();
try {
cpeI.updateIndexFromWeb();
cpeI.update();
} catch (Exception ex) {
Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
}
@@ -130,10 +129,11 @@ public class App {
*/
private void runScan(String reportDirectory, String applicationName, String[] files) {
try {
Scanner scanner = new Scanner();
Engine scanner = new Engine();
for (String file : files) {
scanner.scan(file);
}
scanner.analyzeDependencies();
List<Dependency> dependencies = scanner.getDependencies();
CPEQuery query = new CPEQuery();
query.open();

View File

@@ -0,0 +1,209 @@
package org.codesecure.dependencycheck;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import java.util.EnumMap;
import org.codesecure.dependencycheck.dependency.Dependency;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codesecure.dependencycheck.analyzer.AnalysisPhase;
import org.codesecure.dependencycheck.analyzer.Analyzer;
import org.codesecure.dependencycheck.analyzer.AnalyzerService;
import org.codesecure.dependencycheck.analyzer.ArchiveAnalyzer;
import org.codesecure.dependencycheck.utils.FileUtils;
/**
* Scans files, directories, etc. for Dependencies. Analyzers are loaded and
* used to process the files found by the scan, if a file is encountered and
* an Analyzer is associated with the file type then the file is turned into a
* dependency.
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class Engine {
/**
* The list of dependencies.
*/
protected List<Dependency> dependencies = new ArrayList<Dependency>();
/**
* A Map of analyzers grouped by Analysis phase.
*/
protected EnumMap<AnalysisPhase, List<Analyzer>> analyzers = new EnumMap<AnalysisPhase, List<Analyzer>>(AnalysisPhase.class);
/**
* A set of extensions supported by the analyzers.
*/
protected static final Set<String> extensions = new HashSet<String>();
/**
* Creates a new Engine.
*/
public Engine() {
loadAnalyzers();
}
/**
* Loads the analyzers specified in the configuration file (or system properties).
*/
private void loadAnalyzers() {
for (AnalysisPhase phase : AnalysisPhase.values()) {
analyzers.put(phase, new ArrayList<Analyzer>());
}
AnalyzerService service = AnalyzerService.getInstance();
Iterator<Analyzer> iterator = service.getAnalyzers();
while (iterator.hasNext()) {
Analyzer a = iterator.next();
analyzers.get(a.getAnalysisPhase()).add(a);
if (a.getSupportedExtensions() != null) {
extensions.addAll(a.getSupportedExtensions());
}
}
}
/**
* Get the List of the analyzers for a specific phase of analysis.
*
* @param phase the phase to get the configured analyzers.
* @return the analyzers loaded
*/
public List<Analyzer> getAnalyzers(AnalysisPhase phase) {
return analyzers.get(phase);
}
/**
* Get the dependencies identified
*
* @return the dependencies identified
*/
public List<Dependency> getDependencies() {
return dependencies;
}
/**
* Scans a given file or directory. If a directory is specified, it will be
* scanned recursively.
* Any dependencies identified are added to the dependency collection.
*
* @param path the path to a file or directory to be analyzed.
*/
public void scan(String path) {
File file = new File(path);
if (file.exists()) {
if (file.isDirectory()) {
scanDirectory(file);
} else {
scanFile(file);
}
}
}
/**
* Recursively scans files and directories.
* Any dependencies identified are added to the dependency collection.
*
* @param dir the directory to scan.
*/
protected void scanDirectory(File dir) {
File[] files = dir.listFiles();
for (File f : files) {
if (f.isDirectory()) {
scanDirectory(f);
} else {
scanFile(f);
}
}
}
/**
* Scans a specified file. If a dependency is identified it is added to the
* dependency collection.
*
* @param file The file to scan.
*/
protected void scanFile(File file) {
if (!file.isFile()) {
String msg = String.format("Path passed to scanFile(File) is not a file: %s.", file.toString());
Logger.getLogger(Engine.class.getName()).log(Level.WARNING, msg);
}
String fileName = file.getName();
String extension = FileUtils.getFileExtension(fileName);
if (extension != null) {
if (extensions.contains(extension)) {
Dependency dependency = new Dependency(file);
dependencies.add(dependency);
}
} else {
String msg = String.format("No file extension found on file '%s'. The file was not analyzed.",
file.toString());
Logger.getLogger(Engine.class.getName()).log(Level.FINEST, msg);
}
}
/**
* Runs the analyzers against all of the dependencies.
*/
public void analyzeDependencies() {
for (AnalysisPhase phase : AnalysisPhase.values()) {
List<Analyzer> analyzerList = analyzers.get(phase);
for (Analyzer a : analyzerList) {
a.initialize();
for (Dependency d : dependencies) {
if (a.supportsExtension(d.getFileExtension())) {
try {
if (a instanceof ArchiveAnalyzer) {
ArchiveAnalyzer aa = (ArchiveAnalyzer) a;
aa.analyze(d, this);
} else {
a.analyze(d);
}
} catch (IOException ex) {
String msg = String.format("IOException occured while scanning the file '%s'.",
d.getActualFilePath());
Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, msg, ex);
}
}
}
a.close();
}
}
//Now cycle through all of the analyzers one last time to call
// cleanup on any archiveanalyzers. These should only exist in the
// initial phase, but we are going to be thourough just in case.
for (AnalysisPhase phase : AnalysisPhase.values()) {
List<Analyzer> analyzerList = analyzers.get(phase);
for (Analyzer a : analyzerList) {
if (a instanceof ArchiveAnalyzer) {
ArchiveAnalyzer aa = (ArchiveAnalyzer) a;
aa.cleanup();
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.analyzer;
/*
* This file is part of DependencyCheck.
*

View File

@@ -0,0 +1,56 @@
package org.codesecure.dependencycheck.analyzer;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
/**
* An enumeration defining the phases of analysis.
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public enum AnalysisPhase {
/**
* The first phase of analysis.
*/
INITIAL,
/**
* The second phase of analysis.
*/
INFORMATION_COLLECTION,
/**
* The third phase of analysis.
*/
PRE_IDENTIFIER_ANALYSIS,
/**
* The fourth phase of analysis.
*/
IDENTIFIER_ANALYSIS,
/**
* The fifth phase of analysis.
*/
POST_IDENTIFIER_ANALYSIS,
/**
* The sixth phase of analysis.
*/
FINDING_ANALYSIS,
/**
* The seventh and final phase of analysis.
*/
FINAL
}

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.analyzer;
/*
* This file is part of DependencyCheck.
*
@@ -18,7 +18,7 @@ package org.codesecure.dependencycheck.scanner;
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import java.io.File;
import org.codesecure.dependencycheck.dependency.Dependency;
import java.io.IOException;
import java.util.Set;
@@ -32,20 +32,23 @@ import java.util.Set;
public interface Analyzer {
/**
* Loads a specified file and collects data needed to determine any associated CPE/CVE entries.
* Analyzes the given dependency.
*
* @param file path to the file.
* @return Dependency the dependency containing evidence needed to determine associated CPE/CVE entries.
* @param dependency a dependency to analyze.
* @throws IOException is thrown if there is an error reading the dependency file
*/
Dependency insepct(File file) throws IOException;
void analyze(Dependency dependency) throws IOException;
/**
* Returns a list of supported file extensions. An example would be an analyzer
* <p>Returns a list of supported file extensions. An example would be an analyzer
* that inpected java jar files. The getSupportedExtensions function would return
* a set with a single element "jar".
* a set with a single element "jar".</p>
*
* <p><b>Note:</b> when implementing this the extensions returned MUST be lowercase.</p>
* @return The file extensions supported by this analyzer.
*
* <p>If the analyzer returns null it will not cause additional files to be analyzed
* but will be executed against every file loaded</p>
*/
Set<String> getSupportedExtensions();
@@ -54,11 +57,27 @@ public interface Analyzer {
* @return the name of the analyzer.
*/
String getName();
/**
* 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 tihs analyzer.
*/
boolean supportsExtension(String extension);
/**
* Returns the phase that the analyzer is intended to run in.
* @return the phase that the analyzer is intended to run in.
*/
AnalysisPhase getAnalysisPhase();
/**
* The initialize method is called (once) prior to the analyze method being called on
* all of the dependencies.
*/
void initialize();
/**
* The close method is called after all of the dependencies have been analyzed.
*/
void close();
}

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.analyzer;
/*
* This file is part of DependencyCheck.
*

View File

@@ -0,0 +1,45 @@
package org.codesecure.dependencycheck.analyzer;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import org.codesecure.dependencycheck.dependency.Dependency;
import java.io.IOException;
import org.codesecure.dependencycheck.Engine;
/**
* An interface that defines an Analyzer that is used to expand archives and
* allow the engine to scan the contents.
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public interface ArchiveAnalyzer {
/**
* An ArchiveAnalyzer expands an archive and calls the scan method of the engine on
* the exploded contents.
*
* @param dependency a dependency to analyze.
* @throws IOException is thrown if there is an error reading the dependency file
*/
void analyze(Dependency dependency, Engine engine) throws IOException;
/**
* Cleans any temporary files generated when analyzing the archive.
*/
void cleanup();
}

View File

@@ -0,0 +1,175 @@
package org.codesecure.dependencycheck.analyzer;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import org.codesecure.dependencycheck.dependency.Dependency;
import org.codesecure.dependencycheck.dependency.Evidence;
import java.io.IOException;
import java.util.Set;
import java.util.regex.Pattern;
/**
*
* Takes a dependency and analyzes the filename and determines the hashes.
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class FileNameAnalyzer implements Analyzer {
/**
* The name of the analyzer.
*/
private static final String ANALYZER_NAME = "File Analyzer";
/**
* The phase that this analyzer is intended to run in.
*/
private static final AnalysisPhase ANALYSIS_PHASE = AnalysisPhase.INFORMATION_COLLECTION;
/**
* The set of file extensions supported by this analyzer.
*/
private static final Set<String> EXTENSIONS = null;
/**
* 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 tihs analyzer.
*/
public boolean supportsExtension(String extension) {
return true;
}
/**
* 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;
}
/**
* An enumeration to keep track of the characters in a string as it is being
* read in one character at a time.
*/
private enum STRING_STATE {
ALPHA,
NUMBER,
PERIOD,
OTHER
}
/**
* Determines type of the character passed in.
* @param c a character
* @return a STRING_STATE representing whether the character is number, alpha, or other.
*/
private STRING_STATE determineState(char c) {
if (c >= '0' && c <= '9') {
return STRING_STATE.NUMBER;
} else if (c == '.') {
return STRING_STATE.PERIOD;
} else if (c >= 'a' && c <= 'z') {
return STRING_STATE.ALPHA;
} else {
return STRING_STATE.OTHER;
}
}
/**
* Collects information about the file such as hashsums.
*
* @param dependency the dependency to analyze.
* @throws IOException is thrown if there is an error reading the JAR file.
*/
public void analyze(Dependency dependency) throws IOException {
analyzeFileName(dependency);
}
/**
* Analyzes the filename of the dependency and adds it to the evidence collections.
* @param dependency the dependency to analyze.
*/
private void analyzeFileName(Dependency dependency) {
String fileName = dependency.getFileName();
//slightly process the filename to chunk it into distinct words, numbers.
// Yes, the lucene analyzer might do this, but I want a little better control
// over the process.
String fileNameEvidence = fileName.substring(0, fileName.length() - 4).toLowerCase().replace('-', ' ').replace('_', ' ');
StringBuilder sb = new StringBuilder(fileNameEvidence.length());
STRING_STATE state = determineState(fileNameEvidence.charAt(0));
for (int i = 0; i < fileNameEvidence.length(); i++) {
char c = fileNameEvidence.charAt(i);
STRING_STATE newState = determineState(c);
if (newState != state) {
if ((state != STRING_STATE.NUMBER && newState == STRING_STATE.PERIOD)
|| (state == STRING_STATE.PERIOD && newState != STRING_STATE.NUMBER)
|| (state == STRING_STATE.ALPHA || newState == STRING_STATE.ALPHA)
|| ((state == STRING_STATE.OTHER || newState == STRING_STATE.OTHER) && c != ' ')) {
sb.append(' ');
}
}
state = newState;
sb.append(c);
}
Pattern rx = Pattern.compile("\\s\\s+");
fileNameEvidence = rx.matcher(sb.toString()).replaceAll(" ");
dependency.getProductEvidence().addEvidence("file", "name",
fileNameEvidence, Evidence.Confidence.HIGH);
dependency.getVendorEvidence().addEvidence("file", "name",
fileNameEvidence, Evidence.Confidence.HIGH);
if (fileNameEvidence.matches(".*\\d.*")) {
dependency.getVersionEvidence().addEvidence("file", "name",
fileNameEvidence, Evidence.Confidence.HIGH);
}
}
/**
* The initialize method does nothing for this Analyzer
*/
public void initialize() {
//do nothing
}
/**
* The close method does nothing for this Analyzer
*/
public void close() {
//do nothing
}
}

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.analyzer;
/*
* This file is part of DependencyCheck.
*
@@ -18,37 +18,33 @@ package org.codesecure.dependencycheck.scanner;
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import java.io.File;
import java.io.FileNotFoundException;
import org.codesecure.dependencycheck.dependency.Dependency;
import org.codesecure.dependencycheck.dependency.Evidence;
import org.codesecure.dependencycheck.dependency.EvidenceCollection;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.codesecure.dependencycheck.utils.Checksum;
/**
*
* Used to load a JAR file and collect information that can be used to determine the associated CPE.
*
* <!--
* ideas - scan the JAR to see if there is a "version" final static string?
* scan manifest for version info
* get md5 and sh1 checksums to lookup file via maven centrals hosted md5sum files
* examine file name itself for version info
* -->
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class JarAnalyzer extends AbstractAnalyzer {
/**
* 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.INITIAL;
/**
* A list of elements in the manifest to ignore.
*/
@@ -111,6 +107,14 @@ public class JarAnalyzer extends AbstractAnalyzer {
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;
}
/**
* An enumeration to keep track of the characters in a string as it is being
* read in one character at a time.
@@ -144,66 +148,14 @@ public class JarAnalyzer extends AbstractAnalyzer {
* Loads a specified JAR file and collects information from the manifest and
* checksums to identify the correct CPE information.
*
* @param file path to the JAR file.
* @return a dependency derived for the specified file.
* @param dependency the dependency to analyze.
* @throws IOException is thrown if there is an error reading the JAR file.
*/
public Dependency insepct(File file) throws IOException {
Dependency dependency = new Dependency();
String fileName = file.getName();
dependency.setFileName(fileName);
dependency.setFilePath(file.getCanonicalPath());
//slightly process the filename to chunk it into distinct words, numbers.
// Yes, the lucene analyzer might do this, but I want a little better control
// over the process.
String fileNameEvidence = fileName.substring(0, fileName.length() - 4).toLowerCase().replace('-', ' ').replace('_', ' ');
StringBuilder sb = new StringBuilder(fileNameEvidence.length());
STRING_STATE state = determineState(fileNameEvidence.charAt(0));
for (int i = 0; i < fileNameEvidence.length(); i++) {
char c = fileNameEvidence.charAt(i);
STRING_STATE newState = determineState(c);
if (newState != state) {
if ((state != STRING_STATE.NUMBER && newState == STRING_STATE.PERIOD)
|| (state == STRING_STATE.PERIOD && newState != STRING_STATE.NUMBER)
|| (state == STRING_STATE.ALPHA || newState == STRING_STATE.ALPHA)
|| ((state == STRING_STATE.OTHER || newState == STRING_STATE.OTHER) && c != ' ')) {
sb.append(' ');
}
}
state = newState;
sb.append(c);
}
Pattern rx = Pattern.compile("\\s\\s+");
fileNameEvidence = rx.matcher(sb.toString()).replaceAll(" ");
dependency.getProductEvidence().addEvidence("jar", "file name",
fileNameEvidence, Evidence.Confidence.HIGH);
dependency.getVendorEvidence().addEvidence("jar", "file name",
fileNameEvidence, Evidence.Confidence.HIGH);
if (fileNameEvidence.matches(".*\\d.*")) {
dependency.getVersionEvidence().addEvidence("jar", "file name",
fileNameEvidence, Evidence.Confidence.HIGH);
}
String md5 = null;
String sha1 = null;
try {
md5 = Checksum.getMD5Checksum(file);
sha1 = Checksum.getSHA1Checksum(file);
} catch (FileNotFoundException ex) {
Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(JarAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
}
dependency.setMd5sum(md5);
dependency.setSha1sum(sha1);
public void analyze(Dependency dependency) throws IOException {
parseManifest(dependency);
analyzePackageNames(dependency);
return dependency;
}
/**
@@ -216,7 +168,7 @@ public class JarAnalyzer extends AbstractAnalyzer {
*/
protected void analyzePackageNames(Dependency dependency) throws IOException {
JarFile jar = new JarFile(dependency.getFilePath());
JarFile jar = new JarFile(dependency.getActualFilePath());
java.util.Enumeration en = jar.entries();
HashMap<String, Integer> level0 = new HashMap<String, Integer>();
@@ -375,7 +327,7 @@ public class JarAnalyzer extends AbstractAnalyzer {
* @throws IOException if there is an issue reading the JAR file.
*/
protected void parseManifest(Dependency dependency) throws IOException {
JarFile jar = new JarFile(dependency.getFilePath());
JarFile jar = new JarFile(dependency.getActualFilePath());
Manifest manifest = jar.getManifest();
Attributes atts = manifest.getMainAttributes();
@@ -431,4 +383,18 @@ public class JarAnalyzer extends AbstractAnalyzer {
}
}
}
/**
* The initialize method does nothing for this Analyzer
*/
public void initialize() {
//do nothing
}
/**
* The close method does nothing for this Analyzer
*/
public void close() {
//do nothing
}
}

View File

@@ -12,4 +12,4 @@
* </html>
*/
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.analyzer;

View File

@@ -30,7 +30,7 @@ import org.xml.sax.SAXException;
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public interface WebDataIndex {
public interface CachedWebDataSource {
/**
* Determines if an update to the current index is needed, if it is the new
@@ -41,5 +41,5 @@ public interface WebDataIndex {
* @throws SAXException is thrown if there is an error parsing the CPE XML.
* @throws IOException is thrown if a temporary file could not be created.
*/
public void updateIndexFromWeb() throws MalformedURLException, ParserConfigurationException, SAXException, IOException;
public void update() throws MalformedURLException, ParserConfigurationException, SAXException, IOException;
}

View File

@@ -0,0 +1,45 @@
package org.codesecure.dependencycheck.data;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import org.apache.lucene.search.DefaultSimilarity;
/**
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class DependencySimilarity extends DefaultSimilarity {
private static final long serialVersionUID = 1L;
/**
* <p>Override the default idf implementation so that frequency within
* all document is ignored.</p>
*
* See <a href="http://www.lucenetutorial.com/advanced-topics/scoring.html">this article</a> for more details.
*
* @param docFreq - the number of documents which contain the term
* @param numDocs - the total number of documents in the collection
* @return 1
*/
@Override
public float idf(int docFreq, int numDocs) {
return 1;
}
}

View File

@@ -25,4 +25,11 @@ package org.codesecure.dependencycheck.data;
*/
public class VersionAnalyzer {
//TODO Implement this...
// use custom attributes for major, minor, x, x, x, rcx
// these can then be used to weight the score for searches on the version.
// see http://lucene.apache.org/core/3_6_1/api/core/org/apache/lucene/analysis/package-summary.html#package_description
// look at this article to implement
// http://www.codewrecks.com/blog/index.php/2012/08/25/index-your-blog-using-tags-and-lucene-net/
}

View File

@@ -38,10 +38,10 @@ import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Version;
import org.codesecure.dependencycheck.data.LuceneUtils;
import org.codesecure.dependencycheck.scanner.Dependency;
import org.codesecure.dependencycheck.scanner.Evidence;
import org.codesecure.dependencycheck.scanner.Evidence.Confidence;
import org.codesecure.dependencycheck.scanner.EvidenceCollection;
import org.codesecure.dependencycheck.dependency.Dependency;
import org.codesecure.dependencycheck.dependency.Evidence;
import org.codesecure.dependencycheck.dependency.Evidence.Confidence;
import org.codesecure.dependencycheck.dependency.EvidenceCollection;
/**
* CPEQuery is a utility class that takes a project dependency and attempts
@@ -168,6 +168,8 @@ public class CPEQuery {
dependency.getVendorEvidence().getWeighting());
if (entries.size() > 0) {
//TODO - after changing the lucene query to use the AND conditions we should no longer need this.
List<String> verified = verifyEntries(entries, dependency);
if (verified.size() > 0) {
found = true;
@@ -323,6 +325,8 @@ public class CPEQuery {
* to boost the terms weight.
* @return the Lucene query.
*/
//TODO change this whole search mechanism into building the query
// using terms and the org.apache.lucene.search.Query API.
protected String buildSearch(String vendor, String product, String version,
Set<String> vendorWeighting, Set<String> produdctWeightings) {
@@ -337,10 +341,12 @@ public class CPEQuery {
if (!appendWeightedSearch(sb, Fields.PRODUCT, product.toLowerCase(), produdctWeightings)) {
return null;
}
sb.append(" AND ");
if (!appendWeightedSearch(sb, Fields.VENDOR, vendor.toLowerCase(), vendorWeighting)) {
return null;
}
sb.append(" AND ");
sb.append(Fields.VERSION).append(":(");
if (sb.indexOf("^") > 0) {
//if we have a weighting on something else, reduce the weighting on the version a lot

View File

@@ -45,7 +45,7 @@ import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.codesecure.dependencycheck.data.AbstractIndex;
import org.codesecure.dependencycheck.data.WebDataIndex;
import org.codesecure.dependencycheck.data.CachedWebDataSource;
import org.codesecure.dependencycheck.utils.Downloader;
import org.codesecure.dependencycheck.utils.Settings;
import org.codesecure.dependencycheck.data.cpe.xml.Importer;
@@ -57,7 +57,7 @@ import org.xml.sax.SAXException;
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class Index extends AbstractIndex implements WebDataIndex {
public class Index extends AbstractIndex implements CachedWebDataSource {
/**
* The name of the properties file containing the timestamp of the last update.
@@ -109,7 +109,7 @@ public class Index extends AbstractIndex implements WebDataIndex {
* @throws SAXException is thrown if there is an error parsing the CPE XML.
* @throws IOException is thrown if a temporary file could not be created.
*/
public void updateIndexFromWeb() throws MalformedURLException, ParserConfigurationException, SAXException, IOException {
public void update() throws MalformedURLException, ParserConfigurationException, SAXException, IOException {
long timeStamp = updateNeeded();
if (timeStamp > 0) {
URL url = new URL(Settings.getString(Settings.KEYS.CPE_URL));

View File

@@ -18,7 +18,6 @@ package org.codesecure.dependencycheck.data.cpe.xml;
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import org.codesecure.dependencycheck.data.cpe.Indexer;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.data.cpe;
package org.codesecure.dependencycheck.data.cpe.xml;
/*
* This file is part of DependencyCheck.
*
@@ -26,7 +26,10 @@ import org.apache.lucene.index.FieldInfo.IndexOptions;
import org.apache.lucene.index.Term;
import org.codesecure.dependencycheck.data.LuceneUtils;
import org.codesecure.dependencycheck.data.cpe.Entry;
import org.codesecure.dependencycheck.data.cpe.Entry;
import org.codesecure.dependencycheck.data.cpe.Fields;
import org.codesecure.dependencycheck.data.cpe.Fields;
import org.codesecure.dependencycheck.data.cpe.Index;
import org.codesecure.dependencycheck.data.cpe.Index;
import org.codesecure.dependencycheck.data.cpe.xml.EntrySaveDelegate;
import org.codesecure.dependencycheck.data.cpe.xml.EntrySaveDelegate;

View File

@@ -42,7 +42,7 @@ import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.codesecure.dependencycheck.data.AbstractIndex;
import org.codesecure.dependencycheck.data.WebDataIndex;
import org.codesecure.dependencycheck.data.CachedWebDataSource;
import org.codesecure.dependencycheck.utils.Downloader;
import org.codesecure.dependencycheck.utils.Settings;
import org.codesecure.dependencycheck.data.cpe.xml.Importer;
@@ -54,7 +54,7 @@ import org.xml.sax.SAXException;
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class Index extends AbstractIndex implements WebDataIndex {
public class Index extends AbstractIndex implements CachedWebDataSource {
/**
* The name of the properties file containing the timestamp of the last update.
@@ -106,7 +106,7 @@ public class Index extends AbstractIndex implements WebDataIndex {
* @throws SAXException is thrown if there is an error parsing the CPE XML.
* @throws IOException is thrown if a temporary file could not be created.
*/
public void updateIndexFromWeb() throws MalformedURLException, ParserConfigurationException, SAXException, IOException {
public void update() throws MalformedURLException, ParserConfigurationException, SAXException, IOException {
long timeStamp = updateNeeded();
if (timeStamp > 0) {
URL url = new URL(Settings.getString(Settings.KEYS.CPE_URL));

View File

@@ -18,7 +18,6 @@ package org.codesecure.dependencycheck.data.cve.xml;
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import org.codesecure.dependencycheck.data.cve.Indexer;
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.data.cve;
package org.codesecure.dependencycheck.data.cve.xml;
/*
* This file is part of DependencyCheck.
*
@@ -25,6 +25,9 @@ import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.FieldInfo.IndexOptions;
import org.apache.lucene.index.Term;
import org.codesecure.dependencycheck.data.LuceneUtils;
import org.codesecure.dependencycheck.data.cve.Entry;
import org.codesecure.dependencycheck.data.cve.Fields;
import org.codesecure.dependencycheck.data.cve.Index;
import org.codesecure.dependencycheck.data.cve.xml.EntrySaveDelegate;
/**

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.dependency;
/*
* This file is part of DependencyCheck.
*
@@ -18,8 +18,15 @@ package org.codesecure.dependencycheck.scanner;
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codesecure.dependencycheck.utils.Checksum;
import org.codesecure.dependencycheck.utils.FileUtils;
/**
* A program dependency. This object is one of the core components within
@@ -33,13 +40,21 @@ import java.util.List;
public class Dependency {
/**
* The file path of the dependency.
* The actual file path of the dependency on disk.
*/
private String actualFilePath = null;
/**
* The file path to display.
*/
private String filePath = null;
/**
* The file name of the dependency.
*/
private String fileName = null;
/**
* The file extension of the dependency.
*/
private String fileExtension = null;
/**
* The md5 hash of the dependency.
*/
@@ -49,9 +64,9 @@ public class Dependency {
*/
private String sha1sum = null;
/**
* A list of CPEs.
* A list of Identifiers.
*/
private List<String> cpes = null;
private List<Identifier> identifiers = null;
/**
* A collection of vendor evidence.
*/
@@ -72,45 +87,92 @@ public class Dependency {
vendorEvidence = new EvidenceCollection();
productEvidence = new EvidenceCollection();
versionEvidence = new EvidenceCollection();
cpes = new ArrayList<String>();
identifiers = new ArrayList<Identifier>();
}
public Dependency(File file) {
this();
this.actualFilePath = file.getPath();
this.filePath = this.actualFilePath;
this.fileName = file.getName();
this.fileExtension = FileUtils.getFileExtension(fileName);
determineHashes(file);
}
/**
* Returns the file name of the JAR.
* Returns the file name of the dependency.
*
* @return the file name of the JAR
* @return the file name of the dependency.
*/
public String getFileName() {
return this.fileName;
}
/**
* Sets the file name of the JAR.
* Sets the file name of the dependency.
*
* @param fileName the file name of the JAR
* @param fileName the file name of the dependency.
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* Sets the file path of the JAR.
* @param filePath the file path of the JAR
* Sets the actual file path of the dependency on disk.
* @param actualFilePath the file path of the dependency.
*/
public void setActualFilePath(String actualFilePath) {
this.actualFilePath = actualFilePath;
}
/**
* Gets the file path of the dependency.
*
* @return the file path of the dependency.
*/
public String getActualFilePath() {
return this.actualFilePath;
}
/**
* Sets the file path of the dependency.
* @param filePath the file path of the dependency.
*/
public void setFilePath(String filePath) {
this.filePath = filePath;
}
/**
* Gets the file path of the JAR.
* @return the file path of the JAR.
* <p>Gets the file path of the dependency.</p>
* <p><b>NOTE:</b> This may not be the actual path of the file on disk. The
* actual path of the file on disk can be obtained via the getActualFilePath().</p>
*
* @return the file path of the dependency.
*/
public String getFilePath() {
return this.filePath;
}
/**
* Returns the MD5 Checksum of the JAR file.
* Sets the file name of the dependency.
*
* @param fileExtension the file name of the dependency.
*/
public void setFileExtension(String fileExtension) {
this.fileExtension = fileExtension;
}
/**
* Gets the file extension of the dependency.
*
* @return the file extension of the dependency.
*/
public String getFileExtension() {
return this.fileExtension;
}
/**
* Returns the MD5 Checksum of the dependency file.
*
* @return the MD5 Checksum
*/
@@ -119,7 +181,7 @@ public class Dependency {
}
/**
* Sets the MD5 Checksum of the JAR.
* Sets the MD5 Checksum of the dependency.
*
* @param md5sum the MD5 Checksum
*/
@@ -128,7 +190,7 @@ public class Dependency {
}
/**
* Returns the SHA1 Checksum of the JAR.
* Returns the SHA1 Checksum of the dependency.
*
* @return the SHA1 Checksum
*/
@@ -137,7 +199,7 @@ public class Dependency {
}
/**
* Sets the SHA1 Checksum of the JAR.
* Sets the SHA1 Checksum of the dependency.
*
* @param sha1sum the SHA1 Checksum
*/
@@ -146,33 +208,35 @@ public class Dependency {
}
/**
* Returns a List of possible CPE keys.
* Returns a List of Identifiers.
*
* @return an ArrayList containing possible CPE keys.
* @return an ArrayList of Identifiers.
*/
public List<String> getCPEs() {
return this.cpes;
public List<Identifier> getIdentifiers() {
return this.identifiers;
}
/**
* Sets a List of possible CPE keys.
* Sets a List of Identifiers.
*
* @param cpe A list of CPE values.
* @param identifiers A list of Identifiers.
*/
public void setCPEs(List<String> cpe) {
this.cpes = cpe;
public void setIdentifiers(List<Identifier> identifiers) {
this.identifiers = identifiers;
}
/**
* Adds an entry to the list of detected CPE keys for the dependency file.
* Adds an entry to the list of detected Identifiers for the dependency file.
*
* @param cpe a CPE key for the dependency file
* @param type the type of identifier (such as CPE).
* @param value the value of the identifier.
* @param title the title of the identifier.
* @param url the URL of the identifier.
* @param description the description of the identifier.
*/
public void addCPEentry(String cpe) {
if (cpes == null) {
cpes = new ArrayList<String>();
}
this.cpes.add(cpe);
public void addIdentifier(String type, String value, String title, String url, String description) {
Identifier i = new Identifier(type, value, title, url, description);
this.identifiers.add(i);
}
/**
@@ -184,7 +248,6 @@ public class Dependency {
return EvidenceCollection.mergeUsed(this.productEvidence, this.vendorEvidence, this.versionEvidence);
}
/**
* Returns the evidence used to identify this dependency.
*
@@ -250,7 +313,21 @@ public class Dependency {
if (versionEvidence.containsUsedString(fnd)) {
return true;
}
return false;
}
private void determineHashes(File file) {
String md5 = null;
String sha1 = null;
try {
md5 = Checksum.getMD5Checksum(file);
sha1 = Checksum.getSHA1Checksum(file);
} catch (FileNotFoundException ex) {
Logger.getLogger(Dependency.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(Dependency.class.getName()).log(Level.SEVERE, null, ex);
}
this.setMd5sum(md5);
this.setSha1sum(sha1);
}
}

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.dependency;
/*
* This file is part of DependencyCheck.
*

View File

@@ -1,4 +1,4 @@
package org.codesecure.dependencycheck.scanner;
package org.codesecure.dependencycheck.dependency;
/*
* This file is part of DependencyCheck.
*

View File

@@ -0,0 +1,130 @@
package org.codesecure.dependencycheck.dependency;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
/**
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class Identifier {
protected String value;
Identifier(String type, String value, String title, String url, String description) {
this.type = type;
this.value = value;
this.title = title;
this.url = url;
this.description = description;
}
/**
* Get the value of value
*
* @return the value of value
*/
public String getValue() {
return value;
}
/**
* Set the value of value
*
* @param value new value of value
*/
public void setValue(String value) {
this.value = value;
}
protected String title;
/**
* Get the value of title
*
* @return the value of title
*/
public String getTitle() {
return title;
}
/**
* Set the value of title
*
* @param title new value of title
*/
public void setTitle(String title) {
this.title = title;
}
protected String description;
/**
* Get the value of description
*
* @return the value of description
*/
public String getDescription() {
return description;
}
/**
* Set the value of description
*
* @param description new value of description
*/
public void setDescription(String description) {
this.description = description;
}
protected String url;
/**
* Get the value of url
*
* @return the value of url
*/
public String getUrl() {
return url;
}
/**
* Set the value of url
*
* @param url new value of url
*/
public void setUrl(String url) {
this.url = url;
}
protected String type;
/**
* Get the value of type
*
* @return the value of type
*/
public String getType() {
return type;
}
/**
* <p>Set the value of type.</p><p>Example would be "CPE".</p>
*
* @param type new value of type
*/
public void setType(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,12 @@
/**
* <html>
* <head>
* <title>org.codesecure.dependencycheck.dependency</title>
* </head>
* <body>
* Contains the core Dependency implementation.
* </body>
* </html>
*/
package org.codesecure.dependencycheck.dependency;

View File

@@ -35,7 +35,7 @@ import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.config.EasyFactoryConfiguration;
import org.codesecure.dependencycheck.scanner.Dependency;
import org.codesecure.dependencycheck.dependency.Dependency;
/**
*

View File

@@ -1,167 +0,0 @@
package org.codesecure.dependencycheck.scanner;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Scans files, directories, etc. for Dependencies. Analyzers are loaded and
* used to process the files found by the scanner, if a file is encountered and
* an Analyzer is associated with the file type then the file is turned into a
* dependency by the Analyzer.
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class Scanner {
/**
* The list of dependencies.
*/
protected List<Dependency> dependencies = new ArrayList<Dependency>();
/**
* A List of analyzers.
*/
protected List<Analyzer> analyzers = new ArrayList<Analyzer>();
/**
* Creates a new Scanner.
*/
public Scanner() {
loadAnalyzers();
}
/**
* Loads the analyzers specified in the configuration file (or system properties).
*/
private void loadAnalyzers() {
AnalyzerService service = AnalyzerService.getInstance();
Iterator<Analyzer> iterator = service.getAnalyzers();
while (iterator.hasNext()) {
Analyzer a = iterator.next();
analyzers.add(a);
}
}
/**
* Get the List of the analyzers.
*
* @return the analyzers loaded
*/
public List<Analyzer> getAnalyzers() {
return analyzers;
}
/**
* Get the dependencies identified
*
* @return the dependencies identified
*/
public List<Dependency> getDependencies() {
return dependencies;
}
/**
* Scans a given file or directory. If a directory is specified, it will be
* scanned recursively.
* Any dependencies identified are added to the dependency collection.
*
* @param path the path to a file or directory to be analyzed.
*/
public void scan(String path) {
File file = new File(path);
if (file.exists()) {
if (file.isDirectory()) {
scanDirectory(file);
} else {
scanFile(file);
}
}
}
/**
* Recursively scans files and directories.
* Any dependencies identified are added to the dependency collection.
*
* @param dir the directory to scan.
*/
protected void scanDirectory(File dir) {
File[] files = dir.listFiles();
for (File f : files) {
if (f.isDirectory()) {
scanDirectory(f);
} else {
scanFile(f);
}
}
}
/**
* Scans a specified file. If a dependency is identified it is added to the
* dependency collection.
*
* @param file The file to scan.
*/
protected void scanFile(File file) {
if (!file.isFile()) {
String msg = String.format("Path passed to scanFile(File) is not a file: %s.", file.toString());
Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
}
String fileName = file.getName();
String extension = getFileExtension(fileName);
if (extension != null) {
for (Analyzer a : analyzers) {
if (a.supportsExtension(extension)) {
try {
Dependency dependency = a.insepct(file);
if (dependency != null) {
dependencies.add(dependency);
break;
}
} catch (IOException ex) {
String msg = String.format("IOException occured while scanning the file '%s'.", file.toString());
Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, msg, ex);
}
}
}
} else {
String msg = String.format("No files extension found on file '%s'. The file was not analyzed.", file.toString());
Logger.getLogger(Scanner.class.getName()).log(Level.WARNING, msg);
}
}
/**
* Returns the file extension for a specified file.
* @param fileName the file name to retrieve the file extension from.
* @return the file extension.
*/
protected String getFileExtension(String fileName) {
String ret = null;
int pos = fileName.lastIndexOf(".");
if (pos >= 0) {
ret = fileName.substring(pos + 1, fileName.length());
}
return ret;
}
}

View File

@@ -0,0 +1,41 @@
package org.codesecure.dependencycheck.utils;
/*
* This file is part of DependencyCheck.
*
* DependencyCheck 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.
*
* DependencyCheck 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 DependencyCheck. If not, see http://www.gnu.org/licenses/.
*
* Copyright (c) 2012 Jeremy Long. All Rights Reserved.
*/
/**
* A collection of utilities for processing information about files.
*
* @author Jeremy Long (jeremy.long@gmail.com)
*/
public class FileUtils {
/**
* Returns the (lowercase) file extension for a specified file.
* @param fileName the file name to retrieve the file extension from.
* @return the file extension.
*/
public static String getFileExtension(String fileName) {
String ret = null;
int pos = fileName.lastIndexOf(".");
if (pos >= 0) {
ret = fileName.substring(pos + 1, fileName.length()).toLowerCase();
}
return ret;
}
}