Subject: [PATCH] Fix #22497: Add a setting opposite to proxy.exceptions
---
Index: src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferencesPanel.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferencesPanel.java b/src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferencesPanel.java
--- a/src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferencesPanel.java	(revision 18658)
+++ b/src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferencesPanel.java	(date 1675964955776)
@@ -19,6 +19,7 @@
 import java.util.EnumMap;
 import java.util.Map;
 import java.util.Optional;
+import java.util.ArrayList;
 
 import javax.swing.BorderFactory;
 import javax.swing.Box;
@@ -31,6 +32,7 @@
 import org.openstreetmap.josm.gui.widgets.JosmPasswordField;
 import org.openstreetmap.josm.gui.widgets.JosmTextField;
 import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
+import org.openstreetmap.josm.gui.widgets.EditableList;
 import org.openstreetmap.josm.io.DefaultProxySelector;
 import org.openstreetmap.josm.io.ProxyPolicy;
 import org.openstreetmap.josm.io.auth.CredentialsAgent;
@@ -64,9 +66,12 @@
     private final JosmTextField tfProxySocksPort = new JosmTextField(5);
     private final JosmTextField tfProxyHttpUser = new JosmTextField(20);
     private final JosmPasswordField tfProxyHttpPassword = new JosmPasswordField(20);
+    private final EditableList tfExceptionHosts = new EditableList(tr("No proxy for"));
+    private final EditableList tfIncludeHosts = new EditableList(tr("Proxy only for"));
 
     private JPanel pnlHttpProxyConfigurationPanel;
     private JPanel pnlSocksProxyConfigurationPanel;
+    private JPanel pnlExceptionIncludesHostsProxyConfigurationPanel;
 
     /**
      * Builds the panel for the HTTP proxy configuration
@@ -179,6 +184,43 @@
         return pnl;
     }
 
+    protected final JPanel buildExceptionIncludesHostsProxyConfigurationPanel() {
+        JPanel pnl = new AutoSizePanel();
+        GridBagConstraints gc = new GridBagConstraints();
+        gc.anchor = GridBagConstraints.LINE_START;
+        gc.insets = new Insets(5, 5, 0, 0);
+        gc.weightx = 0.0;
+        pnl.add(new JLabel(tr("No proxy for (hosts):")), gc);
+
+        gc.gridx = 1;
+        gc.weightx = 0.0;
+        gc.fill = GridBagConstraints.NONE;
+        pnl.add(new JLabel(tr("Proxy only for (hosts):")), gc);
+
+        gc.gridy = 1;
+        gc.gridx = 0;
+        gc.weightx = 1.0;
+        gc.fill = HORIZONTAL;
+        tfExceptionHosts.setMinimumSize(tfExceptionHosts.getPreferredSize());
+        pnl.add(tfExceptionHosts, gc);
+
+        gc.gridx = 1;
+        gc.weightx = 1.0;
+        gc.fill = HORIZONTAL;
+        tfIncludeHosts.setMinimumSize(tfIncludeHosts.getPreferredSize());
+        pnl.add(tfIncludeHosts, gc);
+
+        // add an extra spacer, otherwise the layout is broken
+        gc.gridy = 2;
+        gc.gridx = 0;
+        gc.gridwidth = 2;
+        gc.fill = GridBagConstraints.BOTH;
+        gc.weightx = 1.0;
+        gc.weighty = 1.0;
+        pnl.add(new JPanel(), gc);
+        return pnl;
+    }
+
     protected final JPanel buildProxySettingsPanel() {
         JPanel pnl = new JPanel(new GridBagLayout());
 
@@ -217,6 +259,12 @@
 
         pnl.add(Box.createVerticalGlue(), GBC.eol().fill());
 
+        // the panel with the exception and includes hosts
+        pnlExceptionIncludesHostsProxyConfigurationPanel = buildExceptionIncludesHostsProxyConfigurationPanel();
+        pnl.add(pnlExceptionIncludesHostsProxyConfigurationPanel, GBC.eop().fill(HORIZONTAL));
+
+        pnl.add(Box.createVerticalGlue(), GBC.eol().fill());
+
         return pnl;
     }
 
@@ -238,6 +286,8 @@
         tfProxyHttpPort.setText(pref.get(DefaultProxySelector.PROXY_HTTP_PORT, ""));
         tfProxySocksHost.setText(pref.get(DefaultProxySelector.PROXY_SOCKS_HOST, ""));
         tfProxySocksPort.setText(pref.get(DefaultProxySelector.PROXY_SOCKS_PORT, ""));
+        tfExceptionHosts.setItems(pref.getList(DefaultProxySelector.PROXY_EXCEPTIONS, new ArrayList<String>()));
+        tfIncludeHosts.setItems(pref.getList(DefaultProxySelector.PROXY_INCLUDES, new ArrayList<String>()));
 
         if (pp == ProxyPolicy.USE_SYSTEM_SETTINGS && !DefaultProxySelector.willJvmRetrieveSystemProxies()) {
             Logging.warn(tr("JOSM is configured to use proxies from the system setting, but the JVM is not configured to retrieve them. " +
@@ -277,6 +327,11 @@
         }
 
         rbProxyPolicy.get(ProxyPolicy.USE_SYSTEM_SETTINGS).setEnabled(DefaultProxySelector.willJvmRetrieveSystemProxies());
+
+        boolean proxyEnabled = !rbProxyPolicy.get(ProxyPolicy.NO_PROXY).isSelected();
+        for (Component c : pnlExceptionIncludesHostsProxyConfigurationPanel.getComponents()) {
+            c.setEnabled(proxyEnabled);
+        }
     }
 
     class ProxyPolicyChangeListener implements ItemListener {
@@ -311,6 +366,9 @@
         pref.put(DefaultProxySelector.PROXY_HTTP_PORT, tfProxyHttpPort.getText());
         pref.put(DefaultProxySelector.PROXY_SOCKS_HOST, tfProxySocksHost.getText());
         pref.put(DefaultProxySelector.PROXY_SOCKS_PORT, tfProxySocksPort.getText());
+        pref.putList(DefaultProxySelector.PROXY_EXCEPTIONS, tfExceptionHosts.getItems());
+        pref.putList(DefaultProxySelector.PROXY_INCLUDES, tfIncludeHosts.getItems());
+
 
         // update the proxy selector
         ProxySelector selector = ProxySelector.getDefault();
Index: src/org/openstreetmap/josm/io/DefaultProxySelector.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/org/openstreetmap/josm/io/DefaultProxySelector.java b/src/org/openstreetmap/josm/io/DefaultProxySelector.java
--- a/src/org/openstreetmap/josm/io/DefaultProxySelector.java	(revision 18658)
+++ b/src/org/openstreetmap/josm/io/DefaultProxySelector.java	(date 1675967266823)
@@ -43,6 +43,11 @@
     public static final String PROXY_PASS = "proxy.pass";
     /** Property key for proxy exceptions list */
     public static final String PROXY_EXCEPTIONS = "proxy.exceptions";
