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