| /* |
| * 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 = LastStorageChooserForWrite.class |
| ) |
| public class HttpConfigurable implements PersistentStateComponent<HttpConfigurable>, 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<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) { |
| 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<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(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<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); |
| } |
| } |
| |
| @NotNull |
| @Override |
| public File[] getExportFiles() { |
| return new File[]{PathManager.getOptionsFile("proxy.settings")}; |
| } |
| |
| @NotNull |
| @Override |
| public String getPresentableName() { |
| return "Proxy Settings"; |
| } |
| |
| 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; |
| } |
| } |
| } |