+    /**
+     * Property key for hosts that should be proxied (if this is set, only specified hosts should be proxied)
+     * @since xxx
+     */
+    public static final String PROXY_INCLUDES = "proxy.includes.hosts";
 
     private static final List<Proxy> NO_PROXY_LIST = Collections.singletonList(Proxy.NO_PROXY);
 
@@ -86,6 +91,7 @@
     private final Set<String> errorResources = new HashSet<>();
     private final Set<String> errorMessages = new HashSet<>();
     private Set<String> proxyExceptions;
+    private Set<String> proxyIncludes;
 
     /**
      * A typical example is:
@@ -161,9 +167,13 @@
             }
         }
         proxyExceptions = new HashSet<>(
-            Config.getPref().getList(PROXY_EXCEPTIONS,
-                    Arrays.asList("localhost", IPV4_LOOPBACK, IPV6_LOOPBACK))
+                Config.getPref().getList(PROXY_EXCEPTIONS,
+                        Arrays.asList("localhost", IPV4_LOOPBACK, IPV6_LOOPBACK))
         );
+        proxyIncludes = new HashSet<>(
+                Config.getPref().getList(PROXY_INCLUDES,
+                        Collections.emptyList())
+        );
     }
 
     @Override
@@ -214,30 +224,39 @@
 
     @Override
     public List<Proxy> select(URI uri) {
-        if (uri != null && proxyExceptions.contains(uri.getHost())) {
+        // This is specified in ProxySelector#select, "@throws IllegalArgumentException if the argument is null"
+        if (uri == null) {
+            throw new IllegalArgumentException("URI cannot be null");
+        }
+        if (proxyExceptions.contains(uri.getHost())) {
             return NO_PROXY_LIST;
         }
-        switch(proxyPolicy) {
+        switch (proxyPolicy) {
         case USE_SYSTEM_SETTINGS:
             if (!jvmWillUseSystemProxies) {
-                Logging.warn(tr("The JVM is not configured to lookup proxies from the system settings. "+
+                Logging.warn(tr("The JVM is not configured to lookup proxies from the system settings. " +
                         "The property ''java.net.useSystemProxies'' was missing at startup time.  Will not use a proxy."));
                 return NO_PROXY_LIST;
             }
+            if (!proxyIncludes.isEmpty() && !proxyIncludes.contains(uri.getHost())) {
+                return NO_PROXY_LIST;
+            }
             // delegate to the former proxy selector
             return delegate.select(uri);
         case NO_PROXY:
             return NO_PROXY_LIST;
         case USE_HTTP_PROXY:
-            if (httpProxySocketAddress == null)
+            if (httpProxySocketAddress == null || (!proxyIncludes.isEmpty() && !proxyIncludes.contains(uri.getHost()))) {
                 return NO_PROXY_LIST;
+            }
             return Collections.singletonList(new Proxy(Type.HTTP, httpProxySocketAddress));
         case USE_SOCKS_PROXY:
-            if (socksProxySocketAddress == null)
+            if (socksProxySocketAddress == null || (!proxyIncludes.isEmpty() && !proxyIncludes.contains(uri.getHost()))) {
                 return NO_PROXY_LIST;
+            }
             return Collections.singletonList(new Proxy(Type.SOCKS, socksProxySocketAddress));
         }
         // should not happen
-        return null;
+        return Collections.emptyList();
     }
 }
