1/*
2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3 * Copyright (c) 2017 SAP SE. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27#include <string.h>
28#include <CoreFoundation/CoreFoundation.h>
29#include <CoreServices/CoreServices.h>
30
31#include "jni.h"
32#include "jni_util.h"
33#include "jvm.h"
34#include "jvm_md.h"
35
36#include "proxy_util.h"
37
38#include "sun_net_spi_DefaultProxySelector.h"
39
40
41/**
42 * For more information on how to use the APIs in "CFProxySupport.h" see:
43 * https://developer.apple.com/legacy/library/samplecode/CFProxySupportTool/Introduction/Intro.html
44 */
45
46#define kResolveProxyRunLoopMode CFSTR("sun.net.spi.DefaultProxySelector")
47
48#define BUFFER_SIZE 1024
49
50/* Callback for CFNetworkExecuteProxyAutoConfigurationURL. */
51static void proxyUrlCallback(void * client, CFArrayRef proxies, CFErrorRef error) {
52    /* client is a pointer to a CFTypeRef and holds either proxies or an error. */
53    CFTypeRef* resultPtr = (CFTypeRef *)client;
54
55    if (error != NULL) {
56        *resultPtr = CFRetain(error);
57    } else {
58        *resultPtr = CFRetain(proxies);
59    }
60    CFRunLoopStop(CFRunLoopGetCurrent());
61}
62
63/*
64 * Returns a new array of proxies containing all the given non-PAC proxies as
65 * well as the results of executing all the given PAC-based proxies, for the
66 * specified URL. 'proxies' is a list that may contain both PAC and non-PAC
67 * proxies.
68 */
69static CFArrayRef createExpandedProxiesArray(CFArrayRef proxies, CFURLRef url) {
70
71    CFIndex count;
72    CFIndex index;
73    CFMutableArrayRef expandedProxiesArray;
74
75    expandedProxiesArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
76    if (expandedProxiesArray == NULL)
77        return NULL;
78
79    /* Iterate over the array of proxies */
80    count = CFArrayGetCount(proxies);
81    for (index = 0; index < count ; index++) {
82        CFDictionaryRef currentProxy;
83        CFStringRef     proxyType;
84
85        currentProxy = (CFDictionaryRef) CFArrayGetValueAtIndex(proxies, index);
86        if(currentProxy == NULL) {
87            CFRelease(expandedProxiesArray);
88            return NULL;
89        }
90        proxyType = (CFStringRef) CFDictionaryGetValue(currentProxy, kCFProxyTypeKey);
91        if (proxyType == NULL) {
92            CFRelease(expandedProxiesArray);
93            return NULL;
94        }
95
96        if (!CFEqual(proxyType, kCFProxyTypeAutoConfigurationURL)) {
97            /* Non-PAC entry, just copy it to the new array */
98            CFArrayAppendValue(expandedProxiesArray, currentProxy);
99        } else {
100            /* PAC-based URL, execute its script append its results */
101            CFRunLoopSourceRef      runLoop;
102            CFURLRef                scriptURL;
103            CFTypeRef               result = NULL;
104            CFStreamClientContext   context = { 0, &result, NULL, NULL, NULL };
105            CFTimeInterval timeout = 5;
106
107            scriptURL = CFDictionaryGetValue(currentProxy, kCFProxyAutoConfigurationURLKey);
108
109            runLoop = CFNetworkExecuteProxyAutoConfigurationURL(scriptURL, url, proxyUrlCallback,
110                                                                &context);
111            if (runLoop != NULL) {
112                /*
113                 * Despite the fact that CFNetworkExecuteProxyAutoConfigurationURL has
114                 * neither a "Create" nor a "Copy" in the name, we are required to
115                 * release the return CFRunLoopSourceRef <rdar://problem/5533931>.
116                 */
117                CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoop, kResolveProxyRunLoopMode);
118                CFRunLoopRunInMode(kResolveProxyRunLoopMode, timeout, false);
119                CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoop, kResolveProxyRunLoopMode);
120
121                /*
122                 * Once the runloop returns, there will be either an error result or
123                 * a proxies array result. Do the appropriate thing with that result.
124                 */
125                if (result != NULL) {
126                    if (CFGetTypeID(result) == CFArrayGetTypeID()) {
127                        /*
128                         * Append the new array from the PAC list - it contains
129                         * only non-PAC entries.
130                         */
131                        CFArrayAppendArray(expandedProxiesArray, result,
132                                           CFRangeMake(0, CFArrayGetCount(result)));
133                    }
134                    CFRelease(result);
135                }
136                CFRelease(runLoop);
137            }
138        }
139    }
140    return expandedProxiesArray;
141}
142
143
144/*
145 * Class:     sun_net_spi_DefaultProxySelector
146 * Method:    init
147 * Signature: ()Z
148 */
149JNIEXPORT jboolean JNICALL
150Java_sun_net_spi_DefaultProxySelector_init(JNIEnv *env, jclass clazz) {
151    if (!initJavaClass(env)) {
152        return JNI_FALSE;
153    }
154    return JNI_TRUE;
155}
156
157
158/*
159 * Class:     sun_net_spi_DefaultProxySelector
160 * Method:    getSystemProxies
161 * Signature: ([Ljava/lang/String;Ljava/lang/String;)[Ljava/net/Proxy;
162 */
163JNIEXPORT jobjectArray JNICALL
164Java_sun_net_spi_DefaultProxySelector_getSystemProxies(JNIEnv *env,
165                                                       jobject this,
166                                                       jstring proto,
167                                                       jstring host)
168{
169    CFDictionaryRef proxyDicRef = NULL;
170    CFURLRef        urlRef = NULL;
171    bool proxyFound = false;
172    jobjectArray proxyArray = NULL;
173    const char *cproto;
174    const char *chost;
175
176    /* Get system proxy settings */
177    proxyDicRef = CFNetworkCopySystemProxySettings();
178    if (proxyDicRef == NULL) {
179        return NULL;
180    }
181
182    /* Create CFURLRef from proto and host */
183    cproto = (*env)->GetStringUTFChars(env, proto, NULL);
184    if (cproto != NULL) {
185        chost  = (*env)->GetStringUTFChars(env, host, NULL);
186        if (chost != NULL) {
187            char* uri = NULL;
188            size_t protoLen = 0;
189            size_t hostLen = 0;
190
191            protoLen = strlen(cproto);
192            hostLen = strlen(chost);
193
194            /* Construct the uri, cproto + "://" + chost */
195            uri = malloc(protoLen + hostLen + 4);
196            if (uri != NULL) {
197                memcpy(uri, cproto, protoLen);
198                memcpy(uri + protoLen, "://", 3);
199                memcpy(uri + protoLen + 3, chost, hostLen + 1);
200
201                urlRef = CFURLCreateWithBytes(NULL, (const UInt8 *) uri, strlen(uri),
202                                              kCFStringEncodingUTF8, NULL);
203                free(uri);
204            }
205            (*env)->ReleaseStringUTFChars(env, host, chost);
206        }
207        (*env)->ReleaseStringUTFChars(env, proto, cproto);
208    }
209    if (urlRef != NULL) {
210        CFArrayRef urlProxyArrayRef = CFNetworkCopyProxiesForURL(urlRef, proxyDicRef);
211        if (urlProxyArrayRef != NULL) {
212            CFIndex count;
213            CFIndex index;
214
215            CFArrayRef expandedProxyArray = createExpandedProxiesArray(urlProxyArrayRef, urlRef);
216            CFRelease(urlProxyArrayRef);
217
218            if (expandedProxyArray == NULL) {
219                CFRelease(urlRef);
220                CFRelease(proxyDicRef);
221                return NULL;
222            }
223
224            count = CFArrayGetCount(expandedProxyArray);
225
226            proxyArray = (*env)->NewObjectArray(env, count, proxy_class, NULL);
227            if (proxyArray != NULL || (*env)->ExceptionCheck(env)) {
228                /* Iterate over the expanded array of proxies */
229                for (index = 0; index < count ; index++) {
230                    CFDictionaryRef currentProxy;
231                    CFStringRef proxyType;
232                    jobject proxy = NULL;
233
234                    currentProxy = (CFDictionaryRef) CFArrayGetValueAtIndex(expandedProxyArray,
235                                                                            index);
236                    proxyType = (CFStringRef) CFDictionaryGetValue(currentProxy, kCFProxyTypeKey);
237                    if (CFEqual(proxyType, kCFProxyTypeNone)) {
238                        /* This entry states no proxy, therefore just add a NO_PROXY object. */
239                        proxy = (*env)->GetStaticObjectField(env, proxy_class, pr_no_proxyID);
240                    } else {
241                        /*
242                         * Create a proxy object for this entry.
243                         * Differentiate between SOCKS and HTTP type.
244                         */
245                        jfieldID typeID = ptype_httpID;
246                        if (CFEqual(proxyType, kCFProxyTypeSOCKS)) {
247                            typeID = ptype_socksID;
248                        }
249                        CFNumberRef portNumberRef = (CFNumberRef)CFDictionaryGetValue(currentProxy,
250                                                    (const void*)kCFProxyPortNumberKey);
251                        if (portNumberRef  != NULL) {
252                            int port = 0;
253                            if (CFNumberGetValue(portNumberRef, kCFNumberSInt32Type, &port)) {
254                                CFStringRef hostNameRef = (CFStringRef)CFDictionaryGetValue(
255                                              currentProxy, (const void*)kCFProxyHostNameKey);
256                                if (hostNameRef != NULL) {
257                                    char hostNameBuffer[BUFFER_SIZE];
258                                    if (CFStringGetCString(hostNameRef, hostNameBuffer,
259                                                           BUFFER_SIZE, kCFStringEncodingUTF8)) {
260                                        proxy = createProxy(env, typeID, &hostNameBuffer[0], port);
261                                    }
262                                }
263                            }
264                        }
265                    }
266                    if (proxy == NULL || (*env)->ExceptionCheck(env)) {
267                        proxyArray = NULL;
268                        break;
269                    }
270                    (*env)->SetObjectArrayElement(env, proxyArray, index, proxy);
271                    if ((*env)->ExceptionCheck(env)) {
272                        proxyArray = NULL;
273                        break;
274                    }
275                }
276            }
277            CFRelease(expandedProxyArray);
278        }
279        CFRelease(urlRef);
280    }
281    CFRelease(proxyDicRef);
282
283    return proxyArray;
284}
285