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