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