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) 2015 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.update;
19  
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileNotFoundException;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.List;
28  import java.util.zip.GZIPInputStream;
29  import javax.xml.parsers.ParserConfigurationException;
30  import javax.xml.parsers.SAXParser;
31  import javax.xml.parsers.SAXParserFactory;
32  import org.apache.commons.io.FileUtils;
33  import static org.owasp.dependencycheck.data.nvdcve.DatabaseProperties.LAST_CPE_UPDATE;
34  import org.owasp.dependencycheck.data.update.cpe.CPEHandler;
35  import org.owasp.dependencycheck.data.update.cpe.Cpe;
36  import org.owasp.dependencycheck.data.update.exception.UpdateException;
37  import org.owasp.dependencycheck.utils.DateUtil;
38  import org.owasp.dependencycheck.utils.DownloadFailedException;
39  import org.owasp.dependencycheck.utils.Downloader;
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   *
47   * This class is currently unused and if enabled will likely not work on MySQL
48   * as the MERGE statement is used.
49   *
50   * The CpeUpdater is designed to download the CPE data file from NIST and import
51   * the data into the database. However, as this currently adds no beneficial
52   * data, compared to what is in the CPE data contained in the CVE data files,
53   * this class is not currently used. The code is being kept as a future update
54   * may utilize more data from the CPE XML files.
55   *
56   * @author Jeremy Long
57   */
58  public class CpeUpdater extends BaseUpdater implements CachedWebDataSource {
59  
60      /**
61       * Static logger.
62       */
63      private static final Logger LOGGER = LoggerFactory.getLogger(CpeUpdater.class);
64  
65      @Override
66      public void update() throws UpdateException {
67          try {
68              openDataStores();
69              if (updateNeeded()) {
70                  LOGGER.info("Updating the Common Platform Enumeration (CPE)");
71                  final File xml = downloadCpe();
72                  final List<Cpe> cpes = processXML(xml);
73                  getCveDB().deleteUnusedCpe();
74                  for (Cpe cpe : cpes) {
75                      getCveDB().addCpe(cpe.getValue(), cpe.getVendor(), cpe.getProduct());
76                  }
77                  final long now = System.currentTimeMillis();
78                  getProperties().save(LAST_CPE_UPDATE, Long.toString(now));
79                  LOGGER.info("CPE update complete");
80              }
81          } finally {
82              closeDataStores();
83          }
84      }
85  
86      /**
87       * Downloads the CPE XML file.
88       *
89       * @return the file reference to the CPE.xml file
90       * @throws UpdateException thrown if there is an issue downloading the XML
91       * file
92       */
93      private File downloadCpe() throws UpdateException {
94          File xml;
95          final URL url;
96          try {
97              url = new URL(Settings.getString(Settings.KEYS.CPE_URL));
98              xml = File.createTempFile("cpe", ".xml", Settings.getTempDirectory());
99              Downloader.fetchFile(url, xml);
100             if (url.toExternalForm().endsWith(".xml.gz")) {
101                 extractGzip(xml);
102             }
103 
104         } catch (MalformedURLException ex) {
105             throw new UpdateException("Invalid CPE URL", ex);
106         } catch (DownloadFailedException ex) {
107             throw new UpdateException("Unable to download CPE XML file", ex);
108         } catch (IOException ex) {
109             throw new UpdateException("Unable to create temporary file to download CPE", ex);
110         }
111         return xml;
112     }
113 
114     /**
115      * Parses the CPE XML file to return a list of CPE entries.
116      *
117      * @param xml the CPE data file
118      * @return the list of CPE entries
119      * @throws UpdateException thrown if there is an issue with parsing the XML
120      * file
121      */
122     private List<Cpe> processXML(final File xml) throws UpdateException {
123         try {
124             final SAXParserFactory factory = SAXParserFactory.newInstance();
125             factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
126             final SAXParser saxParser = factory.newSAXParser();
127             final CPEHandler handler = new CPEHandler();
128             saxParser.parse(xml, handler);
129             return handler.getData();
130         } catch (ParserConfigurationException ex) {
131             throw new UpdateException("Unable to parse CPE XML file due to SAX Parser Issue", ex);
132         } catch (SAXException ex) {
133             throw new UpdateException("Unable to parse CPE XML file due to SAX Parser Exception", ex);
134         } catch (IOException ex) {
135             throw new UpdateException("Unable to parse CPE XML file due to IO Failure", ex);
136         }
137     }
138 
139     /**
140      * Checks to find the last time the CPE data was refreshed and if it needs
141      * to be updated.
142      *
143      * @return true if the CPE data should be refreshed
144      */
145     private boolean updateNeeded() {
146         final long now = System.currentTimeMillis();
147         final int days = Settings.getInt(Settings.KEYS.CPE_MODIFIED_VALID_FOR_DAYS, 30);
148         long timestamp = 0;
149         final String ts = getProperties().getProperty(LAST_CPE_UPDATE);
150         if (ts != null && ts.matches("^[0-9]+$")) {
151             timestamp = Long.parseLong(ts);
152         }
153         return !DateUtil.withinDateRange(timestamp, now, days);
154     }
155 
156     /**
157      * Extracts the file contained in a gzip archive. The extracted file is
158      * placed in the exact same path as the file specified.
159      *
160      * @param file the archive file
161      * @throws FileNotFoundException thrown if the file does not exist
162      * @throws IOException thrown if there is an error extracting the file.
163      */
164     private void extractGzip(File file) throws FileNotFoundException, IOException {
165         //TODO - move this to a util class as it is duplicative of (copy of) code in the DownloadTask
166         final String originalPath = file.getPath();
167         final File gzip = new File(originalPath + ".gz");
168         if (gzip.isFile() && !gzip.delete()) {
169             LOGGER.debug("Failed to delete intial temporary file {}", gzip.toString());
170             gzip.deleteOnExit();
171         }
172         if (!file.renameTo(gzip)) {
173             throw new IOException("Unable to rename '" + file.getPath() + "'");
174         }
175         final File newfile = new File(originalPath);
176 
177         final byte[] buffer = new byte[4096];
178 
179         GZIPInputStream cin = null;
180         FileOutputStream out = null;
181         try {
182             cin = new GZIPInputStream(new FileInputStream(gzip));
183             out = new FileOutputStream(newfile);
184 
185             int len;
186             while ((len = cin.read(buffer)) > 0) {
187                 out.write(buffer, 0, len);
188             }
189         } finally {
190             if (cin != null) {
191                 try {
192                     cin.close();
193                 } catch (IOException ex) {
194                     LOGGER.trace("ignore", ex);
195                 }
196             }
197             if (out != null) {
198                 try {
199                     out.close();
200                 } catch (IOException ex) {
201                     LOGGER.trace("ignore", ex);
202                 }
203             }
204             if (gzip.isFile() && !FileUtils.deleteQuietly(gzip)) {
205                 LOGGER.debug("Failed to delete temporary file {}", gzip.toString());
206                 gzip.deleteOnExit();
207             }
208         }
209     }
210 }