< prev index next >

src/java.base/windows/native/libnet/DefaultProxySelector.c

Print this page
rev 16207 : 8170868: DefaultProxySelector should use system defaults on Windows, MacOS and Gnome
Contributed-by: arno.zeller@sap.com

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -22,263 +22,355 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 
 #include <windows.h>
+#include <Winhttp.h>
+
 #include "jni.h"
 #include "jni_util.h"
 #include "jvm.h"
-#include "jlong.h"
+
+#include "proxy_util.h"
+
 #include "sun_net_spi_DefaultProxySelector.h"
 
 /**
  * These functions are used by the sun.net.spi.DefaultProxySelector class
  * to access some platform specific settings.
- * This is the Windows code using the registry settings.
+ * This is the Windows code using WinHTTP functions to get the system settings.
  */
 
-static jclass proxy_class;
-static jclass isaddr_class;
-static jclass ptype_class;
-static jmethodID isaddr_createUnresolvedID;
-static jmethodID proxy_ctrID;
-static jfieldID pr_no_proxyID;
-static jfieldID ptype_httpID;
-static jfieldID ptype_socksID;
+/* Keep one static session for all requests. */
+static HINTERNET session = NULL;
 
 /*
  * Class:     sun_net_spi_DefaultProxySelector
  * Method:    init
  * Signature: ()Z
  */
 JNIEXPORT jboolean JNICALL
 Java_sun_net_spi_DefaultProxySelector_init(JNIEnv *env, jclass clazz) {
-  HKEY hKey;
-  LONG ret;
-  jclass cls;
-
-  /**
-   * Get all the method & field IDs for later use.
-   */
-  cls = (*env)->FindClass(env,"java/net/Proxy");
-  CHECK_NULL_RETURN(cls, JNI_FALSE);
-  proxy_class = (*env)->NewGlobalRef(env, cls);
-  CHECK_NULL_RETURN(proxy_class, JNI_FALSE);
-  cls = (*env)->FindClass(env,"java/net/Proxy$Type");
-  CHECK_NULL_RETURN(cls, JNI_FALSE);
-  ptype_class = (*env)->NewGlobalRef(env, cls);
-  CHECK_NULL_RETURN(ptype_class, JNI_FALSE);
-  cls = (*env)->FindClass(env, "java/net/InetSocketAddress");
-  CHECK_NULL_RETURN(cls, JNI_FALSE);
-  isaddr_class = (*env)->NewGlobalRef(env, cls);
-  CHECK_NULL_RETURN(isaddr_class, JNI_FALSE);
-  proxy_ctrID = (*env)->GetMethodID(env, proxy_class, "<init>",
-                                    "(Ljava/net/Proxy$Type;Ljava/net/SocketAddress;)V");
-  CHECK_NULL_RETURN(proxy_ctrID, JNI_FALSE);
-  pr_no_proxyID = (*env)->GetStaticFieldID(env, proxy_class, "NO_PROXY", "Ljava/net/Proxy;");
-  CHECK_NULL_RETURN(pr_no_proxyID, JNI_FALSE);
-  ptype_httpID = (*env)->GetStaticFieldID(env, ptype_class, "HTTP", "Ljava/net/Proxy$Type;");
-  CHECK_NULL_RETURN(ptype_httpID, JNI_FALSE);
-  ptype_socksID = (*env)->GetStaticFieldID(env, ptype_class, "SOCKS", "Ljava/net/Proxy$Type;");
-  CHECK_NULL_RETURN(ptype_socksID, JNI_FALSE);
-  isaddr_createUnresolvedID = (*env)->GetStaticMethodID(env, isaddr_class, "createUnresolved",
-                                                        "(Ljava/lang/String;I)Ljava/net/InetSocketAddress;");
-  CHECK_NULL_RETURN(isaddr_createUnresolvedID, JNI_FALSE);
-
-  /**
-   * Let's see if we can find the proper Registry entry.
-   */
-  ret = RegOpenKeyEx(HKEY_CURRENT_USER,
-                     "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
-                     0, KEY_READ, (PHKEY)&hKey);
-  if (ret == ERROR_SUCCESS) {
-    RegCloseKey(hKey);
-    /**
-     * It worked, we can probably rely on it then.
+
+    /*
+     * Get one WinHTTP session handle to initialize the WinHTTP internal data structures
+     * We keep and use only this one for the whole life time.
      */
-    return JNI_TRUE;
-  }
+    session = WinHttpOpen(L"Only used internal", /* we need no real agent string here */
+                          WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
+                          WINHTTP_NO_PROXY_NAME,
+                          WINHTTP_NO_PROXY_BYPASS,
+                          0);
+    if (session == NULL) {
+        return JNI_FALSE;
+    }
+
+    if (!initJavaClass(env)) {
+        return JNI_FALSE;
+    }
 
-  return JNI_FALSE;
+    return JNI_TRUE;
 }
 
