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) 2014 Jeremy Long. All Rights Reserved.
17   */
18  package org.owasp.dependencycheck.data.central;
19  
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.net.HttpURLConnection;
23  import java.net.URL;
24  import java.util.ArrayList;
25  import java.util.List;
26  import javax.xml.parsers.DocumentBuilder;
27  import javax.xml.xpath.XPath;
28  import javax.xml.xpath.XPathConstants;
29  import javax.xml.xpath.XPathFactory;
30  import org.owasp.dependencycheck.data.nexus.MavenArtifact;
31  import org.owasp.dependencycheck.utils.Settings;
32  import org.owasp.dependencycheck.utils.URLConnectionFactory;
33  import org.owasp.dependencycheck.utils.XmlUtils;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  import org.w3c.dom.Document;
37  import org.w3c.dom.NodeList;
38  
39  /**
40   * Class of methods to search Maven Central via Central.
41   *
42   * @author colezlaw
43   */
44  public class CentralSearch {
45  
46      /**
47       * The URL for the Central service
48       */
49      private final URL rootURL;
50  
51      /**
52       * Whether to use the Proxy when making requests
53       */
54      private final boolean useProxy;
55  
56      /**
57       * Used for logging.
58       */
59      private static final Logger LOGGER = LoggerFactory.getLogger(CentralSearch.class);
60  
61      /**
62       * Creates a NexusSearch for the given repository URL.
63       *
64       * @param rootURL the URL of the repository on which searches should
65       * execute. Only parameters are added to this (so it should end in /select)
66       */
67      public CentralSearch(URL rootURL) {
68          this.rootURL = rootURL;
69          if (null != Settings.getString(Settings.KEYS.PROXY_SERVER)) {
70              useProxy = true;
71              LOGGER.debug("Using proxy");
72          } else {
73              useProxy = false;
74              LOGGER.debug("Not using proxy");
75          }
76      }
77  
78      /**
79       * Searches the configured Central URL for the given sha1 hash. If the
80       * artifact is found, a <code>MavenArtifact</code> is populated with the
81       * GAV.
82       *
83       * @param sha1 the SHA-1 hash string for which to search
84       * @return the populated Maven GAV.
85       * @throws IOException if it's unable to connect to the specified repository
86       * or if the specified artifact is not found.
87       */
88      public List<MavenArtifact> searchSha1(String sha1) throws IOException {
89          if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) {
90              throw new IllegalArgumentException("Invalid SHA1 format");
91          }
92          List<MavenArtifact> result = null;
93          final URL url = new URL(rootURL + String.format("?q=1:\"%s\"&wt=xml", sha1));
94  
95          LOGGER.debug("Searching Central url {}", url);
96  
97          // Determine if we need to use a proxy. The rules:
98          // 1) If the proxy is set, AND the setting is set to true, use the proxy
99          // 2) Otherwise, don't use the proxy (either the proxy isn't configured,
100         // or proxy is specifically set to false)
101         final HttpURLConnection conn = URLConnectionFactory.createHttpURLConnection(url, useProxy);
102 
103         conn.setDoOutput(true);
104 
105         // JSON would be more elegant, but there's not currently a dependency
106         // on JSON, so don't want to add one just for this
107         conn.addRequestProperty("Accept", "application/xml");
108         conn.connect();
109 
110         if (conn.getResponseCode() == 200) {
111             boolean missing = false;
112             try {
113                 final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
114                 final Document doc = builder.parse(conn.getInputStream());
115                 final XPath xpath = XPathFactory.newInstance().newXPath();
116                 final String numFound = xpath.evaluate("/response/result/@numFound", doc);
117                 if ("0".equals(numFound)) {
118                     missing = true;
119                 } else {
120                     result = new ArrayList<MavenArtifact>();
121                     final NodeList docs = (NodeList) xpath.evaluate("/response/result/doc", doc, XPathConstants.NODESET);
122                     for (int i = 0; i < docs.getLength(); i++) {
123                         final String g = xpath.evaluate("./str[@name='g']", docs.item(i));
124                         LOGGER.trace("GroupId: {}", g);
125                         final String a = xpath.evaluate("./str[@name='a']", docs.item(i));
126                         LOGGER.trace("ArtifactId: {}", a);
127                         final String v = xpath.evaluate("./str[@name='v']", docs.item(i));
128                         NodeList atts = (NodeList) xpath.evaluate("./arr[@name='ec']/str", docs.item(i), XPathConstants.NODESET);
129                         boolean pomAvailable = false;
130                         boolean jarAvailable = false;
131                         for (int x = 0; x < atts.getLength(); x++) {
132                             final String tmp = xpath.evaluate(".", atts.item(x));
133                             if (".pom".equals(tmp)) {
134                                 pomAvailable = true;
135                             } else if (".jar".equals(tmp)) {
136                                 jarAvailable = true;
137                             }
138                         }
139 
140                         atts = (NodeList) xpath.evaluate("./arr[@name='tags']/str", docs.item(i), XPathConstants.NODESET);
141                         boolean useHTTPS = false;
142                         for (int x = 0; x < atts.getLength(); x++) {
143                             final String tmp = xpath.evaluate(".", atts.item(x));
144                             if ("https".equals(tmp)) {
145                                 useHTTPS = true;
146                             }
147                         }
148                         LOGGER.trace("Version: {}", v);
149                         result.add(new MavenArtifact(g, a, v, jarAvailable, pomAvailable, useHTTPS));
150                     }
151                 }
152             } catch (Throwable e) {
153                 // Anything else is jacked up XML stuff that we really can't recover from well
154                 throw new IOException(e.getMessage(), e);
155             }
156 
157             if (missing) {
158                 throw new FileNotFoundException("Artifact not found in Central");
159             }
160         } else {
161             LOGGER.debug("Could not connect to Central received response code: {} {}",
162                     conn.getResponseCode(), conn.getResponseMessage());
163             throw new IOException("Could not connect to Central");
164         }
165         return result;
166     }
167 }