ch.cyberduck.core.dav.DAVSession.java Source code

Java tutorial

Introduction

Here is the source code for ch.cyberduck.core.dav.DAVSession.java

Source

package ch.cyberduck.core.dav;

/*
 * Copyright (c) 2002-2011 David Kocher. All rights reserved.
 *
 * http://cyberduck.ch/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Bug fixes, suggestions and comments should be sent to:
 * dkocher@cyberduck.ch
 */

import ch.cyberduck.core.AttributedList;
import ch.cyberduck.core.ConnectionCallback;
import ch.cyberduck.core.DisabledListProgressListener;
import ch.cyberduck.core.Host;
import ch.cyberduck.core.HostKeyCallback;
import ch.cyberduck.core.HostPasswordStore;
import ch.cyberduck.core.HostUrlProvider;
import ch.cyberduck.core.ListProgressListener;
import ch.cyberduck.core.ListService;
import ch.cyberduck.core.LocaleFactory;
import ch.cyberduck.core.LoginCallback;
import ch.cyberduck.core.Path;
import ch.cyberduck.core.ProtocolFactory;
import ch.cyberduck.core.Scheme;
import ch.cyberduck.core.exception.BackgroundException;
import ch.cyberduck.core.exception.ConnectionCanceledException;
import ch.cyberduck.core.exception.ListCanceledException;
import ch.cyberduck.core.exception.LoginCanceledException;
import ch.cyberduck.core.features.*;
import ch.cyberduck.core.http.HttpExceptionMappingService;
import ch.cyberduck.core.http.HttpSession;
import ch.cyberduck.core.http.PreferencesRedirectCallback;
import ch.cyberduck.core.http.RedirectCallback;
import ch.cyberduck.core.preferences.Preferences;
import ch.cyberduck.core.preferences.PreferencesFactory;
import ch.cyberduck.core.proxy.ProxyFinder;
import ch.cyberduck.core.shared.DefaultHomeFinderService;
import ch.cyberduck.core.shared.DefaultTouchFeature;
import ch.cyberduck.core.ssl.DefaultX509KeyManager;
import ch.cyberduck.core.ssl.DisabledX509TrustManager;
import ch.cyberduck.core.ssl.ThreadLocalHostnameDelegatingTrustManager;
import ch.cyberduck.core.ssl.X509KeyManager;
import ch.cyberduck.core.ssl.X509TrustManager;
import ch.cyberduck.core.threading.CancelCallback;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.log4j.Logger;

import javax.net.SocketFactory;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.MessageFormat;

import com.github.sardine.impl.SardineException;
import com.github.sardine.impl.handler.ValidatingResponseHandler;
import com.github.sardine.impl.handler.VoidResponseHandler;

public class DAVSession extends HttpSession<DAVClient> {
    private static final Logger log = Logger.getLogger(DAVSession.class);

    private RedirectCallback redirect = new PreferencesRedirectCallback();

    private final Preferences preferences = PreferencesFactory.get();

    public DAVSession(final Host host) {
        super(host,
                new ThreadLocalHostnameDelegatingTrustManager(new DisabledX509TrustManager(), host.getHostname()),
                new DefaultX509KeyManager());
    }

    public DAVSession(final Host host, final X509TrustManager trust, final X509KeyManager key) {
        super(host, new ThreadLocalHostnameDelegatingTrustManager(trust, host.getHostname()), key);
    }

    public DAVSession(final Host host, final RedirectCallback redirect) {
        super(host,
                new ThreadLocalHostnameDelegatingTrustManager(new DisabledX509TrustManager(), host.getHostname()),
                new DefaultX509KeyManager());
        this.redirect = redirect;
    }

    public DAVSession(final Host host, final X509TrustManager trust, final X509KeyManager key,
            final RedirectCallback redirect) {
        super(host, new ThreadLocalHostnameDelegatingTrustManager(trust, host.getHostname()), key);
        this.redirect = redirect;
    }

    public DAVSession(final Host host, final X509TrustManager trust, final X509KeyManager key,
            final SocketFactory socketFactory) {
        super(host, new ThreadLocalHostnameDelegatingTrustManager(trust, host.getHostname()), key, socketFactory);
    }

