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