+
 #define MAX_STR_LEN 1024
 
+/* A linked list element for a proxy */
+typedef struct list_item {
+    wchar_t *host;
+    int port;
+    struct list_item *next;
+} list_item;
+
+/* Free the linked list */
+static void freeList(list_item *head) {
+    list_item *next = NULL;
+    list_item *current = head;
+    while (current != NULL) {
+        next = current->next;
+        free(current->host);
+        free(current);
+        current = next;
+    }
+}
+
+
 /*
- * Class:     sun_net_spi_DefaultProxySelector
- * Method:    getSystemProxy
- * Signature: ([Ljava/lang/String;Ljava/lang/String;)Ljava/net/Proxy;
+ * Creates a linked list of list_item elements that has to be freed later on.
+ * Returns the size of the array as int.
  */
-JNIEXPORT jobject JNICALL
-Java_sun_net_spi_DefaultProxySelector_getSystemProxy(JNIEnv *env,
-                                                     jobject this,
-                                                     jstring proto,
-                                                     jstring host)
-{
-  jobject isa = NULL;
-  jobject proxy = NULL;
-  jobject type_proxy = NULL;
-  jobject no_proxy = NULL;
-  jboolean isCopy;
-  HKEY hKey;
-  LONG ret;
-  const char* cproto;
-  const char* urlhost;
-  char pproto[MAX_STR_LEN];
-  char regserver[MAX_STR_LEN];
-  char override[MAX_STR_LEN];
-  char *s, *s2;
-  char *ctx = NULL;
-  int pport = 0;
-  int defport = 0;
-  char *phost;
-
-  /**
-   * Let's open the Registry entry. We'll check a few values in it:
-   *
-   * - ProxyEnable: 0 means no proxy, 1 means use the proxy
-   * - ProxyServer: a string that can take 2 forms:
-   *    "server[:port]"
-   *    or
-   *    "protocol1=server[:port][;protocol2=server[:port]]..."
-   * - ProxyOverride: a string containing a list of prefixes for hostnames.
-   *   e.g.: hoth;localhost;<local>
-   */
-  ret = RegOpenKeyEx(HKEY_CURRENT_USER,
-                     "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
-                     0, KEY_READ, (PHKEY)&hKey);
-  if (ret == ERROR_SUCCESS) {
-    DWORD dwLen;
-    DWORD dwProxyEnabled;
-    ULONG ulType;
-    dwLen = sizeof(dwProxyEnabled);
-
-    /**
-     * Let's see if the proxy settings are to be used.
+static int createProxyList(LPWSTR win_proxy, const WCHAR *pproto, list_item **head) {
+    static const wchar_t separators[] = L"\t\r\n ;";
+    list_item *current = NULL;
+    int nr_elems = 0;
+    wchar_t *context = NULL;
+    wchar_t *current_proxy = NULL;
+    BOOL error = FALSE;
+
+    /*
+     * The proxy server list contains one or more of the following strings separated by semicolons or whitespace.
+     * ([<scheme>=][<scheme>"://"]<server>[":"<port>])
      */
-    ret = RegQueryValueEx(hKey, "ProxyEnable",  NULL, &ulType,
-                          (LPBYTE)&dwProxyEnabled, &dwLen);
-    if ((ret == ERROR_SUCCESS) && (dwProxyEnabled > 0)) {
-      /*
-       * Yes, ProxyEnable == 1
-       */
-      dwLen = sizeof(override);
-      override[0] = 0;
-      ret = RegQueryValueEx(hKey, "ProxyOverride", NULL, &ulType,
-                            (LPBYTE)&override, &dwLen);
-      dwLen = sizeof(regserver);
-      regserver[0] = 0;
-      ret = RegQueryValueEx(hKey, "ProxyServer",  NULL, &ulType,
-                            (LPBYTE)&regserver, &dwLen);
-      RegCloseKey(hKey);
-      if (ret == ERROR_SUCCESS) {
-        if (strlen(override) > 0) {
-          /**
-           * we did get ProxyServer and may have an override.
-           * So let's check the override list first, by walking down the list
-           * The semicolons (;) separated entries have to be matched with the
-           * the beginning of the hostname.
-           */
-          s = strtok_s(override, "; ", &ctx);
-          urlhost = (*env)->GetStringUTFChars(env, host, &isCopy);
-          if (urlhost == NULL) {
-            if (!(*env)->ExceptionCheck(env))
-              JNU_ThrowOutOfMemoryError(env, NULL);
-            return NULL;
+    current_proxy = wcstok_s(win_proxy, separators, &context);
+    while (current_proxy != NULL) {
+        LPWSTR pport;
+        LPWSTR phost;
+        int portVal = 0;
+        wchar_t *next_proxy = NULL;
+        list_item *proxy = NULL;
+        wchar_t* pos = NULL;
+
+        /* Filter based on the scheme, if there is one */
+        pos = wcschr(current_proxy, L'=');
+        if (pos) {
+          *pos = L'\0';
+          if (wcscmp(current_proxy, pproto) != 0) {
+              current_proxy = wcstok_s(NULL, separators, &context);
+              continue;
           }
-          while (s != NULL) {
-            if (strncmp(s, urlhost, strlen(s)) == 0) {
-              /**
-               * the URL host name matches with one of the prefixes,
-               * therefore we have to use a direct connection.
-               */
-              if (isCopy == JNI_TRUE)
-                (*env)->ReleaseStringUTFChars(env, host, urlhost);
-              goto noproxy;
+          current_proxy = pos + 1;
+        }
+
+        /* Let's check for a scheme and ignore it. */
+        if ((phost = wcsstr(current_proxy, L"://")) != NULL) {
+            phost += 3;
+        } else {
+            phost = current_proxy;
+        }
+
+        /* Get the port */
+        pport = wcschr(phost, L':');
+        if (pport != NULL) {
+            *pport = 0;
+            pport++;
+            swscanf(pport, L"%d", &portVal);
+        }
+
+        proxy = (list_item *)malloc(sizeof(list_item));
+        if (proxy != NULL) {
+            proxy->next = NULL;
+            proxy->port = portVal;
+            proxy->host = _wcsdup(phost);
+
+            if (proxy->host != NULL) {
+                if (*head == NULL) {
+                    *head = proxy; /* first elem */
+                }
+                if (current != NULL) {
+                    current->next = proxy;
+                }
+                current = proxy;
+                nr_elems++;
+            } else {
+                free(proxy); /* cleanup */
             }
-            s = strtok_s(NULL, "; ", &ctx);
-          }
-          if (isCopy == JNI_TRUE)
-            (*env)->ReleaseStringUTFChars(env, host, urlhost);
         }
+        /* goto next proxy if available... */
+        current_proxy = wcstok_s(NULL, separators, &context);
+    }
+    return nr_elems;
+}
+
+
+
+/*
+ * Class:     sun_net_spi_DefaultProxySelector
+ * Method:    getSystemProxies
+ * Signature: ([Ljava/lang/String;Ljava/lang/String;)[Ljava/net/Proxy;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_sun_net_spi_DefaultProxySelector_getSystemProxies(JNIEnv *env,
+                                                       jobject this,
+                                                       jstring proto,
+                                                       jstring host)
+{
+    jobjectArray proxy_array = NULL;
+    jobject type_proxy = NULL;
+    LPCWSTR lpProto;
+    LPCWSTR lpHost;
+    list_item *head = NULL;
+
+    BOOL                                   use_auto_proxy = FALSE;
+    WINHTTP_CURRENT_USER_IE_PROXY_CONFIG   ie_proxy_config;
+    WINHTTP_AUTOPROXY_OPTIONS              auto_proxy_options;
+    WINHTTP_PROXY_INFO                     proxy_info;
+    LPWSTR win_proxy = NULL;
+    LPWSTR win_bypass_proxy = NULL;
+
+    memset(&ie_proxy_config, 0, sizeof(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG));
+    memset(&auto_proxy_options, 0, sizeof(WINHTTP_AUTOPROXY_OPTIONS));
+    memset(&proxy_info, 0, sizeof(WINHTTP_PROXY_INFO));
+
+    lpHost = (*env)->GetStringChars(env, host, NULL);
+    if (lpHost == NULL) {
+        if (!(*env)->ExceptionCheck(env))
+            JNU_ThrowOutOfMemoryError(env, NULL);
+        return NULL;
+    }
 
-        cproto = (*env)->GetStringUTFChars(env, proto, &isCopy);
-        if (cproto == NULL) {
-          if (!(*env)->ExceptionCheck(env))
+    lpProto = (*env)->GetStringChars(env, proto, NULL);
+    if (lpProto == NULL) {
+        (*env)->ReleaseStringChars(env, host, lpHost);
+        if (!(*env)->ExceptionCheck(env))
             JNU_ThrowOutOfMemoryError(env, NULL);
-          return NULL;
+        return NULL;
+    }
+
+    if (WinHttpGetIEProxyConfigForCurrentUser(&ie_proxy_config) == FALSE) {
+        /* cleanup and exit */
+        (*env)->ReleaseStringChars(env, host, lpHost);
+        (*env)->ReleaseStringChars(env, proto, lpProto);
+        return NULL;
+    }
+
+    if (ie_proxy_config.fAutoDetect) {
+        /* Windows uses WPAD */
+        auto_proxy_options.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP |
+                                               WINHTTP_AUTO_DETECT_TYPE_DNS_A;
+        auto_proxy_options.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
+        auto_proxy_options.fAutoLogonIfChallenged = TRUE;
+        use_auto_proxy = TRUE;
+    } else if (ie_proxy_config.lpszAutoConfigUrl != NULL) {
+        /* Windows uses PAC file */
+        auto_proxy_options.lpszAutoConfigUrl = ie_proxy_config.lpszAutoConfigUrl;
+        auto_proxy_options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
+        use_auto_proxy = TRUE;
+    } else if (ie_proxy_config.lpszProxy != NULL) {
+        /* Windows uses manually entered proxy. */
+        use_auto_proxy = FALSE;
+        win_bypass_proxy = ie_proxy_config.lpszProxyBypass;
+        win_proxy = ie_proxy_config.lpszProxy;
+    }
+
+    if (use_auto_proxy) {
+        WCHAR url[MAX_STR_LEN];
+        /* Create url for WinHttpGetProxyForUrl */
+        _snwprintf(url, sizeof(url) - 1, L"%s://%s", lpProto, lpHost);
+        /* Get proxy for URL from Windows */
+        use_auto_proxy = WinHttpGetProxyForUrl(session, &url[0], &auto_proxy_options, &proxy_info);
+        if (use_auto_proxy) {
+            win_proxy = proxy_info.lpszProxy;
+            win_bypass_proxy = proxy_info.lpszProxyBypass;
         }
+    }
 
+    /* Check the bypass entry. */
+    if (NULL != win_bypass_proxy) {
         /*
-         * Set default port value & proxy type from protocol.
+         * From MSDN:
+         * The proxy bypass list contains one or more server names separated by semicolons or
+         * whitespace. The proxy bypass list can also contain the string "<local>" to indicate
+         * that all local intranet sites are bypassed. Local intranet sites are considered to
+         * be all servers that do not contain a period in their name.
          */
-        if ((strcmp(cproto, "http") == 0) ||
-            (strcmp(cproto, "ftp") == 0) ||
-            (strcmp(cproto, "gopher") == 0))
-          defport = 80;
-        if (strcmp(cproto, "https") == 0)
-          defport = 443;
-        if (strcmp(cproto, "socks") == 0) {
-          defport = 1080;
-          type_proxy = (*env)->GetStaticObjectField(env, ptype_class, ptype_socksID);
-        } else {
-          type_proxy = (*env)->GetStaticObjectField(env, ptype_class, ptype_httpID);
+        wchar_t *context = NULL;
+        LPWSTR s = wcstok_s(win_bypass_proxy, L"; ", &context);
+
+        while (s != NULL) {
+            size_t maxlen = wcslen(s);
+            if (wcsncmp(s, lpHost, maxlen) == 0) {
+                /*
+                 * The URL host name matches with one of the prefixes, we have to use a direct
+                 * connection.
+                 */
+                goto noproxy;
+            }
+            if (wcsncmp(s, L"<local>", maxlen) == 0) {
+                /*
+                 * All local intranet sites are bypassed - Microsoft considers all servers that
+                 * do not contain a period in their name to be local.
+                 */
+                if (wcschr(lpHost, '.') == NULL) {
+                    goto noproxy;
+                }
+            }
+            s = wcstok_s(NULL, L"; ", &context);
         }
+    }
+
+    if (win_proxy != NULL) {
+        wchar_t *context = NULL;
+        int defport = 0;
+        int nr_elems = 0;
 
-        sprintf(pproto,"%s=", cproto);
-        if (isCopy == JNI_TRUE)
-          (*env)->ReleaseStringUTFChars(env, proto, cproto);
         /**
-         * Let's check the protocol specific form first.
+         * Set default port value & proxy type from protocol.
          */
-        if ((s = strstr(regserver, pproto)) != NULL) {
-          s += strlen(pproto);
+        if ((wcscmp(lpProto, L"http") == 0) ||
+            (wcscmp(lpProto, L"ftp") == 0) ||
+            (wcscmp(lpProto, L"gopher") == 0))
+            defport = 80;
+        if (wcscmp(lpProto, L"https") == 0)
+            defport = 443;
+        if (wcscmp(lpProto, L"socks") == 0) {
+            defport = 1080;
+            type_proxy = (*env)->GetStaticObjectField(env, ptype_class, ptype_socksID);
         } else {
-          /**
-           * If we couldn't find *this* protocol but the string is in the
-           * protocol specific format, then don't use proxy
-           */
-          if (strchr(regserver, '=') != NULL)
-            goto noproxy;
-          s = regserver;
+            type_proxy = (*env)->GetStaticObjectField(env, ptype_class, ptype_httpID);
         }
-        s2 = strchr(s, ';');
-        if (s2 != NULL)
-          *s2 = 0;
-
-        /**
-         * Is there a port specified?
-         */
-        s2 = strchr(s, ':');
-        if (s2 != NULL) {
-          *s2 = 0;
-          s2++;
-          sscanf(s2, "%d", &pport);
+        if (type_proxy == NULL || (*env)->ExceptionCheck(env)) {
+            goto noproxy;
         }
-        phost = s;
 
-        if (phost != NULL) {
-          /**
-           * Let's create the appropriate Proxy object then.
-           */
-          jstring jhost;
-          if (pport == 0)
-            pport = defport;
-          jhost = (*env)->NewStringUTF(env, phost);
-          CHECK_NULL_RETURN(jhost, NULL);
-          isa = (*env)->CallStaticObjectMethod(env, isaddr_class, isaddr_createUnresolvedID, jhost, pport);
-          CHECK_NULL_RETURN(isa, NULL);
-          proxy = (*env)->NewObject(env, proxy_class, proxy_ctrID, type_proxy, isa);
-          return proxy;
+        nr_elems = createProxyList(win_proxy, lpProto, &head);
+        if (nr_elems != 0 && head != NULL) {
+            int index = 0;
+            proxy_array = (*env)->NewObjectArray(env, nr_elems, proxy_class, NULL);
+            if (proxy_array == NULL || (*env)->ExceptionCheck(env)) {
+                goto noproxy;
+            }
+            while (head != NULL && index < nr_elems) {
+                jstring jhost;
+                jobject isa;
+                jobject proxy;
+
+                if (head->host != NULL && proxy_array != NULL) {
+                    /* Let's create the appropriate Proxy object then. */
+                    if (head->port == 0) {
+                        head->port = defport;
+                    }
+                    jhost = (*env)->NewString(env, head->host, (jsize)wcslen(head->host));
+                    if (jhost == NULL || (*env)->ExceptionCheck(env)) {
+                        proxy_array = NULL;
+                    }
+                    isa = (*env)->CallStaticObjectMethod(env, isaddr_class,
+                                                         isaddr_createUnresolvedID, jhost,
+                                                         head->port);
+                    if (isa == NULL || (*env)->ExceptionCheck(env)) {
+                        proxy_array = NULL;
+                    }
+                    proxy = (*env)->NewObject(env, proxy_class, proxy_ctrID, type_proxy, isa);
+                    if (proxy == NULL || (*env)->ExceptionCheck(env)) {
+                        proxy_array = NULL;
+                    }
+                    (*env)->SetObjectArrayElement(env, proxy_array, index, proxy);
+                    if ((*env)->ExceptionCheck(env)) {
+                        proxy_array = NULL;
+                    }
+                    index++;
+                }
+                head = head->next;
+            }
         }
-      }
-    } else {
-      /* ProxyEnable == 0 or Query failed      */
-      /* close the handle to the registry key  */
-      RegCloseKey(hKey);
     }
-  }
 
 noproxy:
-  no_proxy = (*env)->GetStaticObjectField(env, proxy_class, pr_no_proxyID);
-  return no_proxy;
+    if (head != NULL) {
+        freeList(head);
+    }
+    if (proxy_info.lpszProxy != NULL)
+      GlobalFree(proxy_info.lpszProxy);
+    if (proxy_info.lpszProxyBypass != NULL)
+      GlobalFree(proxy_info.lpszProxyBypass);
+    if (ie_proxy_config.lpszAutoConfigUrl != NULL)
+      GlobalFree(ie_proxy_config.lpszAutoConfigUrl);
+    if (ie_proxy_config.lpszProxy != NULL)
+      GlobalFree(ie_proxy_config.lpszProxy);
+    if (ie_proxy_config.lpszProxyBypass != NULL)
+      GlobalFree(ie_proxy_config.lpszProxyBypass);
+    if (lpHost != NULL)
+      (*env)->ReleaseStringChars(env, host, lpHost);
+    if (lpProto != NULL)
+      (*env)->ReleaseStringChars(env, proto, lpProto);
+
+    return proxy_array;
 }
< prev index next >