org.eclipse.ecf.provider.filetransfer.httpclient.HttpClientFileSystemBrowser.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ecf.provider.filetransfer.httpclient.HttpClientFileSystemBrowser.java

Source

/****************************************************************************
 * Copyright (c) 2008, 2011 Composent, Inc., IBM and others.
 * 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
 *    Henrich Kraemer - bug 263869, testHttpsReceiveFile fails using HTTP proxy
 *    Henrich Kraemer - Bug 297742 - [transport] Investigate how to maintain HTTP session     
 *****************************************************************************/

package org.eclipse.ecf.provider.filetransfer.httpclient;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
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.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.auth.CredentialsProvider;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.util.DateUtil;
import org.eclipse.core.runtime.Assert;
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.BrowseFileTransferException;
import org.eclipse.ecf.filetransfer.IRemoteFile;
import org.eclipse.ecf.filetransfer.IRemoteFileSystemListener;
import org.eclipse.ecf.filetransfer.IRemoteFileSystemRequest;
import org.eclipse.ecf.filetransfer.events.socket.ISocketEventSource;
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.DebugOptions;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.HttpClientProxyCredentialProvider;
import org.eclipse.ecf.internal.provider.filetransfer.httpclient.Messages;
import org.eclipse.ecf.provider.filetransfer.browse.AbstractFileSystemBrowser;
import org.eclipse.ecf.provider.filetransfer.browse.URLRemoteFile;
import org.eclipse.ecf.provider.filetransfer.events.socket.SocketEventSource;
import org.eclipse.ecf.provider.filetransfer.httpclient.HttpClientRetrieveFileTransfer.HostConfigHelper;
import org.eclipse.ecf.provider.filetransfer.util.JREProxyHelper;
import org.eclipse.ecf.provider.filetransfer.util.ProxySetupHelper;
import org.eclipse.osgi.util.NLS;

/**
 *
 */
public class HttpClientFileSystemBrowser extends AbstractFileSystemBrowser {

    // 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.browse.connectTimeout
    // for https://bugs.eclipse.org/bugs/show_bug.cgi?id=292995
    protected static final int DEFAULT_CONNECTION_TIMEOUT = HttpClientOptions.BROWSE_DEFAULT_CONNECTION_TIMEOUT;

    private static final String USERNAME_PREFIX = "Username:"; //$NON-NLS-1$

    private JREProxyHelper proxyHelper = null;

    private ConnectingSocketMonitor connectingSockets;

    protected String username = null;

    protected String password = null;

    protected HttpClient httpClient = null;

    protected volatile HeadMethod headMethod;

    protected HostConfigHelper hostConfigHelper;

    /**
     * @param directoryOrFileID
     * @param listener
     */
    public HttpClientFileSystemBrowser(HttpClient httpClient, IFileID directoryOrFileID,
            IRemoteFileSystemListener listener, URL directoryOrFileURL, IConnectContext connectContext,
            Proxy proxy) {
        super(directoryOrFileID, listener, directoryOrFileURL, connectContext, proxy);
        Assert.isNotNull(httpClient);
        this.httpClient = httpClient;
        this.proxyHelper = new JREProxyHelper();
        this.connectingSockets = new ConnectingSocketMonitor(1);

    }

    class HttpClientRemoteFileSystemRequest extends RemoteFileSystemRequest {
        protected SocketEventSource socketEventSource;

        HttpClientRemoteFileSystemRequest() {
            this.socketEventSource = new SocketEventSource() {
                public Object getAdapter(Class adapter) {
                    if (adapter == null) {
                        return null;
                    }
                    if (adapter.isInstance(this)) {
                        return this;
                    }
                    if (adapter.isInstance(HttpClientRemoteFileSystemRequest.this)) {
                        return HttpClientRemoteFileSystemRequest.this;
                    }
                    return null;
                }
            };
        }

        public Object getAdapter(Class adapter) {
            if (adapter == null) {
                return null;
            }
            return socketEventSource.getAdapter(adapter);
        }

        public void cancel() {
            HttpClientFileSystemBrowser.this.cancel();
        }
    }

    protected IRemoteFileSystemRequest createRemoteFileSystemRequest() {
        return new HttpClientRemoteFileSystemRequest();
    }

    protected void cancel() {
        if (isCanceled()) {
            return; // break job cancel recursion
        }
        setCanceled(getException());
        super.cancel();
        if (headMethod != null) {
            if (!headMethod.isAborted()) {
                headMethod.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 {
                    socket.close();
                } catch (IOException e) {
                    Trace.catching(Activator.PLUGIN_ID, DebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "cancel", //$NON-NLS-1$
                            e);
                }
            }
        }
    }

    protected boolean hasForceNTLMProxyOption() {
        return (System.getProperties().getProperty(HttpClientOptions.FORCE_NTLM_PROP) != null);
    }

    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(directoryOrFile);
                if (proxy == null) {
                    proxy = ProxySetupHelper.getProxy(directoryOrFile.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);
    }

