com.intellij.util.net.HttpConfigurable.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.util.net.HttpConfigurable.java

Source

/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.util.net;

import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.*;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.util.PopupUtil;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.util.Base64;
import com.intellij.util.SystemProperties;
import com.intellij.util.WaitForProgressToShow;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.proxy.CommonProxy;
import com.intellij.util.proxy.JavaProxyProperty;
import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.intellij.util.xmlb.annotations.Transient;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectObjectProcedure;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

@State(name = "HttpConfigurable", storages = {
        // we use two storages due to backward compatibility, see http://crucible.labs.intellij.net/cru/CR-IC-5142
        @Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml"), @Storage(file = StoragePathMacros.APP_CONFIG
                + "/proxy.settings.xml") }, storageChooser = LastStorageChooserForWrite.class)
public class HttpConfigurable implements PersistentStateComponent<HttpConfigurable>, ApplicationComponent {
    public static final int CONNECTION_TIMEOUT = SystemProperties.getIntProperty("idea.connection.timeout", 10000);
    public static final int READ_TIMEOUT = SystemProperties.getIntProperty("idea.read.timeout", 60000);
    public static final int REDIRECT_LIMIT = SystemProperties.getIntProperty("idea.redirect.limit", 10);

    public boolean PROXY_TYPE_IS_SOCKS;
    public boolean USE_HTTP_PROXY;
    public boolean USE_PROXY_PAC;
    public volatile transient boolean AUTHENTICATION_CANCELLED;
    public String PROXY_HOST;
    public int PROXY_PORT = 80;

    public volatile boolean PROXY_AUTHENTICATION;
    public volatile String PROXY_LOGIN;
    public volatile String PROXY_PASSWORD_CRYPT;
    public boolean KEEP_PROXY_PASSWORD;
    public transient String LAST_ERROR;

    private final THashMap<CommonProxy.HostInfo, ProxyInfo> myGenericPasswords = new THashMap<CommonProxy.HostInfo, ProxyInfo>();
    private final Set<CommonProxy.HostInfo> myGenericCancelled = new THashSet<CommonProxy.HostInfo>();

    public String PROXY_EXCEPTIONS;
    public boolean USE_PAC_URL;
    public String PAC_URL;

    private transient IdeaWideProxySelector mySelector;

    private transient final Object myLock = new Object();

    @SuppressWarnings("UnusedDeclaration")
    public transient Getter<PasswordAuthentication> myTestAuthRunnable = new StaticGetter<PasswordAuthentication>(
            null);
    public transient Getter<PasswordAuthentication> myTestGenericAuthRunnable = new StaticGetter<PasswordAuthentication>(
            null);

    public static HttpConfigurable getInstance() {
        return ServiceManager.getService(HttpConfigurable.class);
    }

    public static boolean editConfigurable(@Nullable JComponent parent) {
        return ShowSettingsUtil.getInstance().editConfigurable(parent, new HttpProxyConfigurable());
    }

    @Override
    public HttpConfigurable getState() {
        CommonProxy.isInstalledAssertion();

        HttpConfigurable state = new HttpConfigurable();
        XmlSerializerUtil.copyBean(this, state);
        if (!KEEP_PROXY_PASSWORD) {
            state.PROXY_PASSWORD_CRYPT = null;
        }
        correctPasswords(state);
        return state;
    }

    @Override
    public void initComponent() {
        mySelector = new IdeaWideProxySelector(this);
        String name = getClass().getName();
        CommonProxy.getInstance().setCustom(name, mySelector);
        CommonProxy.getInstance().setCustomAuth(name, new IdeaWideAuthenticator(this));
    }

    @NotNull
    public ProxySelector getOnlyBySettingsSelector() {
        return mySelector;
    }

    @Override
    public void disposeComponent() {
        final String name = getClass().getName();
        CommonProxy.getInstance().removeCustom(name);
        CommonProxy.getInstance().removeCustomAuth(name);
    }

    @NotNull
    @Override
    public String getComponentName() {
        return getClass().getName();
    }

