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