    private void initHttpClientConnectionManager() {
        Map options = null; //Currently there is no API to pass in options to browse request
        Activator.getDefault().getConnectionManagerHelper().initConnectionManager(httpClient, options);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ecf.provider.filetransfer.browse.AbstractFileSystemBrowser#runRequest()
     */
    protected void runRequest() throws Exception {
        Trace.entering(Activator.PLUGIN_ID, DebugOptions.METHODS_ENTERING, this.getClass(), "runRequest"); //$NON-NLS-1$
        setupProxies();
        // set timeout
        initHttpClientConnectionManager();

        String urlString = directoryOrFile.toString();
        CredentialsProvider credProvider = new HttpClientProxyCredentialProvider() {

            protected Proxy getECFProxy() {
                return getProxy();
            }

            protected Credentials getNTLMCredentials(Proxy lp) {
                if (hasForceNTLMProxyOption())
                    return HttpClientRetrieveFileTransfer.createNTLMCredentials(lp);
                return null;
            }

        };
        // setup authentication
        setupAuthentication(urlString);
        // setup https host and port
        setupHostAndPort(credProvider, urlString);

        headMethod = new HeadMethod(hostConfigHelper.getTargetRelativePath());
        headMethod.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.
        headMethod.getParams().setParameter(CredentialsProvider.PROVIDER, credProvider);
        // set max-age for cache control to 0 for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=249990
        headMethod.addRequestHeader("Cache-Control", "max-age=0"); //$NON-NLS-1$//$NON-NLS-2$
        headMethod.addRequestHeader("Connection", "Keep-Alive"); //$NON-NLS-1$ //$NON-NLS-2$

        long lastModified = 0;
        long fileLength = -1;
        connectingSockets.clear();
        int code = -1;
        try {
            Trace.trace(Activator.PLUGIN_ID, "browse=" + urlString); //$NON-NLS-1$

            code = httpClient.executeMethod(getHostConfiguration(), headMethod);

            Trace.trace(Activator.PLUGIN_ID, "browse 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(headMethod);
            if (ntlmProxyFound && !hasForceNTLMProxyOption())
                throw new BrowseFileTransferException(
                        "HttpClient Provider is not configured to support NTLM proxy authentication.", //$NON-NLS-1$
                        HttpClientOptions.NTLM_PROXY_RESPONSE_CODE);

            if (code == HttpURLConnection.HTTP_OK) {
                fileLength = headMethod.getResponseContentLength();
                lastModified = getLastModifiedTimeFromHeader();
            } else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
                throw new BrowseFileTransferException(NLS.bind("File not found: {0}", urlString), code); //$NON-NLS-1$
            } else if (code == HttpURLConnection.HTTP_UNAUTHORIZED) {
                throw new BrowseFileTransferException(Messages.HttpClientRetrieveFileTransfer_Unauthorized, code);
            } else if (code == HttpURLConnection.HTTP_FORBIDDEN) {
                throw new BrowseFileTransferException("Forbidden", code); //$NON-NLS-1$
            } else if (code == HttpURLConnection.HTTP_PROXY_AUTH) {
                throw new BrowseFileTransferException(Messages.HttpClientRetrieveFileTransfer_Proxy_Auth_Required,
                        code);
            } else {
                throw new BrowseFileTransferException(
                        NLS.bind(Messages.HttpClientRetrieveFileTransfer_ERROR_GENERAL_RESPONSE_CODE,
                                new Integer(code)),
                        code);
            }
            remoteFiles = new IRemoteFile[1];
            remoteFiles[0] = new URLRemoteFile(lastModified, fileLength, fileID);
        } catch (Exception e) {
            Trace.throwing(Activator.PLUGIN_ID, DebugOptions.EXCEPTIONS_THROWING, this.getClass(), "runRequest", e); //$NON-NLS-1$
            BrowseFileTransferException ex = (BrowseFileTransferException) ((e instanceof BrowseFileTransferException)
                    ? e
                    : new BrowseFileTransferException(NLS
                            .bind(Messages.HttpClientRetrieveFileTransfer_EXCEPTION_COULD_NOT_CONNECT, urlString),
                            e, code));
            throw ex;
        } finally {
            headMethod.releaseConnection();
        }
    }

    private long getLastModifiedTimeFromHeader() throws IOException {
        Header lastModifiedHeader = headMethod.getResponseHeader("Last-Modified"); //$NON-NLS-1$
        if (lastModifiedHeader == null)
            return 0L;
        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;
    }

    Proxy getProxy() {
        return proxy;
    }

    protected void setupHostAndPort(CredentialsProvider credProvider, String urlString) {
        getHostConfiguration(); // creates hostConfigHelper if needed
        hostConfigHelper.setTargetHostByURL(credProvider, urlString);
    }

    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 setupAuthentication(String urlString) throws UnsupportedCallbackException, IOException {
        Credentials credentials = null;
        if (username == null) {
            credentials = getFileRequestCredentials();
        }

        if (credentials != null && username != null) {
            final AuthScope authScope = new AuthScope(HttpClientRetrieveFileTransfer.getHostFromURL(urlString),
                    HttpClientRetrieveFileTransfer.getPortFromURL(urlString), AuthScope.ANY_REALM);
            Trace.trace(Activator.PLUGIN_ID, "browse credentials=" + credentials); //$NON-NLS-1$
            httpClient.getState().setCredentials(authScope, credentials);
        }
    }

    private HostConfiguration getHostConfiguration() {
        if (hostConfigHelper == null) {
            ISocketEventSource source = (ISocketEventSource) job.getRequest().getAdapter(ISocketEventSource.class);
            hostConfigHelper = new HostConfigHelper(source, connectingSockets);
        }
        return hostConfigHelper.getHostConfiguration();
    }

    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, "brows socksproxy=" + proxy.getAddress()); //$NON-NLS-1$
            proxyHelper.setupProxy(proxy);
        }
    }

}