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.nexus;
19  
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.net.HttpURLConnection;
23  import java.net.URL;
24  import javax.xml.parsers.DocumentBuilder;
25  import javax.xml.xpath.XPath;
26  import javax.xml.xpath.XPathFactory;
27  
28  import org.owasp.dependencycheck.utils.URLConnectionFactory;
29  import org.owasp.dependencycheck.utils.XmlUtils;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  import org.w3c.dom.Document;
33  
34  /**
35   * Class of methods to search Nexus repositories.
36   *
37   * @author colezlaw
38   */
39  public class NexusSearch {
40  
41      /**
42       * The root URL for the Nexus repository service.
43       */
44      private final URL rootURL;
45  
46      /**
47       * Whether to use the Proxy when making requests.
48       */
49      private final boolean useProxy;
50      /**
51       * Used for logging.
52       */
53      private static final Logger LOGGER = LoggerFactory.getLogger(NexusSearch.class);
54  
55      /**
56       * Creates a NexusSearch for the given repository URL.
57       *
58       * @param rootURL the root URL of the repository on which searches should
59       * execute. full URL's are calculated relative to this URL, so it should end
60       * with a /
61       * @param useProxy flag indicating if the proxy settings should be used
62       */
63      public NexusSearch(URL rootURL, boolean useProxy) {
64          this.rootURL = rootURL;
65          this.useProxy = useProxy;
66          LOGGER.debug("Using proxy: {}", useProxy);
67      }
68  
69      /**
70       * Searches the configured Nexus repository for the given sha1 hash. If the
71       * artifact is found, a <code>MavenArtifact</code> is populated with the
72       * coordinate information.
73       *
74       * @param sha1 The SHA-1 hash string for which to search
75       * @return the populated Maven coordinates
76       * @throws IOException if it's unable to connect to the specified repository
77       * or if the specified artifact is not found.
78       */
79      public MavenArtifact searchSha1(String sha1) throws IOException {
80          if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) {
81              throw new IllegalArgumentException("Invalid SHA1 format");
82          }
83  
84          final URL url = new URL(rootURL, String.format("identify/sha1/%s",
85                  sha1.toLowerCase()));
86  
87          LOGGER.debug("Searching Nexus url {}", url);
88  
89          // Determine if we need to use a proxy. The rules:
90          // 1) If the proxy is set, AND the setting is set to true, use the proxy
91          // 2) Otherwise, don't use the proxy (either the proxy isn't configured,
92          // or proxy is specifically set to false
93          HttpURLConnection conn;
94          conn = URLConnectionFactory.createHttpURLConnection(url, useProxy);
95          conn.setDoOutput(true);
96  
97          // JSON would be more elegant, but there's not currently a dependency
98          // on JSON, so don't want to add one just for this
99          conn.addRequestProperty("Accept", "application/xml");
100         conn.connect();
101 
102         switch (conn.getResponseCode()) {
103             case 200:
104                 try {
105                     final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
106                     final Document doc = builder.parse(conn.getInputStream());
107                     final XPath xpath = XPathFactory.newInstance().newXPath();
108                     final String groupId = xpath
109                             .evaluate(
110                                     "/org.sonatype.nexus.rest.model.NexusArtifact/groupId",
111                                     doc);
112                     final String artifactId = xpath.evaluate(
113                             "/org.sonatype.nexus.rest.model.NexusArtifact/artifactId",
114                             doc);
115                     final String version = xpath
116                             .evaluate(
117                                     "/org.sonatype.nexus.rest.model.NexusArtifact/version",
118                                     doc);
119                     final String link = xpath
120                             .evaluate(
121                                     "/org.sonatype.nexus.rest.model.NexusArtifact/artifactLink",
122                                     doc);
123                     final String pomLink = xpath
124                             .evaluate(
125                                     "/org.sonatype.nexus.rest.model.NexusArtifact/pomLink",
126                                     doc);
127                     final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
128                     if (link != null && !link.isEmpty()) {
129                         ma.setArtifactUrl(link);
130                     }
131                     if (pomLink != null && !pomLink.isEmpty()) {
132                         ma.setPomUrl(pomLink);
133                     }
134                     return ma;
135                 } catch (Throwable e) {
136                     // Anything else is jacked-up XML stuff that we really can't recover
137                     // from well
138                     throw new IOException(e.getMessage(), e);
139                 }
140             case 404:
141                 throw new FileNotFoundException("Artifact not found in Nexus");
142             default:
143                 LOGGER.debug("Could not connect to Nexus received response code: {} {}",
144                         conn.getResponseCode(), conn.getResponseMessage());
145                 throw new IOException("Could not connect to Nexus");
146         }
147     }
148 
149     /**
150      * Do a preflight request to see if the repository is actually working.
151      *
152      * @return whether the repository is listening and returns the /status URL
153      * correctly
154      */
155     public boolean preflightRequest() {
156         HttpURLConnection conn;
157         try {
158             final URL url = new URL(rootURL, "status");
159             conn = URLConnectionFactory.createHttpURLConnection(url, useProxy);
160             conn.addRequestProperty("Accept", "application/xml");
161             conn.connect();
162             if (conn.getResponseCode() != 200) {
163                 LOGGER.warn("Expected 200 result from Nexus, got {}", conn.getResponseCode());
164                 return false;
165             }
166             final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
167 
168             final Document doc = builder.parse(conn.getInputStream());
169             if (!"status".equals(doc.getDocumentElement().getNodeName())) {
170                 LOGGER.warn("Expected root node name of status, got {}", doc.getDocumentElement().getNodeName());
171                 return false;
172             }
173         } catch (Throwable e) {
174             return false;
175         }
176 
177         return true;
178     }
179 }
180 
181 // vim: cc=120:sw=4:ts=4:sts=4