    private void correctPasswords(@NotNull HttpConfigurable to) {
        synchronized (myLock) {
            to.myGenericPasswords.retainEntries(new TObjectObjectProcedure<CommonProxy.HostInfo, ProxyInfo>() {
                @Override
                public boolean execute(CommonProxy.HostInfo hostInfo, ProxyInfo proxyInfo) {
                    return proxyInfo.isStore();
                }
            });
        }
    }

    @Override
    public void loadState(@NotNull HttpConfigurable state) {
        XmlSerializerUtil.copyBean(state, this);
        if (!KEEP_PROXY_PASSWORD) {
            PROXY_PASSWORD_CRYPT = null;
        }
        correctPasswords(this);
    }

    public boolean isGenericPasswordCanceled(@NotNull String host, int port) {
        synchronized (myLock) {
            return myGenericCancelled.contains(new CommonProxy.HostInfo(null, host, port));
        }
    }

    public void setGenericPasswordCanceled(final String host, final int port) {
        synchronized (myLock) {
            myGenericCancelled.add(new CommonProxy.HostInfo(null, host, port));
        }
    }

    public PasswordAuthentication getGenericPassword(@NotNull String host, int port) {
        final ProxyInfo proxyInfo;
        synchronized (myLock) {
            proxyInfo = myGenericPasswords.get(new CommonProxy.HostInfo(null, host, port));
        }
        if (proxyInfo == null) {
            return null;
        }
        return new PasswordAuthentication(proxyInfo.getUsername(),
                decode(String.valueOf(proxyInfo.getPasswordCrypt())).toCharArray());
    }

    public void putGenericPassword(final String host, final int port,
            @NotNull PasswordAuthentication authentication, boolean remember) {
        PasswordAuthentication coded = new PasswordAuthentication(authentication.getUserName(),
                encode(String.valueOf(authentication.getPassword())).toCharArray());
        synchronized (myLock) {
            myGenericPasswords.put(new CommonProxy.HostInfo(null, host, port),
                    new ProxyInfo(remember, coded.getUserName(), String.valueOf(coded.getPassword())));
        }
    }

    @Transient
    @Nullable
    public String getPlainProxyPassword() {
        return PROXY_PASSWORD_CRYPT == null ? null : decode(PROXY_PASSWORD_CRYPT);
    }

    private static String decode(String value) {
        return new String(Base64.decode(value));
    }

    @Transient
    public void setPlainProxyPassword(String password) {
        PROXY_PASSWORD_CRYPT = encode(password);
    }

    private static String encode(String password) {
        return new String(Base64.encode(password.getBytes(CharsetToolkit.UTF8_CHARSET)));
    }

