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