blob: 7c951d1ffeb1d4e352bb0a742bec161cee97e744 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2011 Composent, Inc., IBM All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Composent, Inc. - initial API and implementation
* Maarten Meijer - bug 237936, added gzip encoded transfer default
* Henrich Kraemer - bug 263869, testHttpsReceiveFile fails using HTTP proxy
* Henrich Kraemer - bug 263613, [transport] Update site contacting / downloading is not cancelable
* Henrich Kraemer - Bug 297742 - [transport] Investigate how to maintain HTTP session
******************************************************************************/
package org.eclipse.ecf.provider.filetransfer.httpclient;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.net.SocketFactory;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.HttpVersion;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.auth.CredentialsProvider;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
import org.apache.commons.httpclient.util.DateUtil;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.security.Callback;
import org.eclipse.ecf.core.security.CallbackHandler;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.ecf.core.security.NameCallback;
import org.eclipse.ecf.core.security.ObjectCallback;
import org.eclipse.ecf.core.security.UnsupportedCallbackException;
import org.eclipse.ecf.core.util.Proxy;
import org.eclipse.ecf.core.util.ProxyAddress;
import org.eclipse.ecf.core.util.Trace;
import org.eclipse.ecf.filetransfer.FileTransferJob;
import org.eclipse.ecf.filetransfer.IFileRangeSpecification;
import org.eclipse.ecf.filetransfer.IFileTransferPausable;
import org.eclipse.ecf.filetransfer.IFileTransferRunnable;
import org.eclipse.ecf.filetransfer.IRetrieveFileTransferOptions;
import org.eclipse.ecf.filetransfer.IncomingFileTransferException;
import org.eclipse.ecf.filetransfer.InvalidFileRangeSpecificationException;
import org.eclipse.ecf.filetransfer.events.IFileTransferConnectStartEvent;
import org.eclipse.ecf.filetransfer.events.socket.ISocketEventSource;
import org.eclipse.ecf.filetransfer.events.socket.ISocketListener;
import org.eclipse.ecf.filetransfer.identity.IFileID;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.Activator;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.ConnectingSocketMonitor;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.ConnectionManagerHelper;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.DebugOptions;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.ECFHttpClientProtocolSocketFactory;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.ECFHttpClientSecureProtocolSocketFactory;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.HttpClientProxyCredentialProvider;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.ISSLSocketFactoryModifier;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.Messages;
import org.eclipse.ecf.provider.filetransfer.events.socket.SocketEventSource;
import org.eclipse.ecf.provider.filetransfer.identity.FileTransferID;
import org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer;
import org.eclipse.ecf.provider.filetransfer.retrieve.HttpHelper;
import org.eclipse.ecf.provider.filetransfer.util.JREProxyHelper;
import org.eclipse.ecf.provider.filetransfer.util.ProxySetupHelper;
import org.eclipse.osgi.util.NLS;
public class HttpClientRetrieveFileTransfer extends AbstractRetrieveFileTransfer {
/**
* gzip encoding wrapper for httpclient class. Copied from Mylyn project, bug 205708
*
*/
public class GzipGetMethod extends GetMethod {
private static final String CONTENT_ENCODING = "Content-Encoding"; //$NON-NLS-1$
private static final String ACCEPT_ENCODING = "Accept-encoding"; //$NON-NLS-1$
private static final String CONTENT_ENCODING_GZIP = "gzip"; //$NON-NLS-1$
private static final String CONTENT_ENCODING_ACCEPTED = CONTENT_ENCODING_GZIP;
private boolean gzipReceived = false;
public GzipGetMethod(String urlString) {
super(urlString);
}
private boolean isZippedResponse() {
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=269018
boolean contentEncodingGzip = (null != this.getResponseHeader(CONTENT_ENCODING) && this.getResponseHeader(CONTENT_ENCODING).getValue().equals(CONTENT_ENCODING_GZIP));
Trace.trace(Activator.PLUGIN_ID, "Content-Encoding: gzip header " + (contentEncodingGzip ? "PRESENT" : "ABSENT")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
boolean hasGzSuffix = targetHasGzSuffix(remoteFileName);
return contentEncodingGzip && !hasGzSuffix;
}
public int execute(HttpState state, HttpConnection conn) throws HttpException, IOException {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "GzipGetMethod.execute"); //$NON-NLS-1$
// Insert accept-encoding header
int result = super.execute(state, conn);
// Code to deal with implications described on bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=261881
switch (result) {
case HttpStatus.SC_MOVED_TEMPORARILY :
case HttpStatus.SC_MOVED_PERMANENTLY :
case HttpStatus.SC_SEE_OTHER :
case HttpStatus.SC_TEMPORARY_REDIRECT :
Trace.trace(Activator.PLUGIN_ID, "GzipGetMethod.execute. Received redirect=" + result + ". Removing gzip accept encoding"); //$NON-NLS-1$ //$NON-NLS-2$
gzipReceived = false;
removeRequestHeader(GzipGetMethod.ACCEPT_ENCODING);
default :
}
// test what is sent back
Trace.exiting(Activator.PLUGIN_ID, DebugOptions.METHODS_EXITING, this.getClass(), "GzipGetMethod.execute", new Integer(result)); //$NON-NLS-1$
return result;
}
public InputStream getResponseBodyAsUnzippedStream() throws IOException {
gzipReceived = isZippedResponse();
InputStream input = super.getResponseBodyAsStream();
try {
if (gzipReceived) {
Trace.trace(Activator.PLUGIN_ID, "Using gzip input stream to decode"); //$NON-NLS-1$
// extract on the fly
return new java.util.zip.GZIPInputStream(input);
}
Trace.trace(Activator.PLUGIN_ID, "Not using gzip input stream"); //$NON-NLS-1$
} catch (IOException e) {
Activator.getDefault().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, IStatus.WARNING, "Exception creating gzip input stream", e)); //$NON-NLS-1$
throw e;
}
return input;
}
private Object releaseLock = new Object();
// This override is a workaround for
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=279457
// This makes GetMethod.releaseConnection non-reentrant,
// as with reentrancy under some circumstances a NPE can be
// thrown with multithreaded access
public void releaseConnection() {
synchronized (releaseLock) {
super.releaseConnection();
}
}
}
static final class HostConfigHelper {
private ISocketEventSource source;
private ISocketListener socketListener;
private String targetURL;
private String targetRelativePath;
private HostConfiguration hostConfiguration;
public HostConfigHelper(ISocketEventSource source, ISocketListener socketListener) {
Assert.isNotNull(source);
this.source = source;
this.socketListener = socketListener;
hostConfiguration = new HostConfiguration();
}
public HostConfiguration getHostConfiguration() {
return hostConfiguration;
}
// drops the scheme server and port (e.g http://server:8080/a/b.html -> /a/b.html
private static String getTargetRelativePathFromURL(String url) {
// RFC 3986
/*
*
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
hier-part = "//" authority path-abempty
/ path-absolute
/ path-rootless
/ path-empty
*
* path = path-abempty ; begins with "/" or is empty
/ path-absolute ; begins with "/" but not "//"
/ path-noscheme ; begins with a non-colon segment
/ path-rootless ; begins with a segment
/ path-empty ; zero characters
*
*/
// This routine is supposed to remove authority information from the url
// to make this a 'relative path' for
// HttpClients method constructor (for example GetMethod(String uri)) as
// ECF executes methods passing in a HostConfiguration which represents the
// authority.
final int colonSlashSlash = url.indexOf("://"); //$NON-NLS-1$
if (colonSlashSlash < 0)
return url;
// '://' indicates there must be an authority.
// the authority must not contain a '/' character.
final int nextSlash = url.indexOf('/', colonSlashSlash + 3);
if (nextSlash == -1) {
// try root? or should it be empty?
return ""; //$NON-NLS-1$
}
String relativeURL = url.substring(nextSlash); // include the slash
// This is a workaround for multiple consecutive slashes after the authority.
// HttpClient will parse this as another authority instead of using
// it as a path. In anticipation we add "//example.com" so that this will
// be removed instead of the first path segment.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=270749#c28
// for a full explanation
if (relativeURL.startsWith("//")) { //$NON-NLS-1$
final String host = "example.com"; //$NON-NLS-1$
relativeURL = "//" + host + relativeURL; //$NON-NLS-1$
}
return relativeURL;
}
public void setTargetHostByURL(CredentialsProvider credProvider, String url) {
this.targetURL = url;
this.targetRelativePath = getTargetRelativePathFromURL(targetURL);
String host = getHostFromURL(targetURL);
int port = getPortFromURL(targetURL);
if (HttpClientRetrieveFileTransfer.urlUsesHttps(targetURL)) {
ISSLSocketFactoryModifier sslSocketFactoryModifier = Activator.getDefault().getSSLSocketFactoryModifier();
if (sslSocketFactoryModifier == null) {
sslSocketFactoryModifier = new HttpClientDefaultSSLSocketFactoryModifier();
}
SecureProtocolSocketFactory psf = new ECFHttpClientSecureProtocolSocketFactory(sslSocketFactoryModifier, source, socketListener);
Protocol sslProtocol = new Protocol(HttpClientRetrieveFileTransfer.HTTPS, (ProtocolSocketFactory) psf, HTTPS_PORT);
Trace.trace(Activator.PLUGIN_ID, "retrieve host=" + host + ";port=" + port); //$NON-NLS-1$ //$NON-NLS-2$
hostConfiguration.setHost(host, port, sslProtocol);
hostConfiguration.getParams().setParameter(CredentialsProvider.PROVIDER, credProvider);
} else {
ProtocolSocketFactory psf = new ECFHttpClientProtocolSocketFactory(SocketFactory.getDefault(), source, socketListener);
Protocol protocol = new Protocol(HttpClientRetrieveFileTransfer.HTTP, psf, HTTP_PORT);
Trace.trace(Activator.PLUGIN_ID, "retrieve host=" + host + ";port=" + port); //$NON-NLS-1$ //$NON-NLS-2$
hostConfiguration.setHost(host, port, protocol);
hostConfiguration.getParams().setParameter(CredentialsProvider.PROVIDER, credProvider);
}
}
public String getTargetRelativePath() {
return targetRelativePath;
}
}
private static final String USERNAME_PREFIX = Messages.HttpClientRetrieveFileTransfer_Username_Prefix;
// changing to 2 minutes (120000) as per bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=266246
// 10/26/2009: Added being able to set with system property with name org.eclipse.ecf.provider.filetransfer.httpclient.retrieve.connectTimeout
// for https://bugs.eclipse.org/bugs/show_bug.cgi?id=292995
protected static final int DEFAULT_CONNECTION_TIMEOUT = ConnectionManagerHelper.DEFAULT_CONNECTION_TIMEOUT;
// changing to 2 minutes (120000) as per bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=266246
// 10/26/2009: Added being able to set with system property with name org.eclipse.ecf.provider.filetransfer.httpclient.retrieve.readTimeout
// for https://bugs.eclipse.org/bugs/show_bug.cgi?id=292995
protected static final int DEFAULT_READ_TIMEOUT = ConnectionManagerHelper.DEFAULT_READ_TIMEOUT;
protected static final int HTTP_PORT = 80;
protected static final int HTTPS_PORT = 443;
protected static final int MAX_RETRY = 2;
protected static final String HTTPS = Messages.FileTransferNamespace_Https_Protocol;
protected static final String HTTP = Messages.FileTransferNamespace_Http_Protocol;
protected static final String[] supportedProtocols = {HTTP, HTTPS};
private static final String LAST_MODIFIED_HEADER = "Last-Modified"; //$NON-NLS-1$
private GzipGetMethod getMethod = null;
private HttpClient httpClient = null;
private String username;
private String password;
private int responseCode = -1;
private volatile boolean doneFired = false;
private String remoteFileName;
protected int httpVersion = 1;
protected IFileID fileid = null;
protected JREProxyHelper proxyHelper = null;
private HostConfigHelper hostConfigHelper;
private SocketEventSource socketEventSource;
private ConnectingSocketMonitor connectingSockets;
private FileTransferJob connectJob;
public HttpClientRetrieveFileTransfer(HttpClient client) {
this.httpClient = client;
proxyHelper = new JREProxyHelper();
connectingSockets = new ConnectingSocketMonitor(1);
socketEventSource = new SocketEventSource() {
public Object getAdapter(Class adapter) {
if (adapter == null) {
return null;
}
if (adapter.isInstance(this)) {
return this;
}
return HttpClientRetrieveFileTransfer.this.getAdapter(adapter);
}
};
}
/* (non-Javadoc)
* @see org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer#getRemoteFileName()
*/
public String getRemoteFileName() {
return remoteFileName;
}
public synchronized void cancel() {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "cancel"); //$NON-NLS-1$
if (isCanceled()) {
return; // break job cancel recursion
}
setDoneCanceled(exception);
boolean fireDoneEvent = true;
if (connectJob != null) {
Trace.trace(Activator.PLUGIN_ID, "calling connectJob.cancel()"); //$NON-NLS-1$
connectJob.cancel();
}
synchronized (jobLock) {
if (job != null) {
// Its the transfer jobs responsibility to throw the event.
fireDoneEvent = false;
Trace.trace(Activator.PLUGIN_ID, "calling transfer job.cancel()"); //$NON-NLS-1$
job.cancel();
}
}
if (getMethod != null) {
if (!getMethod.isAborted()) {
Trace.trace(Activator.PLUGIN_ID, "calling getMethod.abort()"); //$NON-NLS-1$
getMethod.abort();
}
}
if (connectingSockets != null) {
// this should unblock socket connect calls, if any
for (Iterator iterator = connectingSockets.getConnectingSockets().iterator(); iterator.hasNext();) {
Socket socket = (Socket) iterator.next();
try {
Trace.trace(Activator.PLUGIN_ID, "Call socket.close() for socket=" + socket.toString()); //$NON-NLS-1$
socket.close();
} catch (IOException e) {
Trace.catching(Activator.PLUGIN_ID, DebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "cancel", e); //$NON-NLS-1$
}
}
}
hardClose();
if (fireDoneEvent) {
fireTransferReceiveDoneEvent();
}
Trace.exiting(Activator.PLUGIN_ID, DebugOptions.METHODS_EXITING, this.getClass(), "cancel");//$NON-NLS-1$
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer#hardClose()
*/
protected void hardClose() {
super.hardClose();
if (getMethod != null) {
getMethod.releaseConnection();
getMethod = null;
}
responseCode = -1;
if (proxyHelper != null) {
proxyHelper.dispose();
proxyHelper = null;
}
}
protected Credentials getFileRequestCredentials() throws UnsupportedCallbackException, IOException {
if (connectContext == null)
return null;
final CallbackHandler callbackHandler = connectContext.getCallbackHandler();
if (callbackHandler == null)
return null;
final NameCallback usernameCallback = new NameCallback(USERNAME_PREFIX);
final ObjectCallback passwordCallback = new ObjectCallback();
callbackHandler.handle(new Callback[] {usernameCallback, passwordCallback});
username = usernameCallback.getName();
password = (String) passwordCallback.getObject();
return new UsernamePasswordCredentials(username, password);
}
protected void setupProxies() {
// If it's been set directly (via ECF API) then this overrides platform settings
if (proxy == null) {
try {
// give SOCKS priority see https://bugs.eclipse.org/bugs/show_bug.cgi?id=295030#c61
proxy = ProxySetupHelper.getSocksProxy(getRemoteFileURL());
if (proxy == null) {
proxy = ProxySetupHelper.getProxy(getRemoteFileURL().toExternalForm());
}
} catch (NoClassDefFoundError e) {
// If the proxy API is not available a NoClassDefFoundError will be thrown here.
// If that happens then we just want to continue on.
Activator.logNoProxyWarning(e);
}
}
if (proxy != null)
setupProxy(proxy);
}
protected void setupAuthentication(String urlString) throws UnsupportedCallbackException, IOException {
Credentials credentials = null;
if (username == null) {
credentials = getFileRequestCredentials();
}
if (credentials != null && username != null) {
final AuthScope authScope = new AuthScope(getHostFromURL(urlString), getPortFromURL(urlString), AuthScope.ANY_REALM);
Trace.trace(Activator.PLUGIN_ID, "retrieve credentials=" + credentials); //$NON-NLS-1$
httpClient.getState().setCredentials(authScope, credentials);
}
}
protected void setupHostAndPort(CredentialsProvider credProvider, String urlString) {
getHostConfiguration(); // creates hostConfigHelper if needed
hostConfigHelper.setTargetHostByURL(credProvider, urlString);
}
protected void setRequestHeaderValues() throws InvalidFileRangeSpecificationException {
final IFileRangeSpecification rangeSpec = getFileRangeSpecification();
if (rangeSpec != null) {
final long startPosition = rangeSpec.getStartPosition();
final long endPosition = rangeSpec.getEndPosition();
if (startPosition < 0)
throw new InvalidFileRangeSpecificationException(Messages.HttpClientRetrieveFileTransfer_RESUME_START_POSITION_LESS_THAN_ZERO, rangeSpec);
if (endPosition != -1L && endPosition <= startPosition)
throw new InvalidFileRangeSpecificationException(Messages.HttpClientRetrieveFileTransfer_RESUME_ERROR_END_POSITION_LESS_THAN_START, rangeSpec);
String rangeHeader = "bytes=" + startPosition + "-" + ((endPosition == -1L) ? "" : ("" + endPosition)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
Trace.trace(Activator.PLUGIN_ID, "retrieve range header=" + rangeHeader); //$NON-NLS-1$
setRangeHeader(rangeHeader);
}
// set max-age for cache control to 0 for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=249990
getMethod.addRequestHeader("Cache-Control", "max-age=0"); //$NON-NLS-1$//$NON-NLS-2$
setRequestHeaderValuesFromOptions();
}
private void setRequestHeaderValuesFromOptions() {
Map localOptions = getOptions();
if (localOptions != null) {
Object o = localOptions.get(IRetrieveFileTransferOptions.REQUEST_HEADERS);
if (o != null && o instanceof Map) {
Map requestHeaders = (Map) o;
for (Iterator i = requestHeaders.keySet().iterator(); i.hasNext();) {
Object n = i.next();
Object v = requestHeaders.get(n);
if (n != null && n instanceof String && v != null && v instanceof String)
getMethod.addRequestHeader((String) n, (String) v);
}
}
}
}
private void setRangeHeader(String value) {
getMethod.addRequestHeader("Range", value); //$NON-NLS-1$
}
private boolean isHTTP11() {
return (httpVersion >= 1);
}
public int getResponseCode() {
if (responseCode != -1)
return responseCode;
HttpVersion version = getMethod.getEffectiveVersion();
if (version == null) {
responseCode = -1;
httpVersion = 1;
return responseCode;
}
httpVersion = version.getMinor();
responseCode = getMethod.getStatusCode();
return responseCode;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.core.identity.IIdentifiable#getID()
*/
public ID getID() {
return fileid;
}
private long getLastModifiedTimeFromHeader() throws IOException {
Header lastModifiedHeader = getMethod.getResponseHeader(LAST_MODIFIED_HEADER);
if (lastModifiedHeader == null)
throw new IOException(Messages.HttpClientRetrieveFileTransfer_INVALID_LAST_MODIFIED_TIME);
String lastModifiedString = lastModifiedHeader.getValue();
long lastModified = 0;
if (lastModifiedString != null) {
try {
lastModified = DateUtil.parseDate(lastModifiedString).getTime();
} catch (Exception e) {
throw new IOException(Messages.HttpClientRetrieveFileTransfer_EXCEPITION_INVALID_LAST_MODIFIED_FROM_SERVER);
}
}
return lastModified;
}
protected void getResponseHeaderValues() throws IOException {
if (getResponseCode() == -1)
throw new IOException(Messages.HttpClientRetrieveFileTransfer_INVALID_SERVER_RESPONSE_TO_PARTIAL_RANGE_REQUEST);
Header lastModifiedHeader = getMethod.getResponseHeader(LAST_MODIFIED_HEADER);
if (lastModifiedHeader != null) {
setLastModifiedTime(getLastModifiedTimeFromHeader());
}
setFileLength(getMethod.getResponseContentLength());
fileid = new FileTransferID(getRetrieveNamespace(), getRemoteFileURL());
// Get content disposition header and get remote file name from it if possible.
Header contentDispositionHeader = getMethod.getResponseHeader(HttpHelper.CONTENT_DISPOSITION_HEADER);
if (contentDispositionHeader != null) {
remoteFileName = HttpHelper.getRemoteFileNameFromContentDispositionHeader(contentDispositionHeader.getValue());
}
// If still null, get the path from httpclient.getMethod()
if (remoteFileName == null) {
// No name could be extracted using Content-Disposition. Let's try the
// path from the getMethod.
String pathStr = getMethod.getPath();
if (pathStr != null && pathStr.length() > 0) {
IPath path = Path.fromPortableString(pathStr);
if (path.segmentCount() > 0)
remoteFileName = path.lastSegment();
}
// If still null, use the input file name
if (remoteFileName == null)
// Last resort. Use the path of the initial URL request
remoteFileName = super.getRemoteFileName();
}
}
final class ECFCredentialsProvider extends HttpClientProxyCredentialProvider {
protected Proxy getECFProxy() {
return getProxy();
}
protected Credentials getNTLMCredentials(Proxy lp) {
if (hasForceNTLMProxyOption())
return HttpClientRetrieveFileTransfer.createNTLMCredentials(lp);
return null;
}
}
Proxy getProxy() {
return proxy;
}
protected void setInputStream(InputStream ins) {
remoteFileContents = ins;
}
protected InputStream wrapTransferReadInputStream(InputStream inputStream, IProgressMonitor monitor) {
return inputStream;
}
protected boolean hasForceNTLMProxyOption() {
Map localOptions = getOptions();
if (localOptions != null && localOptions.get(HttpClientOptions.FORCE_NTLM_PROP) != null)
return true;
return (System.getProperties().getProperty(HttpClientOptions.FORCE_NTLM_PROP) != null);
}
protected int getSocketReadTimeout() {
return ConnectionManagerHelper.getSocketReadTimeout(getOptions());
}
/**
* @since 4.0
*/
protected int getConnectTimeout() {
return ConnectionManagerHelper.getConnectTimeout(getOptions());
}
private void initHttpClientConnectionManager() {
Activator.getDefault().getConnectionManagerHelper().initConnectionManager(httpClient, getOptions());
}
/* (non-Javadoc)
* @see org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer#openStreams()
*/
protected void openStreams() throws IncomingFileTransferException {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "openStreams"); //$NON-NLS-1$
final String urlString = getRemoteFileURL().toString();
this.doneFired = false;
int code = -1;
try {
initHttpClientConnectionManager();
setupAuthentication(urlString);
CredentialsProvider credProvider = new ECFCredentialsProvider();
setupHostAndPort(credProvider, urlString);
getMethod = new GzipGetMethod(hostConfigHelper.getTargetRelativePath());
getMethod.addRequestHeader("Connection", "Keep-Alive"); //$NON-NLS-1$ //$NON-NLS-2$
getMethod.setFollowRedirects(true);
// Define a CredentialsProvider - found that possibility while debugging in org.apache.commons.httpclient.HttpMethodDirector.processProxyAuthChallenge(HttpMethod)
// Seems to be another way to select the credentials.
getMethod.getParams().setParameter(CredentialsProvider.PROVIDER, credProvider);
setRequestHeaderValues();
Trace.trace(Activator.PLUGIN_ID, "retrieve=" + urlString); //$NON-NLS-1$
// Set request header for possible gzip encoding, but only if
// 1) The file range specification is null (we want the whole file)
// 2) The target remote file does *not* end in .gz (see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=280205)
if (getFileRangeSpecification() == null && !targetHasGzSuffix(super.getRemoteFileName())) {
Trace.trace(Activator.PLUGIN_ID, "Accept-Encoding: gzip added to request header"); //$NON-NLS-1$
getMethod.setRequestHeader(GzipGetMethod.ACCEPT_ENCODING, GzipGetMethod.CONTENT_ENCODING_ACCEPTED);
} else {
Trace.trace(Activator.PLUGIN_ID, "Accept-Encoding NOT added to header"); //$NON-NLS-1$
}
fireConnectStartEvent();
if (checkAndHandleDone()) {
return;
}
connectingSockets.clear();
// Actually execute get and get response code (since redirect is set to true, then
// redirect response code handled internally
if (connectJob == null) {
performConnect(new NullProgressMonitor());
} else {
connectJob.schedule();
connectJob.join();
connectJob = null;
}
if (checkAndHandleDone()) {
return;
}
code = responseCode;
responseHeaders = getResponseHeaders();
Trace.trace(Activator.PLUGIN_ID, "retrieve resp=" + code); //$NON-NLS-1$
// Check for NTLM proxy in response headers
// This check is to deal with bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=252002
boolean ntlmProxyFound = NTLMProxyDetector.detectNTLMProxy(getMethod);
if (ntlmProxyFound && !hasForceNTLMProxyOption())
throw new IncomingFileTransferException("HttpClient Provider is not configured to support NTLM proxy authentication.", HttpClientOptions.NTLM_PROXY_RESPONSE_CODE); //$NON-NLS-1$
if (code == HttpURLConnection.HTTP_PARTIAL || code == HttpURLConnection.HTTP_OK) {
getResponseHeaderValues();
setInputStream(getMethod.getResponseBodyAsUnzippedStream());
fireReceiveStartEvent();
} else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
getMethod.releaseConnection();
throw new IncomingFileTransferException(NLS.bind("File not found: {0}", urlString), code); //$NON-NLS-1$
} else if (code == HttpURLConnection.HTTP_UNAUTHORIZED) {
getMethod.releaseConnection();
throw new IncomingFileTransferException(Messages.HttpClientRetrieveFileTransfer_Unauthorized, code);
} else if (code == HttpURLConnection.HTTP_FORBIDDEN) {
getMethod.releaseConnection();
throw new IncomingFileTransferException("Forbidden", code); //$NON-NLS-1$
} else if (code == HttpURLConnection.HTTP_PROXY_AUTH) {
getMethod.releaseConnection();
throw new IncomingFileTransferException(Messages.HttpClientRetrieveFileTransfer_Proxy_Auth_Required, code);
} else {
getMethod.releaseConnection();
throw new IncomingFileTransferException(NLS.bind(Messages.HttpClientRetrieveFileTransfer_ERROR_GENERAL_RESPONSE_CODE, new Integer(code)), code);
}
} catch (final Exception e) {
Trace.throwing(Activator.PLUGIN_ID, DebugOptions.EXCEPTIONS_THROWING, this.getClass(), "openStreams", e); //$NON-NLS-1$
if (code == -1) {
if (!isDone()) {
setDoneException(e);
}
fireTransferReceiveDoneEvent();
} else {
IncomingFileTransferException ex = (IncomingFileTransferException) ((e instanceof IncomingFileTransferException) ? e : new IncomingFileTransferException(NLS.bind(Messages.HttpClientRetrieveFileTransfer_EXCEPTION_COULD_NOT_CONNECT, urlString), e, code));
throw ex;
}
}
Trace.exiting(Activator.PLUGIN_ID, DebugOptions.METHODS_EXITING, this.getClass(), "openStreams"); //$NON-NLS-1$
}
private Map getResponseHeaders() {
if (getMethod == null)
return null;
Header[] headers = getMethod.getResponseHeaders();
Map result = null;
if (headers != null && headers.length > 0) {
result = new HashMap();
for (int i = 0; i < headers.length; i++) {
String name = headers[i].getName();
String val = headers[i].getValue();
if (name != null && val != null)
result.put(name, val);
}
}
return Collections.unmodifiableMap(result);
}
private boolean checkAndHandleDone() {
if (isDone()) {
// for cancel the done event should have been fired always.
if (!doneFired) {
fireTransferReceiveDoneEvent();
}
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.filetransfer.IRetrieveFileTransferContainerAdapter#setConnectContextForAuthentication(org.eclipse.ecf.core.security.IConnectContext)
*/
public void setConnectContextForAuthentication(IConnectContext connectContext) {
super.setConnectContextForAuthentication(connectContext);
this.username = null;
this.password = null;
}
protected static String getHostFromURL(String url) {
String result = url;
final int colonSlashSlash = url.indexOf("://"); //$NON-NLS-1$
if (colonSlashSlash < 0)
return ""; //$NON-NLS-1$
if (colonSlashSlash >= 0) {
result = url.substring(colonSlashSlash + 3);
}
final int colonPort = result.indexOf(':');
final int requestPath = result.indexOf('/');
int substringEnd;
if (colonPort > 0 && requestPath > 0)
substringEnd = Math.min(colonPort, requestPath);
else if (colonPort > 0)
substringEnd = colonPort;
else if (requestPath > 0)
substringEnd = requestPath;
else
substringEnd = result.length();
return result.substring(0, substringEnd);
}
protected static int getPortFromURL(String url) {
final int colonSlashSlash = url.indexOf("://"); //$NON-NLS-1$
if (colonSlashSlash < 0)
return urlUsesHttps(url) ? HTTPS_PORT : HTTP_PORT;
// This is wrong as if the url has no colonPort before '?' then it should return the default
final int colonPort = url.indexOf(':', colonSlashSlash + 1);
if (colonPort < 0)
return urlUsesHttps(url) ? HTTPS_PORT : HTTP_PORT;
// Make sure that the colonPort is not from some part of the rest of the URL
int nextSlash = url.indexOf('/', colonSlashSlash + 3);
if (nextSlash != -1 && colonPort > nextSlash)
return urlUsesHttps(url) ? HTTPS_PORT : HTTP_PORT;
final int requestPath = url.indexOf('/', colonPort + 1);
int end;
if (requestPath < 0)
end = url.length();
else
end = requestPath;
return Integer.parseInt(url.substring(colonPort + 1, end));
}
protected static boolean urlUsesHttps(String url) {
url = url.trim();
return url.startsWith(HTTPS);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.internal.provider.filetransfer.AbstractRetrieveFileTransfer#supportsProtocol(java.lang.String)
*/
public static boolean supportsProtocol(String protocolString) {
for (int i = 0; i < supportedProtocols.length; i++)
if (supportedProtocols[i].equalsIgnoreCase(protocolString))
return true;
return false;
}
protected boolean isConnected() {
return (getMethod != null);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer#doPause()
*/
protected boolean doPause() {
if (isPaused() || !isConnected() || isDone())
return false;
this.paused = true;
return this.paused;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer#doResume()
*/
protected boolean doResume() {
if (!isPaused() || isConnected())
return false;
return openStreamsForResume();
}
protected void setResumeRequestHeaderValues() throws IOException {
if (this.bytesReceived <= 0 || this.fileLength <= this.bytesReceived)
throw new IOException(Messages.HttpClientRetrieveFileTransfer_RESUME_START_ERROR);
setRangeHeader("bytes=" + this.bytesReceived + "-"); //$NON-NLS-1$ //$NON-NLS-2$
// set max-age for cache control to 0 for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=249990
getMethod.addRequestHeader("Cache-Control", "max-age=0"); //$NON-NLS-1$//$NON-NLS-2$
setRequestHeaderValuesFromOptions();
}
private boolean openStreamsForResume() {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "openStreamsForResume"); //$NON-NLS-1$
final String urlString = getRemoteFileURL().toString();
this.doneFired = false;
int code = -1;
try {
initHttpClientConnectionManager();
CredentialsProvider credProvider = new ECFCredentialsProvider();
setupAuthentication(urlString);
setupHostAndPort(credProvider, urlString);
getMethod = new GzipGetMethod(hostConfigHelper.getTargetRelativePath());
getMethod.addRequestHeader("Connection", "Keep-Alive"); //$NON-NLS-1$ //$NON-NLS-2$
getMethod.setFollowRedirects(true);
// Define a CredentialsProvider - found that possibility while debugging in org.apache.commons.httpclient.HttpMethodDirector.processProxyAuthChallenge(HttpMethod)
// Seems to be another way to select the credentials.
getMethod.getParams().setParameter(CredentialsProvider.PROVIDER, credProvider);
setResumeRequestHeaderValues();
Trace.trace(Activator.PLUGIN_ID, "resume=" + urlString); //$NON-NLS-1$
// Gzip encoding is not an option for resume
fireConnectStartEvent();
if (checkAndHandleDone()) {
return false;
}
connectingSockets.clear();
// Actually execute get and get response code (since redirect is set to true, then
// redirect response code handled internally
if (connectJob == null) {
performConnect(new NullProgressMonitor());
} else {
connectJob.schedule();
connectJob.join();
connectJob = null;
}
if (checkAndHandleDone()) {
return false;
}
code = responseCode;
responseHeaders = getResponseHeaders();
Trace.trace(Activator.PLUGIN_ID, "retrieve resp=" + code); //$NON-NLS-1$
if (code == HttpURLConnection.HTTP_PARTIAL || code == HttpURLConnection.HTTP_OK) {
getResumeResponseHeaderValues();
setInputStream(getMethod.getResponseBodyAsUnzippedStream());
this.paused = false;
fireReceiveResumedEvent();
} else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
getMethod.releaseConnection();
throw new IncomingFileTransferException(NLS.bind("File not found: {0}", urlString), code, responseHeaders); //$NON-NLS-1$
} else if (code == HttpURLConnection.HTTP_UNAUTHORIZED) {
getMethod.releaseConnection();
throw new IncomingFileTransferException(Messages.HttpClientRetrieveFileTransfer_Unauthorized, code, responseHeaders);
} else if (code == HttpURLConnection.HTTP_FORBIDDEN) {
getMethod.releaseConnection();
throw new IncomingFileTransferException("Forbidden", code, responseHeaders); //$NON-NLS-1$
} else if (code == HttpURLConnection.HTTP_PROXY_AUTH) {
getMethod.releaseConnection();
throw new IncomingFileTransferException(Messages.HttpClientRetrieveFileTransfer_Proxy_Auth_Required, code, responseHeaders);
} else {
getMethod.releaseConnection();
throw new IncomingFileTransferException(NLS.bind(Messages.HttpClientRetrieveFileTransfer_ERROR_GENERAL_RESPONSE_CODE, new Integer(code)), code, responseHeaders);
}
Trace.exiting(Activator.PLUGIN_ID, DebugOptions.METHODS_EXITING, this.getClass(), "openStreamsForResume", Boolean.TRUE); //$NON-NLS-1$
return true;
} catch (final Exception e) {
Trace.catching(Activator.PLUGIN_ID, DebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "openStreamsForResume", e); //$NON-NLS-1$
if (code == -1) {
if (!isDone()) {
setDoneException(e);
}
} else {
setDoneException((e instanceof IncomingFileTransferException) ? e : new IncomingFileTransferException(NLS.bind(Messages.HttpClientRetrieveFileTransfer_EXCEPTION_COULD_NOT_CONNECT, urlString), e, code, responseHeaders));
}
fireTransferReceiveDoneEvent();
Trace.exiting(Activator.PLUGIN_ID, DebugOptions.METHODS_EXITING, this.getClass(), "openStreamsForResume", Boolean.FALSE); //$NON-NLS-1$
return false;
}
}
protected void getResumeResponseHeaderValues() throws IOException {
if (getResponseCode() != HttpURLConnection.HTTP_PARTIAL)
throw new IOException();
if (lastModifiedTime != getLastModifiedTimeFromHeader())
throw new IOException(Messages.HttpClientRetrieveFileTransfer_EXCEPTION_FILE_MODIFIED_SINCE_LAST_ACCESS);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer#getAdapter(java.lang.Class)
*/
public Object getAdapter(Class adapter) {
if (adapter == null)
return null;
if (adapter.equals(IFileTransferPausable.class) && isHTTP11())
return this;
if (adapter.equals(ISocketEventSource.class))
return this.socketEventSource;
return super.getAdapter(adapter);
}
private HostConfiguration getHostConfiguration() {
if (hostConfigHelper == null) {
hostConfigHelper = new HostConfigHelper(socketEventSource, connectingSockets);
}
return hostConfigHelper.getHostConfiguration();
}
/* (non-Javadoc)
* @see org.eclipse.ecf.provider.filetransfer.retrieve.AbstractRetrieveFileTransfer#setupProxy(org.eclipse.ecf.core.util.Proxy)
*/
protected void setupProxy(Proxy proxy) {
if (proxy.getType().equals(Proxy.Type.HTTP)) {
final ProxyAddress address = proxy.getAddress();
getHostConfiguration().setProxy(address.getHostName(), address.getPort());
} else if (proxy.getType().equals(Proxy.Type.SOCKS)) {
Trace.trace(Activator.PLUGIN_ID, "retrieve socksproxy=" + proxy.getAddress()); //$NON-NLS-1$
proxyHelper.setupProxy(proxy);
}
}
public static NTCredentials createNTLMCredentials(Proxy p) {
if (p == null) {
return null;
}
String un = getNTLMUserName(p);
String domain = getNTLMDomainName(p);
if (un == null || domain == null)
return null;
return new NTCredentials(un, p.getPassword(), p.getAddress().getHostName(), domain);
}
protected static String getNTLMDomainName(Proxy p) {
String domainUsername = p.getUsername();
if (domainUsername == null)
return null;
int slashloc = domainUsername.indexOf('\\');
if (slashloc == -1)
return null;
return domainUsername.substring(0, slashloc);
}
protected static String getNTLMUserName(Proxy p) {
String domainUsername = p.getUsername();
if (domainUsername == null)
return null;
int slashloc = domainUsername.indexOf('\\');
if (slashloc == -1)
return null;
return domainUsername.substring(slashloc + 1);
}
protected void fireConnectStartEvent() {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "fireConnectStartEvent"); //$NON-NLS-1$
// TODO: should the following be in super.fireReceiveStartEvent();
listener.handleTransferEvent(new IFileTransferConnectStartEvent() {
public IFileID getFileID() {
return remoteFileID;
}
public void cancel() {
HttpClientRetrieveFileTransfer.this.cancel();
}
public FileTransferJob prepareConnectJob(FileTransferJob j) {
return HttpClientRetrieveFileTransfer.this.prepareConnectJob(j);
}
public void connectUsingJob(FileTransferJob j) {
HttpClientRetrieveFileTransfer.this.connectUsingJob(j);
}
public String toString() {
final StringBuffer sb = new StringBuffer("IFileTransferConnectStartEvent["); //$NON-NLS-1$
sb.append(getFileID());
sb.append("]"); //$NON-NLS-1$
return sb.toString();
}
public Object getAdapter(Class adapter) {
return HttpClientRetrieveFileTransfer.this.getAdapter(adapter);
}
});
}
protected String createConnectJobName() {
return getRemoteFileURL().toString() + createRangeName() + Messages.HttpClientRetrieveFileTransfer_CONNECTING_JOB_NAME;
}
protected FileTransferJob prepareConnectJob(FileTransferJob cjob) {
if (cjob == null) {
// Create our own
cjob = new FileTransferJob(createJobName());
}
cjob.setFileTransfer(this);
cjob.setFileTransferRunnable(fileConnectRunnable);
return cjob;
}
protected void connectUsingJob(FileTransferJob cjob) {
Assert.isNotNull(cjob);
this.connectJob = cjob;
}
private IFileTransferRunnable fileConnectRunnable = new IFileTransferRunnable() {
public IStatus performFileTransfer(IProgressMonitor monitor) {
return performConnect(monitor);
}
};
private IStatus performConnect(IProgressMonitor monitor) {
// there might be more ticks in the future perhaps for
// connect socket, certificate validation, send request, authenticate,
int ticks = 1;
monitor.beginTask(getRemoteFileURL().toString() + Messages.HttpClientRetrieveFileTransfer_CONNECTING_TASK_NAME, ticks);
try {
if (monitor.isCanceled())
throw newUserCancelledException();
responseCode = httpClient.executeMethod(getHostConfiguration(), getMethod);
Trace.trace(Activator.PLUGIN_ID, "retrieve resp=" + responseCode); //$NON-NLS-1$
} catch (final Exception e) {
Trace.catching(Activator.PLUGIN_ID, DebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "performConnect", e); //$NON-NLS-1$
if (!isDone()) {
setDoneException(e);
}
} finally {
monitor.done();
}
return Status.OK_STATUS;
}
protected void fireReceiveResumedEvent() {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "fireReceiveResumedEvent len=" + fileLength + ";rcvd=" + bytesReceived); //$NON-NLS-1$ //$NON-NLS-2$
super.fireReceiveResumedEvent();
}
protected void fireTransferReceiveDataEvent() {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "fireTransferReceiveDataEvent len=" + fileLength + ";rcvd=" + bytesReceived); //$NON-NLS-1$ //$NON-NLS-2$
super.fireTransferReceiveDataEvent();
}
protected void fireTransferReceiveDoneEvent() {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "fireTransferReceiveDoneEvent len=" + fileLength + ";rcvd=" + bytesReceived); //$NON-NLS-1$ //$NON-NLS-2$
this.doneFired = true;
super.fireTransferReceiveDoneEvent();
}
protected void fireTransferReceivePausedEvent() {
Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "fireTransferReceivePausedEvent len=" + fileLength + ";rcvd=" + bytesReceived); //$NON-NLS-1$ //$NON-NLS-2$
super.fireTransferReceivePausedEvent();
}
}