    public PasswordAuthentication getGenericPromptedAuthentication(final String prefix, final String host,
            final String prompt, final int port, final boolean remember) {
        if (ApplicationManager.getApplication().isUnitTestMode()) {
            return myTestGenericAuthRunnable.get();
        }

        final Ref<PasswordAuthentication> value = Ref.create();
        runAboveAll(new Runnable() {
            @Override
            public void run() {
                if (isGenericPasswordCanceled(host, port)) {
                    return;
                }

                PasswordAuthentication password = getGenericPassword(host, port);
                if (password != null) {
                    value.set(password);
                    return;
                }

                AuthenticationDialog dialog = new AuthenticationDialog(PopupUtil.getActiveComponent(),
                        prefix + host, "Please enter credentials for: " + prompt, "", "", remember);
                dialog.show();
                if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
                    AuthenticationPanel panel = dialog.getPanel();
                    PasswordAuthentication passwordAuthentication = new PasswordAuthentication(panel.getLogin(),
                            panel.getPassword());
                    putGenericPassword(host, port, passwordAuthentication, remember && panel.isRememberPassword());
                    value.set(passwordAuthentication);
                } else {
                    setGenericPasswordCanceled(host, port);
                }
            }
        });
        return value.get();
    }

    public PasswordAuthentication getPromptedAuthentication(final String host, final String prompt) {
        if (AUTHENTICATION_CANCELLED) {
            return null;
        }
        final String password = getPlainProxyPassword();
        if (PROXY_AUTHENTICATION && !StringUtil.isEmptyOrSpaces(PROXY_LOGIN)
                && !StringUtil.isEmptyOrSpaces(password)) {
            return new PasswordAuthentication(PROXY_LOGIN, password.toCharArray());
        }

        // do not try to show any dialogs if application is exiting
        if (ApplicationManager.getApplication() == null || ApplicationManager.getApplication().isDisposeInProgress()
                || ApplicationManager.getApplication().isDisposed())
            return null;

        if (ApplicationManager.getApplication().isUnitTestMode()) {
            return myTestGenericAuthRunnable.get();
        }
        final PasswordAuthentication[] value = new PasswordAuthentication[1];
        runAboveAll(new Runnable() {
            @Override
            public void run() {
                if (AUTHENTICATION_CANCELLED) {
                    return;
                }

                // password might have changed, and the check below is for that
                String password = getPlainProxyPassword();
                if (PROXY_AUTHENTICATION && !StringUtil.isEmptyOrSpaces(PROXY_LOGIN)
                        && !StringUtil.isEmptyOrSpaces(password)) {
                    value[0] = new PasswordAuthentication(PROXY_LOGIN, password.toCharArray());
                    return;
                }
                AuthenticationDialog dialog = new AuthenticationDialog(PopupUtil.getActiveComponent(),
                        "Proxy authentication: " + host, "Please enter credentials for: " + prompt, PROXY_LOGIN, "",
                        KEEP_PROXY_PASSWORD);
                dialog.show();
                if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
                    PROXY_AUTHENTICATION = true;
                    AuthenticationPanel panel = dialog.getPanel();
                    KEEP_PROXY_PASSWORD = panel.isRememberPassword();
                    PROXY_LOGIN = StringUtil.nullize(panel.getLogin());
                    setPlainProxyPassword(String.valueOf(panel.getPassword()));
                    value[0] = new PasswordAuthentication(panel.getLogin(), panel.getPassword());
                } else {
                    AUTHENTICATION_CANCELLED = true;
                }
            }
        });
        return value[0];
    }

    private static void runAboveAll(@NotNull final Runnable runnable) {
        ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
        if (progressIndicator != null && progressIndicator.isModal()) {
            WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(runnable);
        } else {
            Application app = ApplicationManager.getApplication();
            if (app.isDispatchThread()) {
                runnable.run();
            } else {
                app.invokeAndWait(runnable, ModalityState.any());
            }
        }
    }

    //these methods are preserved for compatibility with com.intellij.openapi.project.impl.IdeaServerSettings
    @Deprecated
    public void readExternal(Element element) throws InvalidDataException {
        //noinspection ConstantConditions
        loadState(XmlSerializer.deserialize(element, HttpConfigurable.class));
    }

    @Deprecated
    public void writeExternal(Element element) throws WriteExternalException {
        XmlSerializer.serializeInto(getState(), element);
        if (USE_PROXY_PAC && USE_HTTP_PROXY && !ApplicationManager.getApplication().isDisposed()) {
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                @Override
                public void run() {
                    IdeFrame frame = IdeFocusManager.findInstance().getLastFocusedFrame();
                    if (frame != null) {
                        USE_PROXY_PAC = false;
                        Messages.showMessageDialog(frame.getComponent(),
                                "Proxy: both 'use proxy' and 'autodetect proxy' settings were set."
                                        + "\nOnly one of these options should be selected.\nPlease re-configure.",
                                "Proxy Setup", Messages.getWarningIcon());
                        editConfigurable(frame.getComponent());
                    }
                }
            }, ModalityState.NON_MODAL);
        }
    }

    /**
     * todo [all] It is NOT necessary to call anything if you obey common IDEA proxy settings;
     * todo if you want to define your own behaviour, refer to {@link com.intellij.util.proxy.CommonProxy}
     *
     * also, this method is useful in a way that it test connection to the host [through proxy]
     *
     * @param url URL for HTTP connection
     * @throws IOException
     */
    public void prepareURL(@NotNull String url) throws IOException {
        URLConnection connection = openConnection(url);
        try {
            connection.connect();
            connection.getInputStream();
        } catch (IOException e) {
            throw e;
        } catch (Throwable ignored) {
        } finally {
            if (connection instanceof HttpURLConnection) {
                ((HttpURLConnection) connection).disconnect();
            }
        }
    }

    @NotNull
    public URLConnection openConnection(@NotNull String location) throws IOException {
        final URL url = new URL(location);
        URLConnection urlConnection = null;
        final List<Proxy> proxies = CommonProxy.getInstance().select(url);
        if (ContainerUtil.isEmpty(proxies)) {
            urlConnection = url.openConnection();
        } else {
            IOException exception = null;
            for (Proxy proxy : proxies) {
                try {
                    urlConnection = url.openConnection(proxy);
                } catch (IOException e) {
                    // continue iteration
                    exception = e;
                }
            }
            if (urlConnection == null && exception != null) {
                throw exception;
            }
        }

        assert urlConnection != null;
        urlConnection.setReadTimeout(READ_TIMEOUT);
        urlConnection.setConnectTimeout(CONNECTION_TIMEOUT);
        return urlConnection;
    }

    /**
     * Opens HTTP connection to a given location using configured http proxy settings.
     * @param location url to connect to
     * @return instance of {@link HttpURLConnection}
     * @throws IOException in case of any I/O troubles or if created connection isn't instance of HttpURLConnection.
     */
    @NotNull
    public HttpURLConnection openHttpConnection(@NotNull String location) throws IOException {
        URLConnection urlConnection = openConnection(location);
        if (urlConnection instanceof HttpURLConnection) {
            return (HttpURLConnection) urlConnection;
        } else {
            throw new IOException("Expected " + HttpURLConnection.class + ", but got " + urlConnection.getClass());
        }
    }

    @NotNull
    public RequestConfig.Builder setProxy(@NotNull RequestConfig.Builder builder) {
        return setProxy(builder, USE_HTTP_PROXY);
    }

    @NotNull
    public CredentialsProvider setProxyCredentials(@NotNull CredentialsProvider provider) {
        return setProxyCredentials(provider, USE_HTTP_PROXY);
    }

    @NotNull
    public RequestConfig.Builder setProxy(@NotNull RequestConfig.Builder builder, boolean useProxy) {
        if (useProxy) {
            builder.setProxy(new HttpHost(PROXY_HOST, PROXY_PORT));
        }

        return builder;
    }

    @NotNull
    public CredentialsProvider setProxyCredentials(@NotNull CredentialsProvider provider, boolean useProxy) {
        if (useProxy && PROXY_AUTHENTICATION) {
            provider.setCredentials(new AuthScope(PROXY_HOST, PROXY_PORT),
                    new UsernamePasswordCredentials(PROXY_LOGIN, getPlainProxyPassword()));
        }

        return provider;
    }

    public static List<KeyValue<String, String>> getJvmPropertiesList(final boolean withAutodetection,
            @Nullable final URI uri) {
        final HttpConfigurable me = getInstance();
        if (!me.USE_HTTP_PROXY && !me.USE_PROXY_PAC) {
            return Collections.emptyList();
        }
        final List<KeyValue<String, String>> result = new ArrayList<KeyValue<String, String>>();
        if (me.USE_HTTP_PROXY) {
            final boolean putCredentials = me.KEEP_PROXY_PASSWORD && StringUtil.isNotEmpty(me.PROXY_LOGIN);
            if (me.PROXY_TYPE_IS_SOCKS) {
                result.add(KeyValue.create(JavaProxyProperty.SOCKS_HOST, me.PROXY_HOST));
                result.add(KeyValue.create(JavaProxyProperty.SOCKS_PORT, String.valueOf(me.PROXY_PORT)));
                if (putCredentials) {
                    result.add(KeyValue.create(JavaProxyProperty.SOCKS_USERNAME, me.PROXY_LOGIN));
                    result.add(KeyValue.create(JavaProxyProperty.SOCKS_PASSWORD, me.getPlainProxyPassword()));
                }
            } else {
                result.add(KeyValue.create(JavaProxyProperty.HTTP_HOST, me.PROXY_HOST));
                result.add(KeyValue.create(JavaProxyProperty.HTTP_PORT, String.valueOf(me.PROXY_PORT)));
                result.add(KeyValue.create(JavaProxyProperty.HTTPS_HOST, me.PROXY_HOST));
                result.add(KeyValue.create(JavaProxyProperty.HTTPS_PORT, String.valueOf(me.PROXY_PORT)));
                if (putCredentials) {
                    result.add(KeyValue.create(JavaProxyProperty.HTTP_USERNAME, me.PROXY_LOGIN));
                    result.add(KeyValue.create(JavaProxyProperty.HTTP_PASSWORD, me.getPlainProxyPassword()));
                }
            }
        } else if (me.USE_PROXY_PAC && withAutodetection && uri != null) {
            final List<Proxy> proxies = CommonProxy.getInstance().select(uri);
            // we will just take the first returned proxy, but we have an option to test connection through each of them,
            // for instance, by calling prepareUrl()
            if (proxies != null && !proxies.isEmpty()) {
                for (Proxy proxy : proxies) {
                    if (isRealProxy(proxy)) {
                        final SocketAddress address = proxy.address();
                        if (address instanceof InetSocketAddress) {
                            final InetSocketAddress inetSocketAddress = (InetSocketAddress) address;
                            if (Proxy.Type.SOCKS.equals(proxy.type())) {
                                result.add(KeyValue.create(JavaProxyProperty.SOCKS_HOST,
                                        inetSocketAddress.getHostName()));
                                result.add(KeyValue.create(JavaProxyProperty.SOCKS_PORT,
                                        String.valueOf(inetSocketAddress.getPort())));
                            } else {
                                result.add(KeyValue.create(JavaProxyProperty.HTTP_HOST,
                                        inetSocketAddress.getHostName()));
                                result.add(KeyValue.create(JavaProxyProperty.HTTP_PORT,
                                        String.valueOf(inetSocketAddress.getPort())));
                                result.add(KeyValue.create(JavaProxyProperty.HTTPS_HOST,
                                        inetSocketAddress.getHostName()));
                                result.add(KeyValue.create(JavaProxyProperty.HTTPS_PORT,
                                        String.valueOf(inetSocketAddress.getPort())));
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    public static boolean isRealProxy(@NotNull Proxy proxy) {
        return !Proxy.NO_PROXY.equals(proxy) && !Proxy.Type.DIRECT.equals(proxy.type());
    }

    @NotNull
    public static List<String> convertArguments(@NotNull final List<KeyValue<String, String>> list) {
        if (list.isEmpty()) {
            return Collections.emptyList();
        }
        final List<String> result = new ArrayList<String>(list.size());
        for (KeyValue<String, String> value : list) {
            result.add("-D" + value.getKey() + "=" + value.getValue());
        }
        return result;
    }

    public void clearGenericPasswords() {
        synchronized (myLock) {
            myGenericPasswords.clear();
            myGenericCancelled.clear();
        }
    }

    public void removeGeneric(@NotNull CommonProxy.HostInfo info) {
        synchronized (myLock) {
            myGenericPasswords.remove(info);
        }
    }

    public static class ProxyInfo {
        public boolean myStore;
        public String myUsername;
        public String myPasswordCrypt;

        @SuppressWarnings("UnusedDeclaration")
        public ProxyInfo() {
        }

        public ProxyInfo(boolean store, String username, String passwordCrypt) {
            myStore = store;
            myUsername = username;
            myPasswordCrypt = passwordCrypt;
        }

        public boolean isStore() {
            return myStore;
        }

        public void setStore(boolean store) {
            myStore = store;
        }

        public String getUsername() {
            return myUsername;
        }

        public void setUsername(String username) {
            myUsername = username;
        }

        public String getPasswordCrypt() {
            return myPasswordCrypt;
        }

        @SuppressWarnings("UnusedDeclaration")
        public void setPasswordCrypt(String passwordCrypt) {
            myPasswordCrypt = passwordCrypt;
        }
    }
}