001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.util.url;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.net.URL;
024: import java.net.UnknownHostException;
025: import java.text.ParseException;
026: import java.text.SimpleDateFormat;
027: import java.util.ArrayList;
028: import java.util.List;
029: import java.util.Locale;
030:
031: import org.apache.commons.httpclient.Header;
032: import org.apache.commons.httpclient.HttpClient;
033: import org.apache.commons.httpclient.HttpException;
034: import org.apache.commons.httpclient.HttpMethodBase;
035: import org.apache.commons.httpclient.HttpStatus;
036: import org.apache.commons.httpclient.UsernamePasswordCredentials;
037: import org.apache.commons.httpclient.auth.AuthPolicy;
038: import org.apache.commons.httpclient.methods.GetMethod;
039: import org.apache.commons.httpclient.methods.HeadMethod;
040: import org.apache.ivy.util.CopyProgressListener;
041: import org.apache.ivy.util.Credentials;
042: import org.apache.ivy.util.FileUtil;
043: import org.apache.ivy.util.Message;
044:
045: /**
046: *
047: */
048: public class HttpClientHandler extends AbstractURLHandler {
049: private static final SimpleDateFormat LAST_MODIFIED_FORMAT = new SimpleDateFormat(
050: "EEE, d MMM yyyy HH:mm:ss z", Locale.US);
051:
052: // proxy configuration: obtain from system properties
053: private int proxyPort;
054:
055: private String proxyRealm = null;
056:
057: private String proxyHost = null;
058:
059: private String proxyUserName = null;
060:
061: private String proxyPasswd = null;
062:
063: private HttpClientHelper httpClientHelper;
064:
065: public HttpClientHandler() {
066: configureProxy();
067: }
068:
069: private void configureProxy() {
070: proxyRealm = null;
071: // no equivalent for realm in jdk proxy support ?
072: proxyHost = System.getProperty("http.proxyHost");
073: // TODO constant is better ...
074: if (useProxy()) {
075: proxyPort = Integer.parseInt(System.getProperty(
076: "http.proxyPort", "80"));
077: proxyUserName = System.getProperty("http.proxyUser");
078: proxyPasswd = System.getProperty("http.proxyPassword");
079: // It seems there is no equivalent in HttpClient for
080: // 'http.nonProxyHosts' property
081: Message.verbose("proxy configured: host=" + proxyHost
082: + " port=" + proxyPort + " user=" + proxyUserName);
083: } else {
084: Message.verbose("no proxy configured");
085: }
086: }
087:
088: public InputStream openStream(URL url) throws IOException {
089: GetMethod get = doGet(url);
090: return new GETInputStream(get);
091: }
092:
093: public void download(URL src, File dest, CopyProgressListener l)
094: throws IOException {
095: GetMethod get = doGet(src);
096: FileUtil.copy(get.getResponseBodyAsStream(), dest, l);
097: get.releaseConnection();
098: }
099:
100: public URLInfo getURLInfo(URL url) {
101: return getURLInfo(url, 0);
102: }
103:
104: public URLInfo getURLInfo(URL url, int timeout) {
105: HeadMethod head = null;
106: try {
107: head = doHead(url, timeout);
108: int status = head.getStatusCode();
109: head.releaseConnection();
110: if (status == HttpStatus.SC_OK) {
111: return new URLInfo(true,
112: getResponseContentLength(head),
113: getLastModified(head));
114: }
115: if (status == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
116: Message.error("Your proxy requires authentication.");
117: } else if (String.valueOf(status).startsWith("4")) {
118: Message.verbose("CLIENT ERROR: " + head.getStatusText()
119: + " url=" + url);
120: } else if (String.valueOf(status).startsWith("5")) {
121: Message.warn("SERVER ERROR: " + head.getStatusText()
122: + " url=" + url);
123: }
124: Message.debug("HTTP response status: " + status + "="
125: + head.getStatusText() + " url=" + url);
126: } catch (HttpException e) {
127: Message.error("HttpClientHandler: " + e.getMessage() + ":"
128: + e.getReasonCode() + "=" + e.getReason() + " url="
129: + url);
130: } catch (UnknownHostException e) {
131: Message.warn("Host " + e.getMessage() + " not found. url="
132: + url);
133: Message
134: .info("You probably access the destination server through "
135: + "a proxy server that is not well configured.");
136: } catch (IOException e) {
137: Message.error("HttpClientHandler: " + e.getMessage()
138: + " url=" + url);
139: } catch (IllegalArgumentException e) {
140: // thrown by HttpClient to indicate the URL is not valid, this happens for instance
141: // when trying to download a dynamic version (cfr IVY-390)
142: } finally {
143: if (head != null) {
144: head.releaseConnection();
145: }
146: }
147: return UNAVAILABLE;
148: }
149:
150: private long getLastModified(HeadMethod head) {
151: Header header = head.getResponseHeader("last-modified");
152: if (header != null) {
153: String lastModified = header.getValue();
154: try {
155: return LAST_MODIFIED_FORMAT.parse(lastModified)
156: .getTime();
157: } catch (ParseException e) {
158: // ignored
159: }
160: return System.currentTimeMillis();
161: } else {
162: return System.currentTimeMillis();
163: }
164: }
165:
166: private long getResponseContentLength(HeadMethod head) {
167: return getHttpClientHelper().getResponseContentLength(head);
168: }
169:
170: private HttpClientHelper getHttpClientHelper() {
171: if (httpClientHelper == null) {
172: // use commons httpclient 3.0 if available
173: try {
174: HttpMethodBase.class.getMethod(
175: "getResponseContentLength", new Class[0]);
176: httpClientHelper = new HttpClientHelper3x();
177: Message.verbose("using commons httpclient 3.x helper");
178: } catch (SecurityException e) {
179: Message
180: .verbose("unable to get access to getResponseContentLength of "
181: + "commons-httpclient HeadMethod. Please use commons-httpclient 3.0 or "
182: + "use ivy with sufficient security permissions.");
183: Message.verbose("exception: " + e.getMessage());
184: httpClientHelper = new HttpClientHelper2x();
185: Message.verbose("using commons httpclient 2.x helper");
186: } catch (NoSuchMethodException e) {
187: httpClientHelper = new HttpClientHelper2x();
188: Message.verbose("using commons httpclient 2.x helper");
189: }
190: }
191: return httpClientHelper;
192: }
193:
194: public int getHttpClientMajorVersion() {
195: HttpClientHelper helper = getHttpClientHelper();
196: return helper.getHttpClientMajorVersion();
197: }
198:
199: private GetMethod doGet(URL url) throws IOException {
200: HttpClient client = getClient(url);
201:
202: GetMethod get = new GetMethod(url.toExternalForm());
203: get.setDoAuthentication(useAuthentication(url)
204: || useProxyAuthentication());
205: client.executeMethod(get);
206: return get;
207: }
208:
209: private HeadMethod doHead(URL url, int timeout) throws IOException {
210: HttpClient client = getClient(url);
211: client.setTimeout(timeout);
212:
213: HeadMethod head = new HeadMethod(url.toExternalForm());
214: head.setDoAuthentication(useAuthentication(url)
215: || useProxyAuthentication());
216: client.executeMethod(head);
217: return head;
218: }
219:
220: private HttpClient getClient(URL url) {
221: HttpClient client = new HttpClient();
222:
223: List authPrefs = new ArrayList(2);
224: authPrefs.add(AuthPolicy.DIGEST);
225: authPrefs.add(AuthPolicy.BASIC);
226: // Exclude the NTLM authentication scheme because it is not supported by this class
227: client.getParams().setParameter(
228: AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
229:
230: if (useProxy()) {
231: client.getHostConfiguration()
232: .setProxy(proxyHost, proxyPort);
233: if (useProxyAuthentication()) {
234: client.getState().setProxyCredentials(
235: proxyRealm,
236: proxyHost,
237: new UsernamePasswordCredentials(proxyUserName,
238: proxyPasswd));
239: }
240: }
241: Credentials c = getCredentials(url);
242: if (c != null) {
243: Message.debug("found credentials for " + url + ": " + c);
244: client.getState().setCredentials(
245: c.getRealm(),
246: c.getHost(),
247: new UsernamePasswordCredentials(c.getUserName(), c
248: .getPasswd()));
249: }
250: return client;
251: }
252:
253: private boolean useProxy() {
254: return proxyHost != null && proxyHost.trim().length() > 0;
255: }
256:
257: private boolean useAuthentication(URL url) {
258: return getCredentials(url) != null;
259: }
260:
261: private Credentials getCredentials(URL url) {
262: return CredentialsStore.INSTANCE.getCredentials(null, url
263: .getHost());
264: }
265:
266: private boolean useProxyAuthentication() {
267: return (proxyUserName != null && proxyUserName.trim().length() > 0);
268: }
269:
270: private static final class GETInputStream extends InputStream {
271: private InputStream is;
272:
273: private GetMethod get;
274:
275: private GETInputStream(GetMethod get) throws IOException {
276: this .get = get;
277: is = get.getResponseBodyAsStream();
278: }
279:
280: public int available() throws IOException {
281: return is.available();
282: }
283:
284: public void close() throws IOException {
285: is.close();
286: get.releaseConnection();
287: }
288:
289: public boolean equals(Object obj) {
290: return is.equals(obj);
291: }
292:
293: public int hashCode() {
294: return is.hashCode();
295: }
296:
297: public void mark(int readlimit) {
298: is.mark(readlimit);
299: }
300:
301: public boolean markSupported() {
302: return is.markSupported();
303: }
304:
305: public int read() throws IOException {
306: return is.read();
307: }
308:
309: public int read(byte[] b, int off, int len) throws IOException {
310: return is.read(b, off, len);
311: }
312:
313: public int read(byte[] b) throws IOException {
314: return is.read(b);
315: }
316:
317: public void reset() throws IOException {
318: is.reset();
319: }
320:
321: public long skip(long n) throws IOException {
322: return is.skip(n);
323: }
324:
325: public String toString() {
326: return is.toString();
327: }
328: }
329:
330: private static final class HttpClientHelper3x implements
331: HttpClientHelper {
332: private static final int VERSION = 3;
333:
334: private HttpClientHelper3x() {
335: }
336:
337: public long getResponseContentLength(HeadMethod head) {
338: return head.getResponseContentLength();
339: }
340:
341: /**
342: * {@inheritDoc}
343: */
344: public int getHttpClientMajorVersion() {
345: return VERSION;
346: }
347: }
348:
349: private static final class HttpClientHelper2x implements
350: HttpClientHelper {
351: private static final int VERSION = 2;
352:
353: private HttpClientHelper2x() {
354: }
355:
356: public long getResponseContentLength(HeadMethod head) {
357: Header header = head.getResponseHeader("Content-Length");
358: if (header != null) {
359: try {
360: return Integer.parseInt(header.getValue());
361: } catch (NumberFormatException e) {
362: Message.verbose("Invalid content-length value: "
363: + e.getMessage());
364: }
365: }
366: return 0;
367: }
368:
369: /**
370: * {@inheritDoc}
371: */
372: public int getHttpClientMajorVersion() {
373: return VERSION;
374: }
375: }
376:
377: public interface HttpClientHelper {
378: long getResponseContentLength(HeadMethod head);
379:
380: int getHttpClientMajorVersion();
381: }
382: }
|