    public DAVSession(final Host host, final X509TrustManager trust, final X509KeyManager key,
            final ProxyFinder proxy) {
        super(host, new ThreadLocalHostnameDelegatingTrustManager(trust, host.getHostname()), key, proxy);
    }

    public DAVSession(final Host host, final X509TrustManager trust, final X509KeyManager key,
            final SocketFactory socketFactory, final RedirectCallback redirect) {
        super(host, new ThreadLocalHostnameDelegatingTrustManager(trust, host.getHostname()), key, socketFactory);
        this.redirect = redirect;
    }

    @Override
    public DAVClient connect(final HostKeyCallback key) throws BackgroundException {
        // Always inject new pool to builder on connect because the pool is shutdown on disconnect
        final HttpClientBuilder pool = builder.build(this);
        pool.setRedirectStrategy(new DAVRedirectStrategy(redirect));
        return new DAVClient(new HostUrlProvider(false).get(host), pool);
    }

    @Override
    protected void logout() throws BackgroundException {
        try {
            client.shutdown();
        } catch (IOException e) {
            throw new HttpExceptionMappingService().map(e);
        }
    }

    @Override
    public void login(final HostPasswordStore keychain, final LoginCallback prompt, final CancelCallback cancel)
            throws BackgroundException {
        client.setCredentials(host.getCredentials().getUsername(), host.getCredentials().getPassword(),
                // Windows credentials. Provide empty string for NTLM domain by default.
                preferences.getProperty("webdav.ntlm.workstation"), preferences.getProperty("webdav.ntlm.domain"));
        if (preferences.getBoolean("webdav.basic.preemptive")) {
            // Enable preemptive authentication. See HttpState#setAuthenticationPreemptive
            client.enablePreemptiveAuthentication(host.getHostname(), host.getPort(), host.getPort(),
                    Charset.forName(preferences.getProperty("http.credentials.charset")));
        } else {
            client.disablePreemptiveAuthentication();
        }
        if (host.getCredentials().isPassed()) {
            log.warn(String.format(
                    "Skip verifying credentials with previous successful authentication event for %s", this));
            return;
        }
        try {
            final Path home = new DefaultHomeFinderService(this).find();
            try {
                client.execute(new HttpHead(new DAVPathEncoder().encode(home)), new VoidResponseHandler());
            } catch (SardineException e) {
                switch (e.getStatusCode()) {
                case HttpStatus.SC_FORBIDDEN:
                case HttpStatus.SC_NOT_FOUND:
                case HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE:
                case HttpStatus.SC_METHOD_NOT_ALLOWED:
                    log.warn(String.format("Failed HEAD request to %s with %s. Retry with PROPFIND.", host,
                            e.getResponsePhrase()));
                    cancel.verify();
                    // Possibly only HEAD requests are not allowed
                    this.getFeature(ListService.class).list(home, new DisabledListProgressListener() {
                        @Override
                        public void chunk(final Path parent, final AttributedList<Path> list)
                                throws ListCanceledException {
                            try {
                                cancel.verify();
                            } catch (ConnectionCanceledException e) {
                                throw new ListCanceledException(list, e);
                            }
                        }
                    });
                    break;
                case HttpStatus.SC_BAD_REQUEST:
                    if (preferences.getBoolean("webdav.basic.preemptive")) {
                        log.warn(String.format("Disable preemptive authentication for %s due to failure %s", host,
                                e.getResponsePhrase()));
                        cancel.verify();
                        client.disablePreemptiveAuthentication();
                        client.execute(new HttpHead(new DAVPathEncoder().encode(home)), new VoidResponseHandler());
                    } else {
                        throw new DAVExceptionMappingService().map(e);
                    }
                    break;
                default:
                    throw new DAVExceptionMappingService().map(e);
                }
            }
        } catch (SardineException e) {
            throw new DAVExceptionMappingService().map(e);
        } catch (IOException e) {
            throw new HttpExceptionMappingService().map(e);
        }
    }

