1 /* 2 * This file is part of dependency-check-core. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 * Copyright (c) 2013 Jeremy Long. All Rights Reserved. 17 */ 18 package org.owasp.dependencycheck.analyzer; 19 20 import java.io.File; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.net.MalformedURLException; 24 import java.net.URL; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Set; 28 import java.util.regex.Pattern; 29 import javax.annotation.concurrent.ThreadSafe; 30 import org.owasp.dependencycheck.Engine; 31 import org.owasp.dependencycheck.analyzer.exception.AnalysisException; 32 import org.owasp.dependencycheck.dependency.Dependency; 33 import org.owasp.dependencycheck.exception.InitializationException; 34 import org.owasp.dependencycheck.xml.suppression.SuppressionParseException; 35 import org.owasp.dependencycheck.xml.suppression.SuppressionParser; 36 import org.owasp.dependencycheck.xml.suppression.SuppressionRule; 37 import org.owasp.dependencycheck.utils.DownloadFailedException; 38 import org.owasp.dependencycheck.utils.Downloader; 39 import org.owasp.dependencycheck.utils.FileUtils; 40 import org.owasp.dependencycheck.utils.Settings; 41 import org.slf4j.Logger; 42 import org.slf4j.LoggerFactory; 43 import org.xml.sax.SAXException; 44 45 /** 46 * Abstract base suppression analyzer that contains methods for parsing the 47 * suppression XML file. 48 * 49 * @author Jeremy Long 50 */ 51 @ThreadSafe 52 public abstract class AbstractSuppressionAnalyzer extends AbstractAnalyzer { 53 54 /** 55 * The Logger for use throughout the class. 56 */ 57 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSuppressionAnalyzer.class); 58 /** 59 * The list of suppression rules. 60 */ 61 private List<SuppressionRule> rules = new ArrayList<>(); 62 63 /** 64 * Get the number of suppression rules. 65 * 66 * @return the number of suppression rules 67 */ getRuleCount()68 protected int getRuleCount() { 69 return rules.size(); 70 } 71 72 /** 73 * Returns a list of file EXTENSIONS supported by this analyzer. 74 * 75 * @return a list of file EXTENSIONS supported by this analyzer. 76 */ getSupportedExtensions()77 public Set<String> getSupportedExtensions() { 78 return null; 79 } 80 81 /** 82 * The prepare method loads the suppression XML file. 83 * 84 * @param engine a reference the dependency-check engine 85 * @throws InitializationException thrown if there is an exception 86 */ 87 @Override prepareAnalyzer(Engine engine)88 public synchronized void prepareAnalyzer(Engine engine) throws InitializationException { 89 if (rules.isEmpty()) { 90 try { 91 loadSuppressionBaseData(); 92 } catch (SuppressionParseException ex) { 93 throw new InitializationException("Error initializing the suppression analyzer: " + ex.getLocalizedMessage(), ex, true); 94 } 95 96 try { 97 loadSuppressionData(); 98 } catch (SuppressionParseException ex) { 99 throw new InitializationException("Warn initializing the suppression analyzer: " + ex.getLocalizedMessage(), ex, false); 100 } 101 } 102 } 103 104 @Override analyzeDependency(Dependency dependency, Engine engine)105 protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException { 106 if (rules.isEmpty()) { 107 return; 108 } 109 for (final SuppressionRule rule : rules) { 110 rule.process(dependency); 111 } 112 } 113 114 /** 115 * Loads all the suppression rules files configured in the {@link Settings}. 116 * 117 * @throws SuppressionParseException thrown if the XML cannot be parsed. 118 */ loadSuppressionData()119 private void loadSuppressionData() throws SuppressionParseException { 120 final List<SuppressionRule> ruleList = new ArrayList<>(); 121 final SuppressionParser parser = new SuppressionParser(); 122 final String[] suppressionFilePaths = getSettings().getArray(Settings.KEYS.SUPPRESSION_FILE); 123 final List<String> failedLoadingFiles = new ArrayList<>(); 124 if (suppressionFilePaths != null && suppressionFilePaths.length > 0) { 125 // Load all the suppression file paths 126 for (final String suppressionFilePath : suppressionFilePaths) { 127 try { 128 ruleList.addAll(loadSuppressionFile(parser, suppressionFilePath)); 129 } catch (SuppressionParseException ex) { 130 final String msg = String.format("Failed to load %s, caused by %s. ", suppressionFilePath, ex.getMessage()); 131 failedLoadingFiles.add(msg); 132 } 133 } 134 } 135 LOGGER.debug("{} suppression rules were loaded.", ruleList.size()); 136 rules.addAll(ruleList); 137 if (!failedLoadingFiles.isEmpty()) { 138 LOGGER.debug("{} suppression files failed to load.", failedLoadingFiles.size()); 139 final StringBuilder sb = new StringBuilder(); 140 for (String item : failedLoadingFiles) { 141 sb.append(item); 142 } 143 throw new SuppressionParseException(sb.toString()); 144 } 145 } 146 147 /** 148 * Loads all the base suppression rules files. 149 * 150 * @throws SuppressionParseException thrown if the XML cannot be parsed. 151 */ loadSuppressionBaseData()152 private void loadSuppressionBaseData() throws SuppressionParseException { 153 final SuppressionParser parser = new SuppressionParser(); 154 List<SuppressionRule> ruleList; 155 try { 156 final InputStream in = FileUtils.getResourceAsStream("dependencycheck-base-suppression.xml"); 157 ruleList = parser.parseSuppressionRules(in); 158 } catch (SAXException ex) { 159 throw new SuppressionParseException("Unable to parse the base suppression data file", ex); 160 } 161 rules.addAll(ruleList); 162 } 163 164 /** 165 * Load a single suppression rules file from the path provided using the 166 * parser provided. 167 * 168 * @param parser the parser to use for loading the file 169 * @param suppressionFilePath the path to load 170 * @return the list of loaded suppression rules 171 * @throws SuppressionParseException thrown if the suppression file cannot 172 * be loaded and parsed. 173 */ loadSuppressionFile(final SuppressionParser parser, final String suppressionFilePath)174 private List<SuppressionRule> loadSuppressionFile(final SuppressionParser parser, 175 final String suppressionFilePath) throws SuppressionParseException { 176 LOGGER.debug("Loading suppression rules from '{}'", suppressionFilePath); 177 final List<SuppressionRule> list = new ArrayList<>(); 178 File file = null; 179 boolean deleteTempFile = false; 180 try { 181 final Pattern uriRx = Pattern.compile("^(https?|file)\\:.*", Pattern.CASE_INSENSITIVE); 182 if (uriRx.matcher(suppressionFilePath).matches()) { 183 deleteTempFile = true; 184 file = getSettings().getTempFile("suppression", "xml"); 185 final URL url = new URL(suppressionFilePath); 186 final Downloader downloader = new Downloader(getSettings()); 187 try { 188 downloader.fetchFile(url, file, false); 189 } catch (DownloadFailedException ex) { 190 LOGGER.trace("Failed download - first attempt", ex); 191 downloader.fetchFile(url, file, true); 192 } 193 } else { 194 file = new File(suppressionFilePath); 195 196 if (!file.exists()) { 197 try (InputStream suppressionsFromClasspath = FileUtils.getResourceAsStream(suppressionFilePath)) { 198 if (suppressionsFromClasspath != null) { 199 deleteTempFile = true; 200 file = getSettings().getTempFile("suppression", "xml"); 201 try { 202 org.apache.commons.io.FileUtils.copyInputStreamToFile(suppressionsFromClasspath, file); 203 } catch (IOException ex) { 204 throwSuppressionParseException("Unable to locate suppressions file in classpath", ex, suppressionFilePath); 205 } 206 } 207 } 208 } 209 } 210 if (file != null) { 211 if (!file.exists()) { 212 final String msg = String.format("Suppression file '%s' does not exist", file.getPath()); 213 LOGGER.warn(msg); 214 throw new SuppressionParseException(msg); 215 } 216 try { 217 list.addAll(parser.parseSuppressionRules(file)); 218 } catch (SuppressionParseException ex) { 219 LOGGER.warn("Unable to parse suppression xml file '{}'", file.getPath()); 220 LOGGER.warn(ex.getMessage()); 221 throw ex; 222 } 223 } 224 } catch (DownloadFailedException ex) { 225 throwSuppressionParseException("Unable to fetch the configured suppression file", ex, suppressionFilePath); 226 } catch (MalformedURLException ex) { 227 throwSuppressionParseException("Configured suppression file has an invalid URL", ex, suppressionFilePath); 228 } catch (SuppressionParseException ex) { 229 throw ex; 230 } catch (IOException ex) { 231 throwSuppressionParseException("Unable to create temp file for suppressions", ex, suppressionFilePath); 232 } finally { 233 if (deleteTempFile && file != null) { 234 FileUtils.delete(file); 235 } 236 } 237 return list; 238 } 239 240 /** 241 * Utility method to throw parse exceptions. 242 * 243 * @param message the exception message 244 * @param exception the cause of the exception 245 * @param suppressionFilePath the path file 246 * @throws SuppressionParseException throws the generated 247 * SuppressionParseException 248 */ throwSuppressionParseException(String message, Exception exception, String suppressionFilePath)249 private void throwSuppressionParseException(String message, Exception exception, String suppressionFilePath) throws SuppressionParseException { 250 LOGGER.warn(String.format(message + "'%s'", suppressionFilePath)); 251 LOGGER.debug("", exception); 252 throw new SuppressionParseException(message, exception); 253 } 254 } 255