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