diff options
-rwxr-xr-x | base/android/jni_generator/jni_generator.py | 1 | ||||
-rw-r--r-- | net/android/java/org/chromium/net/ProxyChangeListener.java | 91 | ||||
-rw-r--r-- | net/android/net_jni_registrar.cc | 4 | ||||
-rwxr-xr-x | net/android/tools/proxy_test_cases.py | 343 | ||||
-rw-r--r-- | net/net.gyp | 7 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_android.cc | 334 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_android.h | 81 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_android_unittest.cc | 353 | ||||
-rw-r--r-- | net/proxy/proxy_service.cc | 6 |
9 files changed, 1217 insertions, 3 deletions
diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py index bf8b971..a444fdd 100755 --- a/base/android/jni_generator/jni_generator.py +++ b/base/android/jni_generator/jni_generator.py @@ -186,7 +186,6 @@ def JavaParamToJni(param): 'Lorg/chromium/media/MediaPlayerListener', 'Lorg/chromium/net/NetworkChangeNotifier', 'Lorg/chromium/net/ProxyChangeListener', - 'Lorg/chromium/net/NetworkChangeNotifier', ] if param == 'byte[][]': return '[[B' diff --git a/net/android/java/org/chromium/net/ProxyChangeListener.java b/net/android/java/org/chromium/net/ProxyChangeListener.java new file mode 100644 index 0000000..fb07a35 --- /dev/null +++ b/net/android/java/org/chromium/net/ProxyChangeListener.java @@ -0,0 +1,91 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.net; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Proxy; +import org.chromium.base.CalledByNative; + +// This class partners with native ProxyConfigServiceAndroid to listen for +// proxy change notifications from Android. +class ProxyChangeListener { + private static final String TAG = "ProxyChangeListener"; + + private int mNativePtr; + private Context mContext; + private ProxyReceiver mProxyReceiver; + + private ProxyChangeListener(Context context) { + mContext = context; + } + + @CalledByNative + static public ProxyChangeListener create(Context context) { + return new ProxyChangeListener(context); + } + + @CalledByNative + static public String getProperty(String property) { + return System.getProperty(property); + } + + @CalledByNative + public void start(int nativePtr) { + assert mNativePtr == 0; + mNativePtr = nativePtr; + registerReceiver(); + } + + @CalledByNative + public void stop() { + mNativePtr = 0; + unregisterReceiver(); + } + + private class ProxyReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Proxy.PROXY_CHANGE_ACTION)) { + proxySettingsChanged(); + } + } + } + + private void proxySettingsChanged() { + if (mNativePtr == 0) + return; + + // Note that this code currently runs on a MESSAGE_LOOP_UI thread, but + // the C++ code must run the callbacks on the network thread. + nativeProxySettingsChanged(mNativePtr); + } + + private void registerReceiver() { + if (mProxyReceiver != null) { + return; + } + IntentFilter filter = new IntentFilter(); + filter.addAction(Proxy.PROXY_CHANGE_ACTION); + mProxyReceiver = new ProxyReceiver(); + mContext.getApplicationContext().registerReceiver(mProxyReceiver, filter); + } + + private void unregisterReceiver() { + if (mProxyReceiver == null) { + return; + } + mContext.unregisterReceiver(mProxyReceiver); + mProxyReceiver = null; + } + + /** + * See net/proxy/proxy_config_service_android.cc + */ + private native void nativeProxySettingsChanged( + int nativePtr /* net::ProxyConfigServiceAndroid::JNIDelegate */); +} diff --git a/net/android/net_jni_registrar.cc b/net/android/net_jni_registrar.cc index 951b1fb..b475cac3 100644 --- a/net/android/net_jni_registrar.cc +++ b/net/android/net_jni_registrar.cc @@ -7,8 +7,9 @@ #include "base/basictypes.h" #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" -#include "net/android/network_library.h" #include "net/android/network_change_notifier_android.h" +#include "net/android/network_library.h" +#include "net/proxy/proxy_config_service_android.h" namespace net { namespace android { @@ -16,6 +17,7 @@ namespace android { static base::android::RegistrationMethod kNetRegisteredMethods[] = { { "AndroidNetworkLibrary", net::android::RegisterNetworkLibrary }, { "NetworkChangeNotifier", net::android::NetworkChangeNotifier::Register }, + { "ProxyConfigService", net::ProxyConfigServiceAndroid::Register }, }; bool RegisterJni(JNIEnv* env) { diff --git a/net/android/tools/proxy_test_cases.py b/net/android/tools/proxy_test_cases.py new file mode 100755 index 0000000..0950902 --- /dev/null +++ b/net/android/tools/proxy_test_cases.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generator script for proxy tests. + +See AndroidProxySelectorTest.java +and net/proxy/proxy_config_service_android_unittest.cc + +To generate C++, run this script without arguments. +To generate Java, run this script with -j argument. + +Note that this generator is not run as part of the build process because +we are assuming that these test cases will not change often. +""" + +import optparse + +test_cases = [ + { + "name": "NoProxy", + "description" : "Test direct mapping when no proxy defined.", + "properties" : { + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndPort", + "description" : "Test http.proxyHost and http.proxyPort works.", + "properties" : { + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostOnly", + "description" : "We should get the default port (80) for proxied hosts.", + "properties" : { + "http.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:80", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyPortOnly", + "description" : + "http.proxyPort only should not result in any hosts being proxied.", + "properties" : { + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT" + } + }, + { + "name": "HttpNonProxyHosts1", + "description" : "Test that HTTP non proxy hosts are mapped correctly", + "properties" : { + "http.nonProxyHosts" : "slashdot.org", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "http://slashdot.org/" : "DIRECT", + } + }, + { + "name": "HttpNonProxyHosts2", + "description" : "Test that | pattern works.", + "properties" : { + "http.nonProxyHosts" : "slashdot.org|freecode.net", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY httpproxy.com:8080", + "http://slashdot.org/" : "DIRECT", + "http://freecode.net/" : "DIRECT", + } + }, + { + "name": "HttpNonProxyHosts3", + "description" : "Test that * pattern works.", + "properties" : { + "http.nonProxyHosts" : "*example.com", + "http.proxyHost" : "httpproxy.com", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "http://www.example.com/" : "DIRECT", + "http://slashdot.org/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "FtpNonProxyHosts", + "description" : "Test that FTP non proxy hosts are mapped correctly", + "properties" : { + "ftp.nonProxyHosts" : "slashdot.org", + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "FtpProxyHostAndPort", + "description" : "Test ftp.proxyHost and ftp.proxyPort works.", + "properties" : { + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "ftp://example.com/" : "PROXY httpproxy.com:8080", + "http://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "FtpProxyHostOnly", + "description" : "Test ftp.proxyHost and default port.", + "properties" : { + "ftp.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "ftp://example.com/" : "PROXY httpproxy.com:80", + "http://example.com/" : "DIRECT", + "https://example.com/" : "DIRECT", + } + }, + { + "name": "HttpsProxyHostAndPort", + "description" : "Test https.proxyHost and https.proxyPort works.", + "properties" : { + "https.proxyHost" : "httpproxy.com", + "https.proxyPort" : "8080", + }, + "mappings" : { + "https://example.com/" : "HTTPS httpproxy.com:8080", + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpsProxyHostOnly", + "description" : "Test https.proxyHost and default port.", + "properties" : { + "https.proxyHost" : "httpproxy.com", + }, + "mappings" : { + "https://example.com/" : "HTTPS httpproxy.com:443", + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostIPv6", + "description" : "Test IPv6 https.proxyHost and default port.", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + }, + "mappings" : { + "http://example.com/" : "PROXY [a:b:c::d:1]:80", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndPortIPv6", + "description" : "Test IPv6 http.proxyHost and http.proxyPort works.", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + "http.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY [a:b:c::d:1]:8080", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "HttpProxyHostAndInvalidPort", + "description" : "Test invalid http.proxyPort does not crash.", + "properties" : { + "http.proxyHost" : "a:b:c::d:1", + "http.proxyPort" : "65536", + }, + "mappings" : { + "http://example.com/" : "DIRECT", + "ftp://example.com/" : "DIRECT", + } + }, + { + "name": "DefaultProxyExplictPort", + "description" : + "Default http proxy is used if a scheme-specific one is not found.", + "properties" : { + "proxyHost" : "defaultproxy.com", + "proxyPort" : "8080", + "ftp.proxyHost" : "httpproxy.com", + "ftp.proxyPort" : "8080", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:8080", + "https://example.com/" : "HTTPS defaultproxy.com:8080", + "ftp://example.com/" : "PROXY httpproxy.com:8080", + } + }, + { + "name": "DefaultProxyDefaultPort", + "description" : "Check that the default proxy port is as expected.", + "properties" : { + "proxyHost" : "defaultproxy.com", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + "https://example.com/" : "HTTPS defaultproxy.com:443", + } + }, + { + "name": "FallbackToSocks", + "description" : "SOCKS proxy is used if scheme-specific one is not found.", + "properties" : { + "http.proxyHost" : "defaultproxy.com", + "socksProxyHost" : "socksproxy.com" + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + "https://example.com/" : "SOCKS5 socksproxy.com:1080", + "ftp://example.com" : "SOCKS5 socksproxy.com:1080", + } + }, + { + "name": "SocksExplicitPort", + "description" : "SOCKS proxy port is used if specified", + "properties" : { + "socksProxyHost" : "socksproxy.com", + "socksProxyPort" : "9000", + }, + "mappings" : { + "http://example.com/" : "SOCKS5 socksproxy.com:9000", + } + }, + { + "name": "HttpProxySupercedesSocks", + "description" : "SOCKS proxy is ignored if default HTTP proxy defined.", + "properties" : { + "proxyHost" : "defaultproxy.com", + "socksProxyHost" : "socksproxy.com", + "socksProxyPort" : "9000", + }, + "mappings" : { + "http://example.com/" : "PROXY defaultproxy.com:80", + } + }, +] + +class GenerateCPlusPlus: + """Generate C++ test cases""" + + def Generate(self): + for test_case in test_cases: + print ("TEST_F(ProxyConfigServiceAndroidTest, %s) {" % test_case["name"]) + if "description" in test_case: + self._GenerateDescription(test_case["description"]); + self._GenerateConfiguration(test_case["properties"]) + self._GenerateMappings(test_case["mappings"]) + print "}" + print "" + + def _GenerateDescription(self, description): + print " // %s" % description + + def _GenerateConfiguration(self, properties): + for key in sorted(properties.iterkeys()): + print " AddProperty(\"%s\", \"%s\");" % (key, properties[key]) + print " ProxySettingsChanged();" + + def _GenerateMappings(self, mappings): + for url in sorted(mappings.iterkeys()): + print " TestMapping(\"%s\", \"%s\");" % (url, mappings[url]) + + +class GenerateJava: + """Generate Java test cases""" + + def Generate(self): + for test_case in test_cases: + if "description" in test_case: + self._GenerateDescription(test_case["description"]); + print " @SmallTest" + print " public void test%s() throws Exception {" % test_case["name"] + self._GenerateConfiguration(test_case["properties"]) + self._GenerateMappings(test_case["mappings"]) + print " }" + print "" + + def _GenerateDescription(self, description): + print " /**" + print " * %s" % description + print " *" + print " * @throws Exception" + print " */" + + def _GenerateConfiguration(self, properties): + for key in sorted(properties.iterkeys()): + print " System.setProperty(\"%s\", \"%s\");" % ( + key, properties[key]) + + def _GenerateMappings(self, mappings): + for url in sorted(mappings.iterkeys()): + print " checkMapping(\"%s\", \"%s\");" % (url, mappings[url]) + + +def main(): + parser = optparse.OptionParser() + parser.add_option("-j", "--java", + action="store_true", dest="java"); + (options, args) = parser.parse_args(); + if options.java: + generator = GenerateJava() + else: + generator = GenerateCPlusPlus() + generator.Generate() + +if __name__ == '__main__': + main() diff --git a/net/net.gyp b/net/net.gyp index d813c78..4066148 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -553,6 +553,8 @@ 'proxy/proxy_config.cc', 'proxy/proxy_config.h', 'proxy/proxy_config_service.h', + 'proxy/proxy_config_service_android.cc', + 'proxy/proxy_config_service_android.h', 'proxy/proxy_config_service_fixed.cc', 'proxy/proxy_config_service_fixed.h', 'proxy/proxy_config_service_linux.cc', @@ -1174,8 +1176,8 @@ 'http/mock_gssapi_library_posix.h', 'http/mock_http_cache.cc', 'http/mock_http_cache.h', - 'http/mock_sspi_library_win.h', 'http/mock_sspi_library_win.cc', + 'http/mock_sspi_library_win.h', 'http/url_security_manager_unittest.cc', 'proxy/dhcp_proxy_script_adapter_fetcher_win_unittest.cc', 'proxy/dhcp_proxy_script_fetcher_factory_unittest.cc', @@ -1183,6 +1185,7 @@ 'proxy/multi_threaded_proxy_resolver_unittest.cc', 'proxy/network_delegate_error_observer_unittest.cc', 'proxy/proxy_bypass_rules_unittest.cc', + 'proxy/proxy_config_service_android_unittest.cc', 'proxy/proxy_config_service_linux_unittest.cc', 'proxy/proxy_config_service_win_unittest.cc', 'proxy/proxy_config_unittest.cc', @@ -1830,10 +1833,12 @@ 'java_sources': [ 'android/java/org/chromium/net/AndroidNetworkLibrary.java', 'android/java/org/chromium/net/NetworkChangeNotifier.java', + 'android/java/org/chromium/net/ProxyChangeListener.java', ], 'jni_headers': [ '<(SHARED_INTERMEDIATE_DIR)/net/jni/android_network_library_jni.h', '<(SHARED_INTERMEDIATE_DIR)/net/jni/network_change_notifier_jni.h', + '<(SHARED_INTERMEDIATE_DIR)/net/jni/proxy_change_listener_jni.h', ], }, 'includes': [ '../build/jni_generator.gypi' ], diff --git a/net/proxy/proxy_config_service_android.cc b/net/proxy/proxy_config_service_android.cc new file mode 100644 index 0000000..0bb75b8 --- /dev/null +++ b/net/proxy/proxy_config_service_android.cc @@ -0,0 +1,334 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/proxy/proxy_config_service_android.h" + +#include <sys/system_properties.h> + +#include "base/android/jni_string.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "base/sequenced_task_runner.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "googleurl/src/url_parse.h" +#include "jni/proxy_change_listener_jni.h" +#include "net/base/host_port_pair.h" +#include "net/proxy/proxy_config.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertUTF8ToJavaString; +using base::android::ConvertJavaStringToUTF8; +using base::android::CheckException; +using base::android::ClearException; +using base::android::GetMethodID; +using base::android::ScopedJavaGlobalRef; + +namespace net { + +namespace { + +typedef ProxyConfigServiceAndroid::GetPropertyCallback GetPropertyCallback; + +// Returns whether the provided string was successfully converted to a port. +bool ConvertStringToPort(const std::string& port, int* output) { + url_parse::Component component(0, port.size()); + int result = url_parse::ParsePort(port.c_str(), component); + if (result == url_parse::PORT_INVALID || + result == url_parse::PORT_UNSPECIFIED) + return false; + *output = result; + return true; +} + +ProxyServer ConstructProxyServer(ProxyServer::Scheme scheme, + const std::string& proxy_host, + const std::string& proxy_port) { + DCHECK(!proxy_host.empty()); + int port_as_int = 0; + if (proxy_port.empty()) + port_as_int = ProxyServer::GetDefaultPortForScheme(scheme); + else if (!ConvertStringToPort(proxy_port, &port_as_int)) + return ProxyServer(); + DCHECK(port_as_int > 0); + return ProxyServer( + scheme, + HostPortPair(proxy_host, static_cast<uint16>(port_as_int))); +} + +ProxyServer LookupProxy(const std::string& prefix, + const GetPropertyCallback& get_property, + ProxyServer::Scheme scheme) { + DCHECK(!prefix.empty()); + std::string proxy_host = get_property.Run(prefix + ".proxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run(prefix + ".proxyPort"); + return ConstructProxyServer(scheme, proxy_host, proxy_port); + } + // Fall back to default proxy, if any. + proxy_host = get_property.Run("proxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run("proxyPort"); + return ConstructProxyServer(scheme, proxy_host, proxy_port); + } + return ProxyServer(); +} + +ProxyServer LookupSocksProxy(const GetPropertyCallback& get_property) { + std::string proxy_host = get_property.Run("socksProxyHost"); + if (!proxy_host.empty()) { + std::string proxy_port = get_property.Run("socksProxyPort"); + return ConstructProxyServer(ProxyServer::SCHEME_SOCKS5, proxy_host, + proxy_port); + } + return ProxyServer(); +} + +void AddBypassRules(const std::string& scheme, + const GetPropertyCallback& get_property, + ProxyBypassRules* bypass_rules) { + // The format of a hostname pattern is a list of hostnames that are separated + // by | and that use * as a wildcard. For example, setting the + // http.nonProxyHosts property to *.android.com|*.kernel.org will cause + // requests to http://developer.android.com to be made without a proxy. + std::string non_proxy_hosts = + get_property.Run(scheme + ".nonProxyHosts"); + if (non_proxy_hosts.empty()) + return; + StringTokenizer tokenizer(non_proxy_hosts, "|"); + while (tokenizer.GetNext()) { + std::string token = tokenizer.token(); + std::string pattern; + TrimWhitespaceASCII(token, TRIM_ALL, &pattern); + if (pattern.empty()) + continue; + // '?' is not one of the specified pattern characters above. + DCHECK_EQ(std::string::npos, pattern.find('?')); + bypass_rules->AddRuleForHostname(scheme, pattern, -1); + } +} + +// Returns true if a valid proxy was found. +bool GetProxyRules(const GetPropertyCallback& get_property, + ProxyConfig::ProxyRules* rules) { + // See libcore/luni/src/main/java/java/net/ProxySelectorImpl.java for the + // equivalent Android implementation. + rules->type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME; + rules->proxy_for_http = LookupProxy("http", get_property, + ProxyServer::SCHEME_HTTP); + rules->proxy_for_https = LookupProxy("https", get_property, + ProxyServer::SCHEME_HTTPS); + rules->proxy_for_ftp = LookupProxy("ftp", get_property, + ProxyServer::SCHEME_HTTP); + rules->fallback_proxy = LookupSocksProxy(get_property); + rules->bypass_rules.Clear(); + AddBypassRules("ftp", get_property, &rules->bypass_rules); + AddBypassRules("http", get_property, &rules->bypass_rules); + AddBypassRules("https", get_property, &rules->bypass_rules); + return rules->proxy_for_http.is_valid() || + rules->proxy_for_https.is_valid() || + rules->proxy_for_ftp.is_valid() || + rules->fallback_proxy.is_valid(); +}; + +void GetLatestProxyConfigInternal(const GetPropertyCallback& get_property, + ProxyConfig* config) { + if (!GetProxyRules(get_property, &config->proxy_rules())) + *config = ProxyConfig::CreateDirect(); +} + +std::string GetJavaProperty(const std::string& property) { + // Use Java System.getProperty to get configuration information. + // TODO(pliard): Conversion to/from UTF8 ok here? + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, property); + ScopedJavaLocalRef<jstring> result = + Java_ProxyChangeListener_getProperty(env, str.obj()); + return result.is_null() ? + std::string() : ConvertJavaStringToUTF8(env, result.obj()); +} + +} // namespace + +class ProxyConfigServiceAndroid::Delegate + : public base::RefCountedThreadSafe<Delegate> { + public: + Delegate(base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner, + const GetPropertyCallback& get_property_callback) + : ALLOW_THIS_IN_INITIALIZER_LIST(jni_delegate_(this)), + network_task_runner_(network_task_runner), + jni_task_runner_(jni_task_runner), + get_property_callback_(get_property_callback) { + } + + void SetupJNI() { + DCHECK(OnJNIThread()); + JNIEnv* env = AttachCurrentThread(); + if (java_proxy_change_listener_.is_null()) { + java_proxy_change_listener_.Reset( + Java_ProxyChangeListener_create( + env, base::android::GetApplicationContext())); + CHECK(!java_proxy_change_listener_.is_null()); + } + Java_ProxyChangeListener_start( + env, + java_proxy_change_listener_.obj(), + reinterpret_cast<jint>(&jni_delegate_)); + } + + void FetchInitialConfig() { + DCHECK(OnJNIThread()); + ProxyConfig proxy_config; + GetLatestProxyConfigInternal(get_property_callback_, &proxy_config); + network_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Delegate::SetNewConfigOnNetworkThread, this, proxy_config)); + } + + void Shutdown() { + if (OnJNIThread()) { + ShutdownOnJNIThread(); + } else { + jni_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Delegate::ShutdownOnJNIThread, this)); + } + } + + // Called only on the network thread. + void AddObserver(Observer* observer) { + DCHECK(OnNetworkThread()); + observers_.AddObserver(observer); + } + + void RemoveObserver(Observer* observer) { + DCHECK(OnNetworkThread()); + observers_.RemoveObserver(observer); + } + + ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) { + DCHECK(OnNetworkThread()); + if (!config) + return ProxyConfigService::CONFIG_UNSET; + *config = proxy_config_; + return ProxyConfigService::CONFIG_VALID; + } + + // Called on the JNI thread. + void ProxySettingsChanged() { + DCHECK(OnJNIThread()); + ProxyConfig proxy_config; + GetLatestProxyConfigInternal(get_property_callback_, &proxy_config); + network_task_runner_->PostTask( + FROM_HERE, + base::Bind( + &Delegate::SetNewConfigOnNetworkThread, this, proxy_config)); + } + + private: + friend class base::RefCountedThreadSafe<Delegate>; + + class JNIDelegateImpl : public ProxyConfigServiceAndroid::JNIDelegate { + public: + explicit JNIDelegateImpl(Delegate* delegate) : delegate_(delegate) {} + + // ProxyConfigServiceAndroid::JNIDelegate overrides. + virtual void ProxySettingsChanged(JNIEnv*, jobject) OVERRIDE { + delegate_->ProxySettingsChanged(); + } + + private: + Delegate* const delegate_; + }; + + virtual ~Delegate() {} + + void ShutdownOnJNIThread() { + if (java_proxy_change_listener_.is_null()) + return; + JNIEnv* env = AttachCurrentThread(); + Java_ProxyChangeListener_stop(env, java_proxy_change_listener_.obj()); + } + + // Called on the network thread. + void SetNewConfigOnNetworkThread(const ProxyConfig& proxy_config) { + DCHECK(OnNetworkThread()); + proxy_config_ = proxy_config; + FOR_EACH_OBSERVER(Observer, observers_, + OnProxyConfigChanged(proxy_config, + ProxyConfigService::CONFIG_VALID)); + } + + bool OnJNIThread() const { + return jni_task_runner_->RunsTasksOnCurrentThread(); + } + + bool OnNetworkThread() const { + return network_task_runner_->RunsTasksOnCurrentThread(); + } + + ScopedJavaGlobalRef<jobject> java_proxy_change_listener_; + + JNIDelegateImpl jni_delegate_; + ObserverList<Observer> observers_; + scoped_refptr<base::SequencedTaskRunner> network_task_runner_; + scoped_refptr<base::SequencedTaskRunner> jni_task_runner_; + GetPropertyCallback get_property_callback_; + ProxyConfig proxy_config_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); +}; + +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( + base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner) + : delegate_(new Delegate( + network_task_runner, jni_task_runner, base::Bind(&GetJavaProperty))) { + delegate_->SetupJNI(); + delegate_->FetchInitialConfig(); +} + +ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() { + delegate_->Shutdown(); +} + +// static +bool ProxyConfigServiceAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +void ProxyConfigServiceAndroid::AddObserver(Observer* observer) { + delegate_->AddObserver(observer); +} + +void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) { + delegate_->RemoveObserver(observer); +} + +ProxyConfigService::ConfigAvailability +ProxyConfigServiceAndroid::GetLatestProxyConfig(ProxyConfig* config) { + return delegate_->GetLatestProxyConfig(config); +} + +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( + base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner, + GetPropertyCallback get_property_callback) + : delegate_(new Delegate( + network_task_runner, jni_task_runner, get_property_callback)) { + delegate_->FetchInitialConfig(); +} + +void ProxyConfigServiceAndroid::ProxySettingsChanged() { + delegate_->ProxySettingsChanged(); +} + +} // namespace net diff --git a/net/proxy/proxy_config_service_android.h b/net/proxy/proxy_config_service_android.h new file mode 100644 index 0000000..30ebd11 --- /dev/null +++ b/net/proxy/proxy_config_service_android.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_ +#define NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_ +#pragma once + +#include <string> + +#include "base/android/jni_android.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "net/base/net_export.h" +#include "net/proxy/proxy_config_service.h" + +namespace base { +class SequencedTaskRunner; +} + +namespace net { + +class ProxyConfig; + +class NET_EXPORT ProxyConfigServiceAndroid : public ProxyConfigService { + public: + // Callback that returns the value of the property identified by the provided + // key. If it was not found, an empty string is returned. Note that this + // interface does not let you distinguish an empty property from a + // non-existing property. This callback is invoked on the JNI thread. + typedef base::Callback<std::string (const std::string& property)> + GetPropertyCallback; + + // Separate class whose instance is owned by the Delegate class implemented in + // the .cc file. + class JNIDelegate { + public: + virtual ~JNIDelegate() {} + + // Called from Java (on JNI thread) to signal that the proxy settings have + // changed. + virtual void ProxySettingsChanged(JNIEnv*, jobject) = 0; + }; + + ProxyConfigServiceAndroid(base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner); + + virtual ~ProxyConfigServiceAndroid(); + + // Register JNI bindings. + static bool Register(JNIEnv* env); + + // ProxyConfigService: + // Called only on the network thread. + virtual void AddObserver(Observer* observer) OVERRIDE; + virtual void RemoveObserver(Observer* observer) OVERRIDE; + virtual ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) OVERRIDE; + + private: + friend class ProxyConfigServiceAndroidTestBase; + class Delegate; + + + // For tests. + ProxyConfigServiceAndroid(base::SequencedTaskRunner* network_task_runner, + base::SequencedTaskRunner* jni_task_runner, + GetPropertyCallback get_property_callback); + + // For tests. + void ProxySettingsChanged(); + + scoped_refptr<Delegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(ProxyConfigServiceAndroid); +}; + +} // namespace net + +#endif // NET_PROXY_PROXY_CONFIG_SERVICE_ANDROID_H_ diff --git a/net/proxy/proxy_config_service_android_unittest.cc b/net/proxy/proxy_config_service_android_unittest.cc new file mode 100644 index 0000000..84449f0 --- /dev/null +++ b/net/proxy/proxy_config_service_android_unittest.cc @@ -0,0 +1,353 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <map> +#include <string> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "net/proxy/proxy_config.h" +#include "net/proxy/proxy_config_service_android.h" +#include "net/proxy/proxy_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class TestObserver : public ProxyConfigService::Observer { + public: + TestObserver() : availability_(ProxyConfigService::CONFIG_UNSET) {} + + // ProxyConfigService::Observer: + virtual void OnProxyConfigChanged( + const ProxyConfig& config, + ProxyConfigService::ConfigAvailability availability) OVERRIDE { + config_ = config; + availability_ = availability; + } + + ProxyConfigService::ConfigAvailability availability() const { + return availability_; + } + + const ProxyConfig& config() const { + return config_; + } + + private: + ProxyConfig config_; + ProxyConfigService::ConfigAvailability availability_; +}; + +} // namespace + +typedef std::map<std::string, std::string> StringMap; + +class ProxyConfigServiceAndroidTestBase : public testing::Test { + protected: + // Note that the current thread's message loop is initialized by the test + // suite (see net/base/net_test_suite.cc). + ProxyConfigServiceAndroidTestBase(const StringMap& initial_configuration) + : configuration_(initial_configuration), + message_loop_(MessageLoop::current()), + service_( + message_loop_->message_loop_proxy(), + message_loop_->message_loop_proxy(), + base::Bind(&ProxyConfigServiceAndroidTestBase::GetProperty, + base::Unretained(this))) {} + + virtual ~ProxyConfigServiceAndroidTestBase() {} + + // testing::Test: + virtual void SetUp() OVERRIDE { + message_loop_->RunAllPending(); + service_.AddObserver(&observer_); + } + + virtual void TearDown() OVERRIDE { + service_.RemoveObserver(&observer_); + } + + void ClearConfiguration() { + configuration_.clear(); + } + + void AddProperty(const std::string& key, const std::string& value) { + configuration_[key] = value; + } + + std::string GetProperty(const std::string& key) { + StringMap::const_iterator it = configuration_.find(key); + if (it == configuration_.end()) + return std::string(); + return it->second; + } + + void ProxySettingsChanged() { + service_.ProxySettingsChanged(); + message_loop_->RunAllPending(); + } + + void TestMapping(const std::string& url, const std::string& expected) { + ProxyConfigService::ConfigAvailability availability; + ProxyConfig proxy_config; + availability = service_.GetLatestProxyConfig(&proxy_config); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, availability); + ProxyInfo proxy_info; + proxy_config.proxy_rules().Apply(GURL(url), &proxy_info); + EXPECT_EQ(expected, proxy_info.ToPacString()); + } + + StringMap configuration_; + TestObserver observer_; + MessageLoop* const message_loop_; + ProxyConfigServiceAndroid service_; +}; + +class ProxyConfigServiceAndroidTest : public ProxyConfigServiceAndroidTestBase { + public: + ProxyConfigServiceAndroidTest() + : ProxyConfigServiceAndroidTestBase(StringMap()) {} +}; + +class ProxyConfigServiceAndroidWithInitialConfigTest + : public ProxyConfigServiceAndroidTestBase { + public: + ProxyConfigServiceAndroidWithInitialConfigTest() + : ProxyConfigServiceAndroidTestBase(MakeInitialConfiguration()) {} + + private: + StringMap MakeInitialConfiguration() { + StringMap initial_configuration; + initial_configuration["http.proxyHost"] = "httpproxy.com"; + initial_configuration["http.proxyPort"] = "8080"; + return initial_configuration; + } +}; + +TEST_F(ProxyConfigServiceAndroidTest, TestChangePropertiesNotification) { + // Set up a non-empty configuration + AddProperty("http.proxyHost", "localhost"); + ProxySettingsChanged(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability()); + EXPECT_FALSE(observer_.config().proxy_rules().empty()); + + // Set up an empty configuration + ClearConfiguration(); + ProxySettingsChanged(); + EXPECT_EQ(ProxyConfigService::CONFIG_VALID, observer_.availability()); + EXPECT_TRUE(observer_.config().proxy_rules().empty()); +} + +TEST_F(ProxyConfigServiceAndroidWithInitialConfigTest, TestInitialConfig) { + // Make sure that the initial config is set. + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + + // Override the initial configuration. + ClearConfiguration(); + AddProperty("http.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:80"); +} + +// !! The following test cases are automatically generated from +// !! net/android/tools/proxy_test_cases.py. +// !! Please edit that file instead of editing the test cases below and +// !! update also the corresponding Java unit tests in +// !! AndroidProxySelectorTest.java + +TEST_F(ProxyConfigServiceAndroidTest, NoProxy) { + // Test direct mapping when no proxy defined. + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPort) { + // Test http.proxyHost and http.proxyPort works. + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostOnly) { + // We should get the default port (80) for proxied hosts. + AddProperty("http.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY httpproxy.com:80"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyPortOnly) { + // http.proxyPort only should not result in any hosts being proxied. + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts1) { + // Test that HTTP non proxy hosts are mapped correctly + AddProperty("http.nonProxyHosts", "slashdot.org"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://slashdot.org/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts2) { + // Test that | pattern works. + AddProperty("http.nonProxyHosts", "slashdot.org|freecode.net"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://freecode.net/", "DIRECT"); + TestMapping("http://slashdot.org/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpNonProxyHosts3) { + // Test that * pattern works. + AddProperty("http.nonProxyHosts", "*example.com"); + AddProperty("http.proxyHost", "httpproxy.com"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("http://slashdot.org/", "PROXY httpproxy.com:8080"); + TestMapping("http://www.example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpNonProxyHosts) { + // Test that FTP non proxy hosts are mapped correctly + AddProperty("ftp.nonProxyHosts", "slashdot.org"); + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostAndPort) { + // Test ftp.proxyHost and ftp.proxyPort works. + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FtpProxyHostOnly) { + // Test ftp.proxyHost and default port. + AddProperty("ftp.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:80"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostAndPort) { + // Test https.proxyHost and https.proxyPort works. + AddProperty("https.proxyHost", "httpproxy.com"); + AddProperty("https.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "HTTPS httpproxy.com:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpsProxyHostOnly) { + // Test https.proxyHost and default port. + AddProperty("https.proxyHost", "httpproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); + TestMapping("https://example.com/", "HTTPS httpproxy.com:443"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostIPv6) { + // Test IPv6 https.proxyHost and default port. + AddProperty("http.proxyHost", "a:b:c::d:1"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:80"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndPortIPv6) { + // Test IPv6 http.proxyHost and http.proxyPort works. + AddProperty("http.proxyHost", "a:b:c::d:1"); + AddProperty("http.proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "PROXY [a:b:c::d:1]:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxyHostAndInvalidPort) { + // Test invalid http.proxyPort does not crash. + AddProperty("http.proxyHost", "a:b:c::d:1"); + AddProperty("http.proxyPort", "65536"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "DIRECT"); + TestMapping("http://example.com/", "DIRECT"); +} + +TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyExplictPort) { + // Default http proxy is used if a scheme-specific one is not found. + AddProperty("ftp.proxyHost", "httpproxy.com"); + AddProperty("ftp.proxyPort", "8080"); + AddProperty("proxyHost", "defaultproxy.com"); + AddProperty("proxyPort", "8080"); + ProxySettingsChanged(); + TestMapping("ftp://example.com/", "PROXY httpproxy.com:8080"); + TestMapping("http://example.com/", "PROXY defaultproxy.com:8080"); + TestMapping("https://example.com/", "HTTPS defaultproxy.com:8080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, DefaultProxyDefaultPort) { + // Check that the default proxy port is as expected. + AddProperty("proxyHost", "defaultproxy.com"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); + TestMapping("https://example.com/", "HTTPS defaultproxy.com:443"); +} + +TEST_F(ProxyConfigServiceAndroidTest, FallbackToSocks) { + // SOCKS proxy is used if scheme-specific one is not found. + AddProperty("http.proxyHost", "defaultproxy.com"); + AddProperty("socksProxyHost", "socksproxy.com"); + ProxySettingsChanged(); + TestMapping("ftp://example.com", "SOCKS5 socksproxy.com:1080"); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); + TestMapping("https://example.com/", "SOCKS5 socksproxy.com:1080"); +} + +TEST_F(ProxyConfigServiceAndroidTest, SocksExplicitPort) { + // SOCKS proxy port is used if specified + AddProperty("socksProxyHost", "socksproxy.com"); + AddProperty("socksProxyPort", "9000"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "SOCKS5 socksproxy.com:9000"); +} + +TEST_F(ProxyConfigServiceAndroidTest, HttpProxySupercedesSocks) { + // SOCKS proxy is ignored if default HTTP proxy defined. + AddProperty("proxyHost", "defaultproxy.com"); + AddProperty("socksProxyHost", "socksproxy.com"); + AddProperty("socksProxyPort", "9000"); + ProxySettingsChanged(); + TestMapping("http://example.com/", "PROXY defaultproxy.com:80"); +} + +} // namespace net diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc index 3cd82a5..35ccfbd 100644 --- a/net/proxy/proxy_service.cc +++ b/net/proxy/proxy_service.cc @@ -41,6 +41,8 @@ #include "net/proxy/proxy_resolver_mac.h" #elif defined(OS_LINUX) && !defined(OS_CHROMEOS) #include "net/proxy/proxy_config_service_linux.h" +#elif defined(OS_ANDROID) +#include "net/proxy/proxy_config_service_android.h" #endif using base::TimeDelta; @@ -1453,6 +1455,10 @@ ProxyConfigService* ProxyService::CreateSystemProxyConfigService( static_cast<MessageLoopForIO*>(file_loop)); return linux_config_service; +#elif defined(OS_ANDROID) + return new ProxyConfigServiceAndroid( + io_thread_task_runner, + MessageLoopForUI::current()->message_loop_proxy()); #else LOG(WARNING) << "Failed to choose a system proxy settings fetcher " "for this platform."; |