    @Override
    public boolean alert(final ConnectionCallback callback) throws BackgroundException {
        if (super.alert(callback)) {
            // Propose protocol change if HEAD request redirects to HTTPS
            final Path home = new DefaultHomeFinderService(this).find();
            try {
                final RequestConfig context = client.context().getRequestConfig();
                final HttpHead request = new HttpHead(new DAVPathEncoder().encode(home));
                request.setConfig(RequestConfig.copy(context).setRedirectsEnabled(false).build());
                final Header location = client.execute(request, new ValidatingResponseHandler<Header>() {
                    @Override
                    public Header handleResponse(final HttpResponse response) throws IOException {
                        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY) {
                            return response.getFirstHeader(HttpHeaders.LOCATION);
                        }
                        return null;
                    }
                });
                // Reset default redirect configuration in context
                client.context().setRequestConfig(RequestConfig.copy(context).setRedirectsEnabled(true).build());
                if (null != location) {
                    final URL url = new URL(location.getValue());
                    if (StringUtils.equals(Scheme.https.name(), url.getProtocol())) {
                        try {
                            callback.warn(host,
                                    MessageFormat.format(LocaleFactory.localizedString("Unsecured {0} connection",
                                            "Credentials"), host.getProtocol().getName()),
                                    MessageFormat.format("{0} {1}.",
                                            MessageFormat.format(LocaleFactory.localizedString(
                                                    "The server supports encrypted connections. Do you want to switch to {0}?",
                                                    "Credentials"), new DAVSSLProtocol().getName()),
                                            LocaleFactory.localizedString(
                                                    "Please contact your web hosting service provider for assistance",
                                                    "Support")),
                                    LocaleFactory.localizedString("Continue", "Credentials"),
                                    LocaleFactory.localizedString("Change", "Credentials"),
                                    String.format("connection.unsecure.%s", host.getHostname()));
                            // Continue chosen. Login using plain FTP.
                        } catch (LoginCanceledException e) {
                            // Protocol switch
                            host.setHostname(url.getHost());
                            host.setProtocol(ProtocolFactory.get().forScheme(Scheme.davs));
                            return false;
                        }
                    }
                }
                // Continue with default alert
            } catch (SardineException e) {
                // Ignore failure
                log.warn(String.format("Ignore failed HEAD request to %s with %s.", host, e.getResponsePhrase()));
            } catch (IOException e) {
                throw new HttpExceptionMappingService().map(e);
            }
            return preferences.getBoolean("webdav.basic.preemptive");
        }
        return false;
    }

    @Override
    public AttributedList<Path> list(final Path directory, final ListProgressListener listener)
            throws BackgroundException {
        return new DAVListService(this).list(directory, listener);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T _getFeature(final Class<T> type) {
        if (type == Directory.class) {
            return (T) new DAVDirectoryFeature(this);
        }
        if (type == Read.class) {
            return (T) new DAVReadFeature(this);
        }
        if (type == Write.class) {
            return (T) new DAVWriteFeature(this);
        }
        if (type == Upload.class) {
            return (T) new DAVUploadFeature(new DAVWriteFeature(this));
        }
        if (type == Delete.class) {
            return (T) new DAVDeleteFeature(this);
        }
        if (type == Move.class) {
            return (T) new DAVMoveFeature(this);
        }
        if (type == Headers.class) {
            return (T) new DAVMetadataFeature(this);
        }
        if (type == Metadata.class) {
            return (T) new DAVMetadataFeature(this);
        }
        if (type == Copy.class) {
            return (T) new DAVCopyFeature(this);
        }
        if (type == Find.class) {
            return (T) new DAVFindFeature(this);
        }
        if (type == AttributesFinder.class) {
            return (T) new DAVAttributesFinderFeature(this);
        }
        if (type == Timestamp.class) {
            return (T) new DAVTimestampFeature(this);
        }
        if (type == Quota.class) {
            return (T) new DAVQuotaFeature(this);
        }
        if (type == Lock.class) {
            return (T) new DAVLockFeature(this);
        }
        if (type == Touch.class) {
            return (T) new DefaultTouchFeature(new DAVUploadFeature(new DAVWriteFeature(this)));
        }
        return super._getFeature(type);
    }

}