View Javadoc
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.xml.suppression;
19  
20  import java.util.ArrayList;
21  import java.util.Calendar;
22  import java.util.List;
23  import java.util.Optional;
24  import javax.annotation.concurrent.NotThreadSafe;
25  import org.owasp.dependencycheck.exception.ParseException;
26  import org.owasp.dependencycheck.utils.DateUtil;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  import org.xml.sax.Attributes;
30  import org.xml.sax.SAXException;
31  import org.xml.sax.helpers.DefaultHandler;
32  
33  /**
34   * A handler to load suppression rules. In the input xml a suppression rule can be part of a {@code suppressionGroup}. In that
35   * case the attributes set on group element will act as default values for child suppressions.
36   *
37   * @author Jeremy Long
38   */
39  @NotThreadSafe
40  public class SuppressionHandler extends DefaultHandler {
41  
42      /**
43       * The logger.
44       */
45      private static final Logger LOGGER = LoggerFactory.getLogger(SuppressionHandler.class);
46  
47      /**
48       * The suppressionGroup node, indicates the start of a new suppressionGroup.
49       */
50      public static final String SUPPRESSION_GROUP = "suppressionGroup";
51      /**
52       * The suppress node, indicates the start of a new rule.
53       */
54      public static final String SUPPRESS = "suppress";
55      /**
56       * The file path element name.
57       */
58      public static final String FILE_PATH = "filePath";
59      /**
60       * The sha1 hash element name.
61       */
62      public static final String SHA1 = "sha1";
63      /**
64       * The CVE element name.
65       */
66      public static final String CVE = "cve";
67      /**
68       * The vulnerabilityName element name.
69       */
70      public static final String VULNERABILITY_NAME = "vulnerabilityName";
71  
72      /**
73       * The CVE element name.
74       */
75      public static final String NOTES = "notes";
76  
77      /**
78       * The CPE element name.
79       */
80      public static final String CPE = "cpe";
81      /**
82       * The CWE element name.
83       */
84      public static final String CWE = "cwe";
85      /**
86       * The GAV element name.
87       */
88      public static final String GAV = "gav";
89      /**
90       * The Package URL element name.
91       */
92      public static final String PACKAGE_URL = "packageUrl";
93      /**
94       * The cvssBelow element name.
95       */
96      public static final String CVSS_BELOW = "cvssBelow";
97      /**
98       * The cvssV2Below element name.
99       */
100     public static final String CVSS_V2_BELOW = "cvssV2Below";
101     /**
102      * The cvssV3Below element name.
103      */
104     public static final String CVSS_V3_BELOW = "cvssV3Below";
105     /**
106      * The cvssV4Below element name.
107      */
108     public static final String CVSS_V4_BELOW = "cvssV4Below";
109     /**
110      * A list of suppression rules.
111      */
112     private final List<SuppressionRule> suppressionRules = new ArrayList<>();
113     /**
114      * The current rule being read.
115      */
116     private SuppressionRule rule;
117     /**
118      * The attributes of the node being read.
119      */
120     private Attributes currentAttributes;
121     /**
122      * The current node text being extracted from the element.
123      */
124     private StringBuilder currentText;
125 
126     private Boolean groupBase = null;
127     private Calendar groupUntil = null;
128 
129 
130     /**
131      * Get the value of suppressionRules.
132      *
133      * @return the value of suppressionRules
134      */
135     public List<SuppressionRule> getSuppressionRules() {
136         return suppressionRules;
137     }
138 
139     /**
140      * Handles the start element event.
141      *
142      * @param uri the URI of the element being processed
143      * @param localName the local name of the element being processed
144      * @param qName the qName of the element being processed
145      * @param attributes the attributes of the element being processed
146      * @throws SAXException thrown if there is an exception processing
147      */
148     @Override
149     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
150         currentAttributes = attributes;
151         currentText = new StringBuilder();
152 
153         if (SUPPRESSION_GROUP.equals(qName)) {
154             groupBase = attributes.getValue("base") != null ? Boolean.parseBoolean(attributes.getValue("base")) : null;
155             groupUntil = parseUntilAttribute(attributes).orElse(null);
156         }
157 
158         if (SUPPRESS.equals(qName)) {
159             Boolean base = attributes.getValue("base") != null ? Boolean.parseBoolean(attributes.getValue("base")) : null;
160             Calendar until = parseUntilAttribute(attributes).orElse(null);
161 
162             rule = new SuppressionRule();
163             //If suppression doesn't have attribute set, use that of the group (if in group).
164             rule.setBase(base != null ? base : groupBase);
165             rule.setUntil(until != null ? until : groupUntil);
166         }
167     }
168 
169     /**
170      * Read the provided {@code attributes} for attribute {@code until}. Return {@link Calendar} object if attribute is
171      * present and can be parsed.
172      *
173      * @return empty if attribute {@code until} is not present.
174      * @throws SAXException if attribute {@code until} is present but value can not be parsed as {@link Calendar}.
175      */
176     private static Optional<Calendar> parseUntilAttribute(Attributes attributes) throws SAXException {
177         String untilStr = attributes.getValue("until");
178         if (untilStr != null) {
179             try {
180                 return Optional.of(DateUtil.parseXmlDate(untilStr));
181             } catch (ParseException ex) {
182                 throw new SAXException("Unable to parse attribute 'until': " + untilStr, ex);
183             }
184         } else {
185             return Optional.empty();
186         }
187     }
188 
189     /**
190      * Handles the end element event.
191      *
192      * @param uri the URI of the element
193      * @param localName the local name of the element
194      * @param qName the qName of the element
195      * @throws SAXException thrown if there is an exception processing
196      */
197     @Override
198     public void endElement(String uri, String localName, String qName) throws SAXException {
199         if (null != qName) {
200             switch (qName) {
201                 case SUPPRESS:
202                     if (rule.getUntil() != null && rule.getUntil().before(Calendar.getInstance())) {
203                         LOGGER.info("Suppression is expired for rule: {}", rule);
204                     } else {
205                         suppressionRules.add(rule);
206                     }
207                     rule = null;
208                     break;
209                 case SUPPRESSION_GROUP:
210                     groupBase = null;
211                     groupUntil = null;
212                     break;
213                 case FILE_PATH:
214                     rule.setFilePath(processPropertyType());
215                     break;
216                 case SHA1:
217                     rule.setSha1(currentText.toString().trim());
218                     break;
219                 case GAV:
220                     rule.setGav(processPropertyType());
221                     break;
222                 case PACKAGE_URL:
223                     rule.setPackageUrl(processPropertyType());
224                     break;
225                 case CPE:
226                     rule.addCpe(processPropertyType());
227                     break;
228                 case CWE:
229                     rule.addCwe(currentText.toString().trim());
230                     break;
231                 case CVE:
232                     rule.addCve(currentText.toString().trim());
233                     break;
234                 case VULNERABILITY_NAME:
235                     rule.addVulnerabilityName(processPropertyType());
236                     break;
237                 case NOTES:
238                     // Check that the notes element is from a suppression and not a suppressionGroup.
239                     if(rule != null) {
240                         rule.setNotes(currentText.toString().trim());
241                     }
242                     break;
243                 case CVSS_BELOW:
244                     final Double cvss = Double.valueOf(currentText.toString().trim());
245                     rule.addCvssBelow(cvss);
246                     break;
247                 case CVSS_V2_BELOW:
248                     final Double cvssV2 = Double.valueOf(currentText.toString().trim());
249                     rule.addCvssV2Below(cvssV2);
250                     break;
251                 case CVSS_V3_BELOW:
252                     final Double cvssV3 = Double.valueOf(currentText.toString().trim());
253                     rule.addCvssV3Below(cvssV3);
254                     break;
255                 case CVSS_V4_BELOW:
256                     final Double cvssV4 = Double.valueOf(currentText.toString().trim());
257                     rule.addCvssV4Below(cvssV4);
258                     break;
259                 default:
260                     break;
261             }
262         }
263     }
264 
265     /**
266      * Collects the body text of the node being processed.
267      *
268      * @param ch the char array of text
269      * @param start the start position to copy text from in the char array
270      * @param length the number of characters to copy from the char array
271      * @throws SAXException thrown if there is a parsing exception
272      */
273     @Override
274     public void characters(char[] ch, int start, int length) throws SAXException {
275         currentText.append(ch, start, length);
276     }
277 
278     /**
279      * Processes field members that have been collected during the characters
280      * and startElement method to construct a PropertyType object.
281      *
282      * @return a PropertyType object
283      */
284     private PropertyType processPropertyType() {
285         final PropertyType pt = new PropertyType();
286         pt.setValue(currentText.toString().trim());
287         if (currentAttributes != null && currentAttributes.getLength() > 0) {
288             final String regex = currentAttributes.getValue("regex");
289             if (regex != null) {
290                 pt.setRegex(Boolean.parseBoolean(regex));
291             }
292             final String caseSensitive = currentAttributes.getValue("caseSensitive");
293             if (caseSensitive != null) {
294                 pt.setCaseSensitive(Boolean.parseBoolean(caseSensitive));
295             }
296         }
297         return pt;
298     }
299 }