/* * 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.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; 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.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.*; import java.util.*; @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 = HttpConfigurable.StorageChooser.class ) public class HttpConfigurable implements PersistentStateComponent, ExportableApplicationComponent { public static final int CONNECTION_TIMEOUT = SystemProperties.getIntProperty("idea.connection.timeout", 10000); private static final Logger LOG = Logger.getInstance(HttpConfigurable.class); 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 myGenericPasswords = new THashMap(); private final Set myGenericCancelled = new THashSet(); 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 myTestAuthRunnable = new StaticGetter(null); public transient Getter myTestGenericAuthRunnable = new StaticGetter(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() { @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 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) { final Runnable throughSwing = new Runnable() { @Override public void run() { if (SwingUtilities.isEventDispatchThread()) { runnable.run(); } else { try { SwingUtilities.invokeAndWait(runnable); } catch (InterruptedException e) { LOG.info(e); } catch (InvocationTargetException e) { LOG.info(e); } } } }; ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); if (progressIndicator != null && progressIndicator.isModal()) { WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(runnable); } else { throughSwing.run(); } } //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 { CommonProxy.isInstalledAssertion(); final URL url = new URL(location); URLConnection urlConnection = null; final List 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(CONNECTION_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()); } } public static List> 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> result = new ArrayList>(); 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 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 convertArguments(@NotNull final List> list) { if (list.isEmpty()) { return Collections.emptyList(); } final List result = new ArrayList(list.size()); for (KeyValue 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); } } @NotNull @Override public File[] getExportFiles() { return new File[]{PathManager.getOptionsFile("proxy.settings")}; } @NotNull @Override public String getPresentableName() { return "Proxy Settings"; } public static class StorageChooser implements StateStorageChooser { @Override public Storage[] selectStorages(Storage[] storages, HttpConfigurable component, StateStorageOperation operation) { if (operation == StateStorageOperation.WRITE) { for (Storage storage : storages) { if (storage.file().equals(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml")) { return new Storage[] {storage}; } } } return storages; } } 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; } } }