/* * Copyright (C) 2014 Guillaume Nodet * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ops4j.pax.url.mvn.internal.wagon; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.auth.AUTH; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.NTCredentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.AuthCache; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicHeader; import org.apache.http.protocol.HTTP; import org.apache.maven.wagon.ConnectionException; import org.apache.maven.wagon.authentication.AuthenticationException; import org.apache.maven.wagon.authentication.AuthenticationInfo; import org.apache.maven.wagon.providers.http.AbstractHttpClientWagon; import org.apache.maven.wagon.providers.http.HttpMethodConfiguration; import org.apache.maven.wagon.providers.http.HttpWagon; import org.apache.maven.wagon.proxy.ProxyInfo; import org.apache.maven.wagon.proxy.ProxyInfoProvider; import org.apache.maven.wagon.repository.Repository; import org.ops4j.net.URLUtils; /** * An http wagon provider providing more configuration options * through the use of an HttpClient instance. * * @author Guillaume Nodet */ public class ConfigurableHttpWagon extends HttpWagon { private final CloseableHttpClient client; public ConfigurableHttpWagon(CloseableHttpClient client, int readTimeout, int connectionTimeout) { this.client = client; setReadTimeout(readTimeout); setTimeout(connectionTimeout); } @Override protected CloseableHttpResponse execute(HttpUriRequest httpMethod) throws HttpException, IOException { setHeaders( httpMethod ); String userAgent = getUserAgent( httpMethod ); if ( userAgent != null ) { httpMethod.setHeader( HTTP.USER_AGENT, userAgent ); } RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); // WAGON-273: default the cookie-policy to browser compatible requestConfigBuilder.setCookieSpec( CookieSpecs.BROWSER_COMPATIBILITY ); Repository repo = getRepository(); ProxyInfo proxyInfo = getProxyInfo( repo.getProtocol(), repo.getHost() ); if ( proxyInfo != null ) { HttpHost proxy = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); requestConfigBuilder.setProxy( proxy ); } HttpMethodConfiguration config = getHttpConfiguration() == null ? null : getHttpConfiguration().getMethodConfiguration(httpMethod); if ( config != null ) { copyConfig(config, requestConfigBuilder); } else { requestConfigBuilder.setSocketTimeout( getReadTimeout() ); requestConfigBuilder.setConnectTimeout( getTimeout() ); } getLocalContext().setRequestConfig(requestConfigBuilder.build()); if ( config != null && config.isUsePreemptive() ) { HttpHost targetHost = new HttpHost( repo.getHost(), repo.getPort(), repo.getProtocol() ); AuthScope targetScope = getBasicAuthScope().getScope( targetHost ); if ( getCredentialsProvider().getCredentials(targetScope) != null ) { BasicScheme targetAuth = new BasicScheme(); targetAuth.processChallenge( new BasicHeader( AUTH.WWW_AUTH, "BASIC preemptive" ) ); getAuthCache().put(targetHost, targetAuth); } } if ( proxyInfo != null ) { if ( proxyInfo.getHost() != null ) { HttpHost proxyHost = new HttpHost( proxyInfo.getHost(), proxyInfo.getPort() ); AuthScope proxyScope = getProxyBasicAuthScope().getScope( proxyHost ); String proxyUsername = proxyInfo.getUserName(); String proxyPassword = proxyInfo.getPassword(); String proxyNtlmHost = proxyInfo.getNtlmHost(); String proxyNtlmDomain = proxyInfo.getNtlmDomain(); if ( proxyUsername != null && proxyPassword != null ) { Credentials creds; if ( proxyNtlmHost != null || proxyNtlmDomain != null ) { creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain ); } else { creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword ); } getCredentialsProvider().setCredentials(proxyScope, creds); BasicScheme proxyAuth = new BasicScheme(); proxyAuth.processChallenge( new BasicHeader( AUTH.PROXY_AUTH, "BASIC preemptive" ) ); getAuthCache().put(proxyHost, proxyAuth); } } } return client.execute( httpMethod, getLocalContext() ); } @Override public void connect(Repository repository, AuthenticationInfo authenticationInfo, ProxyInfoProvider proxyInfoProvider) throws ConnectionException, AuthenticationException { if (repository == null) { throw new IllegalStateException("The repository specified cannot be null."); } if (authenticationInfo == null) { authenticationInfo = new AuthenticationInfo(); } if (authenticationInfo.getUserName() == null) { // Get user/pass that were encoded in the URL. if (repository.getUsername() != null) { // Need to decode username/password because it may contain encoded characters (http://www.w3schools.com/tags/ref_urlencode.asp) // A common encoding is to provide a username as an email address like user%40domain.org authenticationInfo.setUserName(URLUtils.decode(repository.getUsername())); if (repository.getPassword() != null && authenticationInfo.getPassword() == null) { authenticationInfo.setPassword(URLUtils.decode(repository.getPassword())); } } } super.connect(repository, authenticationInfo, proxyInfoProvider); } private AuthCache getAuthCache() { return getField(AuthCache.class, "authCache"); } private CredentialsProvider getCredentialsProvider() { return getField(CredentialsProvider.class, "credentialsProvider"); } private HttpClientContext getLocalContext() { return getField(HttpClientContext.class, "localContext"); } private <T> T getField(Class<T> clazz, String name) { try { Field field = AbstractHttpClientWagon.class.getDeclaredField(name); field.setAccessible(true); return clazz.cast(field.get(this)); } catch (NoSuchFieldException e) { throw new IllegalStateException("Unable to retrieve field " + name, e); } catch (IllegalAccessException e) { throw new IllegalStateException("Unable to retrieve field " + name, e); } } private void copyConfig(HttpMethodConfiguration config, RequestConfig.Builder builder) { try { Class<?> clazz = getClass().getClassLoader().loadClass("org.apache.maven.wagon.providers.http.ConfigurationUtils"); Method method = clazz.getMethod("copyConfig", HttpMethodConfiguration.class, RequestConfig.Builder.class); method.invoke(null, config, builder); } catch (ClassNotFoundException e) { throw new IllegalStateException("Unable to call copyConfig", e); } catch (InvocationTargetException e) { throw new IllegalStateException("Unable to call copyConfig", e); } catch (NoSuchMethodException e) { throw new IllegalStateException("Unable to call copyConfig", e); } catch (IllegalAccessException e) { throw new IllegalStateException("Unable to call copyConfig", e); } } }