blob: 92dc157b7b4f68257549a2d25ac0e9f6bbbdb671 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.maven.wagon.shared.http;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.ChallengeState;
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.HttpRequestRetryHandler;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.AuthSchemes;
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.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.SSLInitializationException;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.auth.BasicSchemeFactory;
import org.apache.http.impl.auth.DigestSchemeFactory;
import org.apache.http.impl.auth.NTLMSchemeFactory;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.OutputData;
import org.apache.maven.wagon.PathUtils;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.StreamWagon;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.apache.maven.wagon.resource.Resource;
import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatAuthorizationMessage;
import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatResourceDoesNotExistMessage;
import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatTransferDebugMessage;
import static org.apache.maven.wagon.shared.http.HttpMessageUtils.formatTransferFailedMessage;
/**
* @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
* @author <a href="mailto:james@atlassian.com">James William Dumay</a>
*/
public abstract class AbstractHttpClientWagon extends StreamWagon {
final class WagonHttpEntity extends AbstractHttpEntity {
private final Resource resource;
private final Wagon wagon;
private InputStream stream;
private File source;
private long length = -1;
private boolean repeatable;
private WagonHttpEntity(final InputStream stream, final Resource resource, final Wagon wagon, final File source)
throws TransferFailedException {
if (source != null) {
this.source = source;
this.repeatable = true;
} else {
this.stream = stream;
this.repeatable = false;
}
this.resource = resource;
this.length = resource == null ? -1 : resource.getContentLength();
this.wagon = wagon;
}
public Resource getResource() {
return resource;
}
public Wagon getWagon() {
return wagon;
}
public InputStream getContent() throws IOException, IllegalStateException {
if (this.source != null) {
return new FileInputStream(this.source);
}
return stream;
}
public File getSource() {
return source;
}
public long getContentLength() {
return length;
}
public boolean isRepeatable() {
return repeatable;
}
public void writeTo(final OutputStream output) throws IOException {
if (output == null) {
throw new NullPointerException("output cannot be null");
}
TransferEvent transferEvent =
new TransferEvent(wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT);
transferEvent.setTimestamp(System.currentTimeMillis());
try (ReadableByteChannel input = (this.source != null)
? new RandomAccessFile(this.source, "r").getChannel()
: Channels.newChannel(stream)) {
ByteBuffer buffer = ByteBuffer.allocate(getBufferCapacityForTransfer(this.length));
int halfBufferCapacity = buffer.capacity() / 2;
long remaining = this.length < 0L ? Long.MAX_VALUE : this.length;
while (remaining > 0L) {
int read = input.read(buffer);
if (read == -1) {
// EOF, but some data has not been written yet.
if (((Buffer) buffer).position() != 0) {
((Buffer) buffer).flip();
fireTransferProgress(transferEvent, buffer.array(), ((Buffer) buffer).limit());
output.write(buffer.array(), 0, ((Buffer) buffer).limit());
((Buffer) buffer).clear();
}
break;
}
// Prevent minichunking/fragmentation: when less than half the buffer is utilized,
// read some more bytes before writing and firing progress.
if (((Buffer) buffer).position() < halfBufferCapacity) {
continue;
}
((Buffer) buffer).flip();
fireTransferProgress(transferEvent, buffer.array(), ((Buffer) buffer).limit());
output.write(buffer.array(), 0, ((Buffer) buffer).limit());
remaining -= ((Buffer) buffer).limit();
((Buffer) buffer).clear();
}
output.flush();
}
}
public boolean isStreaming() {
return true;
}
}
private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT");
/**
* use http(s) connection pool mechanism.
* <b>enabled by default</b>
*/
private static boolean persistentPool = Boolean.valueOf(System.getProperty("maven.wagon.http.pool", "true"));
/**
* skip failure on certificate validity checks.
* <b>disabled by default</b>
*/
private static final boolean SSL_INSECURE =
Boolean.valueOf(System.getProperty("maven.wagon.http.ssl.insecure", "false"));
/**
* if using sslInsecure, certificate date issues will be ignored
* <b>disabled by default</b>
*/
private static final boolean IGNORE_SSL_VALIDITY_DATES =
Boolean.valueOf(System.getProperty("maven.wagon.http.ssl.ignore.validity.dates", "false"));
/**
* If enabled, ssl hostname verifier does not check hostname. Disable this will use a browser compat hostname
* verifier <b>disabled by default</b>
*/
private static final boolean SSL_ALLOW_ALL =
Boolean.valueOf(System.getProperty("maven.wagon.http.ssl.allowall", "false"));
/**
* Maximum concurrent connections per distinct route.
* <b>20 by default</b>
*/
private static final int MAX_CONN_PER_ROUTE =
Integer.parseInt(System.getProperty("maven.wagon.httpconnectionManager.maxPerRoute", "20"));
/**
* Maximum concurrent connections in total.
* <b>40 by default</b>
*/
private static final int MAX_CONN_TOTAL =
Integer.parseInt(System.getProperty("maven.wagon.httpconnectionManager.maxTotal", "40"));
/**
* Time to live in seconds for an HTTP connection. After that time, the connection will be dropped.
* Intermediates tend to drop connections after some idle period. Set to -1 to maintain connections
* indefinitely. This value defaults to 300 seconds.
*
* @since 3.2
*/
private static final long CONN_TTL = Long.getLong("maven.wagon.httpconnectionManager.ttlSeconds", 300L);
/**
* Internal connection manager
*/
private static HttpClientConnectionManager httpClientConnectionManager = createConnManager();
/**
* See RFC6585
*/
protected static final int SC_TOO_MANY_REQUESTS = 429;
/**
* For exponential backoff.
*/
/**
* Initial seconds to back off when a HTTP 429 received.
* Subsequent 429 responses result in exponental backoff.
* <b>5 by default</b>
*
* @since 2.7
*/
private int initialBackoffSeconds =
Integer.parseInt(System.getProperty("maven.wagon.httpconnectionManager.backoffSeconds", "5"));
/**
* The maximum amount of time we want to back off in the case of
* repeated HTTP 429 response codes.
*
* @since 2.7
*/
private static final int MAX_BACKOFF_WAIT_SECONDS =
Integer.parseInt(System.getProperty("maven.wagon.httpconnectionManager.maxBackoffSeconds", "180"));
protected int backoff(int wait, String url) throws InterruptedException, TransferFailedException {
TimeUnit.SECONDS.sleep(wait);
int nextWait = wait * 2;
if (nextWait >= getMaxBackoffWaitSeconds()) {
throw new TransferFailedException(
formatTransferFailedMessage(url, SC_TOO_MANY_REQUESTS, null, getProxyInfo()));
}
return nextWait;
}
@SuppressWarnings("checkstyle:linelength")
private static PoolingHttpClientConnectionManager createConnManager() {
String sslProtocolsStr = System.getProperty("https.protocols");
String cipherSuitesStr = System.getProperty("https.cipherSuites");
String[] sslProtocols = sslProtocolsStr != null ? sslProtocolsStr.split(" *, *") : null;
String[] cipherSuites = cipherSuitesStr != null ? cipherSuitesStr.split(" *, *") : null;
SSLConnectionSocketFactory sslConnectionSocketFactory;
if (SSL_INSECURE) {
try {
SSLContext sslContext = new SSLContextBuilder()
.useSSL()
.loadTrustMaterial(null, new RelaxedTrustStrategy(IGNORE_SSL_VALIDITY_DATES))
.build();
sslConnectionSocketFactory = new SSLConnectionSocketFactory(
sslContext,
sslProtocols,
cipherSuites,
SSL_ALLOW_ALL
? SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER
: SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
} catch (Exception ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
}
} else {
sslConnectionSocketFactory = new SSLConnectionSocketFactory(
HttpsURLConnection.getDefaultSSLSocketFactory(),
sslProtocols,
cipherSuites,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
}
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", sslConnectionSocketFactory)
.build();
PoolingHttpClientConnectionManager connManager =
new PoolingHttpClientConnectionManager(registry, null, null, null, CONN_TTL, TimeUnit.SECONDS);
if (persistentPool) {
connManager.setDefaultMaxPerRoute(MAX_CONN_PER_ROUTE);
connManager.setMaxTotal(MAX_CONN_TOTAL);
} else {
connManager.setMaxTotal(1);
}
return connManager;
}
/**
* The type of the retry handler, defaults to {@code standard}.
* Values can be {@link default DefaultHttpRequestRetryHandler},
* or {@link standard StandardHttpRequestRetryHandler},
* or a fully qualified name class with a no-arg.
*
* @since 3.2
*/
private static final String RETRY_HANDLER_CLASS =
System.getProperty("maven.wagon.http.retryHandler.class", "standard");
/**
* Whether or not methods that have successfully sent their request will be retried,
* defaults to {@code false}.
* Note: only used for default and standard retry handlers.
*
* @since 3.2
*/
private static final boolean RETRY_HANDLER_REQUEST_SENT_ENABLED =
Boolean.getBoolean("maven.wagon.http.retryHandler.requestSentEnabled");
/**
* Number of retries for the retry handler, defaults to 3.
* Note: only used for default and standard retry handlers.
*
* @since 3.2
*/
private static final int RETRY_HANDLER_COUNT = Integer.getInteger("maven.wagon.http.retryHandler.count", 3);
/**
* Comma-separated list of non-retryable exception classes.
* Note: only used for default retry handler.
*
* @since 3.2
*/
private static final String RETRY_HANDLER_EXCEPTIONS =
System.getProperty("maven.wagon.http.retryHandler.nonRetryableClasses");
private static HttpRequestRetryHandler createRetryHandler() {
switch (RETRY_HANDLER_CLASS) {
case "default":
if (RETRY_HANDLER_EXCEPTIONS == null || RETRY_HANDLER_EXCEPTIONS.isEmpty()) {
return new DefaultHttpRequestRetryHandler(RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED);
}
return new DefaultHttpRequestRetryHandler(
RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED, getNonRetryableExceptions()) {};
case "standard":
return new StandardHttpRequestRetryHandler(RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED);
default:
try {
final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader();
return HttpRequestRetryHandler.class.cast(classLoader
.loadClass(RETRY_HANDLER_CLASS)
.getConstructor()
.newInstance());
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
}
}
/**
* The type of the serviceUnavailableRetryStrategy, defaults to {@code none}.
* Values can be {@link default DefaultServiceUnavailableRetryStrategy},
* or {@link standard StandardServiceUnavailableRetryStrategy}, or
* a fully qualified name class with a no-arg or none to not use a ServiceUnavailableRetryStrategy.
*/
private static final String SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS =
System.getProperty("maven.wagon.http.serviceUnavailableRetryStrategy.class", "none");
/**
* Interval in milliseconds between retries when using a serviceUnavailableRetryStrategy.
* <b>1000 by default</b>
*/
private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL =
Integer.getInteger("maven.wagon.http.serviceUnavailableRetryStrategy.retryInterval", 1000);
/**
* Maximum number of retries when using a serviceUnavailableRetryStrategy.
* <b>5 by default</b>
*/
private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES =
Integer.getInteger("maven.wagon.http.serviceUnavailableRetryStrategy.maxRetries", 5);
private static ServiceUnavailableRetryStrategy createServiceUnavailableRetryStrategy() {
switch (SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS) {
case "none":
return null;
case "default":
return new DefaultServiceUnavailableRetryStrategy(
SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES,
SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL);
case "standard":
return new StandardServiceUnavailableRetryStrategy(
SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES,
SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL);
default:
try {
final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader();
return ServiceUnavailableRetryStrategy.class.cast(classLoader
.loadClass(SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS)
.getConstructor()
.newInstance());
} catch (final Exception e) {
throw new IllegalArgumentException(e);
}
}
}
private static Registry<AuthSchemeProvider> createAuthSchemeRegistry() {
return RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.BASIC, new BasicSchemeFactory(StandardCharsets.UTF_8))
.register(AuthSchemes.DIGEST, new DigestSchemeFactory(StandardCharsets.UTF_8))
.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.build();
}
private static Collection<Class<? extends IOException>> getNonRetryableExceptions() {
final List<Class<? extends IOException>> exceptions = new ArrayList<>();
final ClassLoader loader = AbstractHttpClientWagon.class.getClassLoader();
for (final String ex : RETRY_HANDLER_EXCEPTIONS.split(",")) {
try {
exceptions.add((Class<? extends IOException>) loader.loadClass(ex));
} catch (final ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
return exceptions;
}
private static CloseableHttpClient httpClient = createClient();
private static CloseableHttpClient createClient() {
return HttpClientBuilder.create() //
.useSystemProperties() //
.disableConnectionState() //
.setConnectionManager(httpClientConnectionManager) //
.setRetryHandler(createRetryHandler())
.setServiceUnavailableRetryStrategy(createServiceUnavailableRetryStrategy())
.setDefaultAuthSchemeRegistry(createAuthSchemeRegistry())
.setRedirectStrategy(new WagonRedirectStrategy())
.build();
}
private CredentialsProvider credentialsProvider;
private AuthCache authCache;
private Closeable closeable;
/**
* @plexus.configuration
* @deprecated Use httpConfiguration instead.
*/
private Properties httpHeaders;
/**
* @since 1.0-beta-6
*/
private HttpConfiguration httpConfiguration;
/**
* Basic auth scope overrides
* @since 2.8
*/
private BasicAuthScope basicAuth;
/**
* Proxy basic auth scope overrides
* @since 2.8
*/
private BasicAuthScope proxyAuth;
public void openConnectionInternal() {
repository.setUrl(getURL(repository));
credentialsProvider = new BasicCredentialsProvider();
authCache = new BasicAuthCache();
if (authenticationInfo != null) {
String username = authenticationInfo.getUserName();
String password = authenticationInfo.getPassword();
if ((username != null && !username.isEmpty()) && (password != null && !password.isEmpty())) {
Credentials creds = new UsernamePasswordCredentials(username, password);
AuthScope targetScope = getBasicAuthScope()
.getScope(getRepository().getHost(), getRepository().getPort());
credentialsProvider.setCredentials(targetScope, creds);
}
}
ProxyInfo proxyInfo =
getProxyInfo(getRepository().getProtocol(), getRepository().getHost());
if (proxyInfo != null) {
String proxyUsername = proxyInfo.getUserName();
String proxyPassword = proxyInfo.getPassword();
String proxyHost = proxyInfo.getHost();
String proxyNtlmHost = proxyInfo.getNtlmHost();
String proxyNtlmDomain = proxyInfo.getNtlmDomain();
if (proxyHost != null) {
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);
}
AuthScope proxyScope = getProxyBasicAuthScope().getScope(proxyHost, proxyInfo.getPort());
credentialsProvider.setCredentials(proxyScope, creds);
}
}
}
}
public void closeConnection() {
if (!persistentPool) {
httpClientConnectionManager.closeIdleConnections(0, TimeUnit.MILLISECONDS);
}
if (authCache != null) {
authCache.clear();
authCache = null;
}
if (credentialsProvider != null) {
credentialsProvider.clear();
credentialsProvider = null;
}
}
public static CloseableHttpClient getHttpClient() {
return httpClient;
}
public static void setPersistentPool(boolean persistent) {
persistentPool = persistent;
}
public static void setPoolingHttpClientConnectionManager(
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager) {
httpClientConnectionManager = poolingHttpClientConnectionManager;
httpClient = createClient();
}
public void put(File source, String resourceName)
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
Resource resource = new Resource(resourceName);
firePutInitiated(resource, source);
resource.setContentLength(source.length());
resource.setLastModified(source.lastModified());
put(null, resource, source);
}
public void putFromStream(final InputStream stream, String destination, long contentLength, long lastModified)
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
Resource resource = new Resource(destination);
firePutInitiated(resource, null);
resource.setContentLength(contentLength);
resource.setLastModified(lastModified);
put(stream, resource, null);
}
private void put(final InputStream stream, Resource resource, File source)
throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {
put(resource, source, new WagonHttpEntity(stream, resource, this, source));
}
private void put(Resource resource, File source, HttpEntity httpEntity)
throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {
put(resource, source, httpEntity, buildUrl(resource));
}
/**
* Builds a complete URL string from the repository URL and the relative path of the resource passed.
*
* @param resource the resource to extract the relative path from.
* @return the complete URL
*/
private String buildUrl(Resource resource) {
return buildUrl(resource.getName());
}
/**
* Builds a complete URL string from the repository URL and the relative path of the resource passed.
*
* @param resourceName the resourcerelative path
* @return the complete URL
*/
private String buildUrl(String resourceName) {
return EncodingUtil.encodeURLToString(getRepository().getUrl(), resourceName);
}
private void put(Resource resource, File source, HttpEntity httpEntity, String url)
throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {
put(getInitialBackoffSeconds(), resource, source, httpEntity, url);
}
private void put(int wait, Resource resource, File source, HttpEntity httpEntity, String url)
throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {
// Parent directories need to be created before posting
try {
mkdirs(PathUtils.dirname(resource.getName()));
} catch (HttpException he) {
fireTransferError(resource, he, TransferEvent.REQUEST_PUT);
} catch (IOException e) {
fireTransferError(resource, e, TransferEvent.REQUEST_PUT);
}
// preemptive for put
// TODO: is it a good idea, though? 'Expect-continue' handshake would serve much better
// FIXME Perform only when preemptive has been configured
Repository repo = getRepository();
HttpHost targetHost = new HttpHost(repo.getHost(), repo.getPort(), repo.getProtocol());
AuthScope targetScope = getBasicAuthScope().getScope(targetHost);
if (credentialsProvider.getCredentials(targetScope) != null) {
BasicScheme targetAuth = new BasicScheme(StandardCharsets.UTF_8);
authCache.put(targetHost, targetAuth);
}
HttpPut putMethod = new HttpPut(url);
firePutStarted(resource, source);
try {
putMethod.setEntity(httpEntity);
CloseableHttpResponse response = execute(putMethod);
try {
fireTransferDebug(formatTransferDebugMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
int statusCode = response.getStatusLine().getStatusCode();
// Check that we didn't run out of retries.
switch (statusCode) {
// Success Codes
case HttpStatus.SC_OK: // 200
case HttpStatus.SC_CREATED: // 201
case HttpStatus.SC_ACCEPTED: // 202
case HttpStatus.SC_NO_CONTENT: // 204
break;
// TODO Move 401/407 to AuthenticationException after WAGON-587
case HttpStatus.SC_FORBIDDEN:
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
EntityUtils.consumeQuietly(response.getEntity());
fireSessionConnectionRefused();
throw new AuthorizationException(formatAuthorizationMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
case HttpStatus.SC_NOT_FOUND:
case HttpStatus.SC_GONE:
EntityUtils.consumeQuietly(response.getEntity());
throw new ResourceDoesNotExistException(formatResourceDoesNotExistMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
case SC_TOO_MANY_REQUESTS:
EntityUtils.consumeQuietly(response.getEntity());
put(backoff(wait, url), resource, source, httpEntity, url);
break;
// add more entries here
default:
EntityUtils.consumeQuietly(response.getEntity());
TransferFailedException e = new TransferFailedException(formatTransferFailedMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
fireTransferError(resource, e, TransferEvent.REQUEST_PUT);
throw e;
}
firePutCompleted(resource, source);
EntityUtils.consume(response.getEntity());
} finally {
response.close();
}
} catch (IOException | HttpException | InterruptedException e) {
fireTransferError(resource, e, TransferEvent.REQUEST_PUT);
throw new TransferFailedException(formatTransferFailedMessage(url, getProxyInfo()), e);
}
}
protected void mkdirs(String dirname) throws HttpException, IOException {
// nothing to do
}
public boolean resourceExists(String resourceName) throws TransferFailedException, AuthorizationException {
return resourceExists(getInitialBackoffSeconds(), resourceName);
}
private boolean resourceExists(int wait, String resourceName)
throws TransferFailedException, AuthorizationException {
String url = buildUrl(resourceName);
HttpHead headMethod = new HttpHead(url);
try {
CloseableHttpResponse response = execute(headMethod);
try {
int statusCode = response.getStatusLine().getStatusCode();
boolean result;
switch (statusCode) {
case HttpStatus.SC_OK:
result = true;
break;
case HttpStatus.SC_NOT_MODIFIED:
result = true;
break;
// TODO Move 401/407 to AuthenticationException after WAGON-587
case HttpStatus.SC_FORBIDDEN:
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
throw new AuthorizationException(formatAuthorizationMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
case HttpStatus.SC_NOT_FOUND:
case HttpStatus.SC_GONE:
result = false;
break;
case SC_TOO_MANY_REQUESTS:
return resourceExists(backoff(wait, resourceName), resourceName);
// add more entries here
default:
throw new TransferFailedException(formatTransferFailedMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
}
return result;
} finally {
response.close();
}
} catch (IOException | HttpException | InterruptedException e) {
throw new TransferFailedException(formatTransferFailedMessage(url, getProxyInfo()), e);
}
}
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);
}
requestConfigBuilder.setConnectTimeout(getTimeout());
requestConfigBuilder.setSocketTimeout(getReadTimeout());
// We don't apply this to MKCOL because RFC 7231 says that this will not work without a body
// and our MKCOL requests don't have a body. They will logically behave like GET.
if (httpMethod instanceof HttpPut) {
requestConfigBuilder.setExpectContinueEnabled(true);
}
HttpMethodConfiguration config =
httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration(httpMethod);
if (config != null) {
ConfigurationUtils.copyConfig(config, requestConfigBuilder);
}
HttpClientContext localContext = HttpClientContext.create();
localContext.setCredentialsProvider(credentialsProvider);
localContext.setAuthCache(authCache);
localContext.setRequestConfig(requestConfigBuilder.build());
if (config != null && config.isUsePreemptive()) {
HttpHost targetHost = new HttpHost(repo.getHost(), repo.getPort(), repo.getProtocol());
AuthScope targetScope = getBasicAuthScope().getScope(targetHost);
if (credentialsProvider.getCredentials(targetScope) != null) {
BasicScheme targetAuth = new BasicScheme(StandardCharsets.UTF_8);
authCache.put(targetHost, targetAuth);
}
}
if (proxyInfo != null) {
if (proxyInfo.getHost() != null) {
HttpHost proxyHost = new HttpHost(proxyInfo.getHost(), proxyInfo.getPort());
AuthScope proxyScope = getProxyBasicAuthScope().getScope(proxyHost);
if (credentialsProvider.getCredentials(proxyScope) != null) {
/* This is extremely ugly because we need to set challengeState to PROXY, but
* the constructor is deprecated. Alternatively, we could subclass BasicScheme
* to ProxyBasicScheme and set the state internally in the constructor.
*/
BasicScheme proxyAuth = new BasicScheme(ChallengeState.PROXY);
authCache.put(proxyHost, proxyAuth);
}
}
}
return httpClient.execute(httpMethod, localContext);
}
public void setHeaders(HttpUriRequest method) {
HttpMethodConfiguration config =
httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration(method);
if (config == null || config.isUseDefaultHeaders()) {
// TODO: merge with the other headers and have some better defaults, unify with lightweight headers
method.addHeader("Cache-control", "no-cache");
method.addHeader("Pragma", "no-cache");
}
if (httpHeaders != null) {
for (Map.Entry<Object, Object> entry : httpHeaders.entrySet()) {
method.setHeader((String) entry.getKey(), (String) entry.getValue());
}
}
Header[] headers = config == null ? null : config.asRequestHeaders();
if (headers != null) {
for (Header header : headers) {
method.setHeader(header);
}
}
Header userAgentHeader = method.getFirstHeader(HTTP.USER_AGENT);
if (userAgentHeader == null) {
String userAgent = getUserAgent(method);
if (userAgent != null) {
method.setHeader(HTTP.USER_AGENT, userAgent);
}
}
}
protected String getUserAgent(HttpUriRequest method) {
if (httpHeaders != null) {
String value = (String) httpHeaders.get(HTTP.USER_AGENT);
if (value != null) {
return value;
}
}
HttpMethodConfiguration config =
httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration(method);
if (config != null) {
return (String) config.getHeaders().get(HTTP.USER_AGENT);
}
return null;
}
/**
* getUrl
* Implementors can override this to remove unwanted parts of the url such as role-hints
*
* @param repository
* @return
*/
protected String getURL(Repository repository) {
return repository.getUrl();
}
public HttpConfiguration getHttpConfiguration() {
return httpConfiguration;
}
public void setHttpConfiguration(HttpConfiguration httpConfiguration) {
this.httpConfiguration = httpConfiguration;
}
/**
* Get the override values for standard HttpClient AuthScope
*
* @return the basicAuth
*/
public BasicAuthScope getBasicAuthScope() {
if (basicAuth == null) {
basicAuth = new BasicAuthScope();
}
return basicAuth;
}
/**
* Set the override values for standard HttpClient AuthScope
*
* @param basicAuth the AuthScope to set
*/
public void setBasicAuthScope(BasicAuthScope basicAuth) {
this.basicAuth = basicAuth;
}
/**
* Get the override values for proxy HttpClient AuthScope
*
* @return the proxyAuth
*/
public BasicAuthScope getProxyBasicAuthScope() {
if (proxyAuth == null) {
proxyAuth = new BasicAuthScope();
}
return proxyAuth;
}
/**
* Set the override values for proxy HttpClient AuthScope
*
* @param proxyAuth the AuthScope to set
*/
public void setProxyBasicAuthScope(BasicAuthScope proxyAuth) {
this.proxyAuth = proxyAuth;
}
public void fillInputData(InputData inputData)
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
fillInputData(getInitialBackoffSeconds(), inputData);
}
private void fillInputData(int wait, InputData inputData)
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
Resource resource = inputData.getResource();
String url = buildUrl(resource);
HttpGet getMethod = new HttpGet(url);
long timestamp = resource.getLastModified();
if (timestamp > 0) {
SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US);
fmt.setTimeZone(GMT_TIME_ZONE);
Header hdr = new BasicHeader("If-Modified-Since", fmt.format(new Date(timestamp)));
fireTransferDebug("sending ==> " + hdr + "(" + timestamp + ")");
getMethod.addHeader(hdr);
}
try {
CloseableHttpResponse response = execute(getMethod);
closeable = response;
fireTransferDebug(formatTransferDebugMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
int statusCode = response.getStatusLine().getStatusCode();
switch (statusCode) {
case HttpStatus.SC_OK:
break;
case HttpStatus.SC_NOT_MODIFIED:
// return, leaving last modified set to original value so getIfNewer should return unmodified
return;
// TODO Move 401/407 to AuthenticationException after WAGON-587
case HttpStatus.SC_FORBIDDEN:
case HttpStatus.SC_UNAUTHORIZED:
case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
EntityUtils.consumeQuietly(response.getEntity());
fireSessionConnectionRefused();
throw new AuthorizationException(formatAuthorizationMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
case HttpStatus.SC_NOT_FOUND:
case HttpStatus.SC_GONE:
EntityUtils.consumeQuietly(response.getEntity());
throw new ResourceDoesNotExistException(formatResourceDoesNotExistMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
case SC_TOO_MANY_REQUESTS:
EntityUtils.consumeQuietly(response.getEntity());
fillInputData(backoff(wait, url), inputData);
break;
// add more entries here
default:
EntityUtils.consumeQuietly(response.getEntity());
cleanupGetTransfer(resource);
TransferFailedException e = new TransferFailedException(formatTransferFailedMessage(
url,
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase(),
getProxyInfo()));
fireTransferError(resource, e, TransferEvent.REQUEST_GET);
throw e;
}
Header contentLengthHeader = response.getFirstHeader("Content-Length");
if (contentLengthHeader != null) {
try {
long contentLength = Long.parseLong(contentLengthHeader.getValue());
resource.setContentLength(contentLength);
} catch (NumberFormatException e) {
fireTransferDebug(
"error parsing content length header '" + contentLengthHeader.getValue() + "' " + e);
}
}
Header lastModifiedHeader = response.getFirstHeader("Last-Modified");
if (lastModifiedHeader != null) {
Date lastModified = DateUtils.parseDate(lastModifiedHeader.getValue());
if (lastModified != null) {
resource.setLastModified(lastModified.getTime());
fireTransferDebug(
"last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified.getTime() + ")");
}
}
HttpEntity entity = response.getEntity();
if (entity != null) {
inputData.setInputStream(entity.getContent());
}
} catch (IOException | HttpException | InterruptedException e) {
fireTransferError(resource, e, TransferEvent.REQUEST_GET);
throw new TransferFailedException(formatTransferFailedMessage(url, getProxyInfo()), e);
}
}
protected void cleanupGetTransfer(Resource resource) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ignore) {
// ignore
}
}
}
@Override
public void putFromStream(InputStream stream, String destination)
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
putFromStream(stream, destination, -1, -1);
}
@Override
protected void putFromStream(InputStream stream, Resource resource)
throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException {
putFromStream(stream, resource.getName(), -1, -1);
}
public Properties getHttpHeaders() {
return httpHeaders;
}
public void setHttpHeaders(Properties httpHeaders) {
this.httpHeaders = httpHeaders;
}
@Override
public void fillOutputData(OutputData outputData) throws TransferFailedException {
// no needed in this implementation but throw an Exception if used
throw new IllegalStateException("this wagon http client must not use fillOutputData");
}
protected CredentialsProvider getCredentialsProvider() {
return credentialsProvider;
}
protected AuthCache getAuthCache() {
return authCache;
}
public int getInitialBackoffSeconds() {
return initialBackoffSeconds;
}
public void setInitialBackoffSeconds(int initialBackoffSeconds) {
this.initialBackoffSeconds = initialBackoffSeconds;
}
public static int getMaxBackoffWaitSeconds() {
return MAX_BACKOFF_WAIT_SECONDS;
}
}