Chromium Code Reviews| Index: net/proxy/proxy_config_service_android.cc |
| diff --git a/net/proxy/proxy_config_service_android.cc b/net/proxy/proxy_config_service_android.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..36be0c6d8ec8cf7f2458a25146dfecab4b6cf4a6 |
| --- /dev/null |
| +++ b/net/proxy/proxy_config_service_android.cc |
| @@ -0,0 +1,284 @@ |
| +// 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_android.h" |
| +#include "base/android/jni_string.h" |
| +#include "base/android/scoped_java_ref.h" |
| +#include "base/basictypes.h" |
| +#include "base/bind.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/single_thread_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; |
| + |
| +namespace net { |
| + |
| +namespace { |
| + |
| +// 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::FromURI( |
|
Ryan Sleevi
2012/05/19 01:26:31
use the constructor
return ProxyServer(scheme, Ho
Philippe
2012/05/22 15:23:05
Done.
|
| + HostPortPair(proxy_host, static_cast<uint>(port_as_int)).ToString(), |
|
Ryan Sleevi
2012/05/19 01:26:31
uint16
Philippe
2012/05/22 15:23:05
Done.
|
| + scheme); |
| +} |
| + |
| +ProxyServer LookupProxy( |
| + const std::string& prefix, |
| + ProxyConfigServiceAndroid::Delegate* delegate, |
| + ProxyServer::Scheme scheme) { |
| + DCHECK(!prefix.empty()); |
| + std::string proxy_host = delegate->GetProperty(prefix + ".proxyHost"); |
| + if (!proxy_host.empty()) { |
| + std::string proxy_port = delegate->GetProperty(prefix + ".proxyPort"); |
| + return ConstructProxyServer(scheme, proxy_host, proxy_port); |
| + } |
| + // Fall back to default proxy, if any. |
| + proxy_host = delegate->GetProperty("proxyHost"); |
| + if (!proxy_host.empty()) { |
| + std::string proxy_port = delegate->GetProperty("proxyPort"); |
| + return ConstructProxyServer(scheme, proxy_host, proxy_port); |
| + } |
| + return ProxyServer(); |
| +} |
| + |
| +ProxyServer LookupSocksProxy( |
| + ProxyConfigServiceAndroid::Delegate* delegate) { |
| + std::string proxy_host = delegate->GetProperty("socksProxyHost"); |
| + if (!proxy_host.empty()) { |
| + std::string proxy_port = delegate->GetProperty("socksProxyPort"); |
| + return ConstructProxyServer(ProxyServer::SCHEME_SOCKS5, proxy_host, |
| + proxy_port); |
| + } |
| + return ProxyServer(); |
| +} |
| + |
| +void AddBypassRules( |
| + const std::string& scheme, |
| + ProxyConfigServiceAndroid::Delegate* delegate, |
| + 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 = |
| + delegate->GetProperty(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( |
| + ProxyConfigServiceAndroid::Delegate* delegate, |
| + 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", delegate, |
| + ProxyServer::SCHEME_HTTP); |
| + rules->proxy_for_https = LookupProxy("https", delegate, |
| + ProxyServer::SCHEME_HTTPS); |
| + rules->proxy_for_ftp = LookupProxy("ftp", delegate, ProxyServer::SCHEME_HTTP); |
| + rules->fallback_proxy = LookupSocksProxy(delegate); |
| + rules->bypass_rules.Clear(); |
| + AddBypassRules("ftp", delegate, &rules->bypass_rules); |
| + AddBypassRules("http", delegate, &rules->bypass_rules); |
| + AddBypassRules("https", delegate, &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( |
| + ProxyConfigServiceAndroid::Delegate* delegate, |
| + ProxyConfig* config) { |
| + if (!GetProxyRules(delegate, &config->proxy_rules())) |
| + *config = ProxyConfig::CreateDirect(); |
| +} |
| + |
| +// This class implements the link to Android's proxy broadcast mechanism. |
| +class DelegateImpl : public ProxyConfigServiceAndroid::Delegate { |
| + public: |
| + virtual void Start(ProxyConfigServiceAndroid* service) OVERRIDE { |
| + JNIEnv* env = AttachCurrentThread(); |
| + if (java_proxy_change_listener_android_.is_null()) { |
| + java_proxy_change_listener_android_.Reset( |
| + Java_ProxyChangeListener_create( |
| + env, base::android::GetApplicationContext())); |
| + CHECK(!java_proxy_change_listener_android_.is_null()); |
| + } |
| + Java_ProxyChangeListener_start( |
| + env, |
| + java_proxy_change_listener_android_.obj(), |
| + reinterpret_cast<jint>(service)); |
| + } |
| + |
| + virtual void Stop() OVERRIDE { |
| + // The java object will be NULL if Stop() was already called (e.g. after a |
| + // Start()), or if Start() was never called. Stop() is called from the |
| + // destructor of ProxyConfigServiceAndroid. |
| + if (java_proxy_change_listener_android_.is_null()) |
| + return; |
| + JNIEnv* env = AttachCurrentThread(); |
| + Java_ProxyChangeListener_stop(env, |
| + java_proxy_change_listener_android_.obj()); |
| + } |
| + |
| + virtual std::string GetProperty(const std::string& property) OVERRIDE { |
| + // 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() |
|
Ryan Sleevi
2012/05/19 01:26:31
the ? should be on the previous line
See http://w
Philippe
2012/05/22 15:23:05
Done.
|
| + : ConvertJavaStringToUTF8(env, result.obj()); |
| + } |
| + |
| + private: |
| + base::android::ScopedJavaGlobalRef<jobject> |
| + java_proxy_change_listener_android_; |
| +}; |
| + |
| +} // namespace |
| + |
| +class ProxyConfigServiceAndroid::SharedState |
| + : public base::RefCountedThreadSafe<SharedState> { |
| + public: |
| + explicit SharedState(Delegate* delegate) : delegate_(delegate) { |
| + DCHECK(delegate); |
| + } |
| + |
| + // Non-reassignable pointer thus safely readable by any thread. |
| + const scoped_ptr<Delegate> delegate_; |
| + // Note that this is only accessed from the observer thread. |
| + ObserverList<Observer> observers_; |
| + |
| + private: |
| + friend class base::RefCountedThreadSafe<SharedState>; |
| + ~SharedState() {} |
| + |
| + DISALLOW_COPY_AND_ASSIGN(SharedState); |
| +}; |
| + |
| +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( |
| + base::SingleThreadTaskRunner* observer_runner) |
| + : shared_state_(new SharedState(new DelegateImpl())), |
| + observer_runner_(observer_runner) { |
| + DCHECK(observer_runner_.get()); |
| +} |
| + |
| +ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() { |
| + shared_state_->delegate_->Stop(); |
| +} |
| + |
| +// static |
| +bool ProxyConfigServiceAndroid::Register(JNIEnv* env) { |
| + return RegisterNativesImpl(env); |
| +} |
| + |
| +void ProxyConfigServiceAndroid::AddObserver(Observer* observer) { |
| + DCHECK(OnObserverThread()); |
| + if (shared_state_->observers_.size() == 0) |
| + shared_state_->delegate_->Start(this); |
| + // Note that any callbacks from the delegate will result in a scheduled task |
| + // on this same thread, so there is no race between delegate_->Start() and |
| + // AddObserver(). |
| + shared_state_->observers_.AddObserver(observer); |
| +} |
| + |
| +void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) { |
| + DCHECK(OnObserverThread()); |
| + shared_state_->observers_.RemoveObserver(observer); |
| + if (shared_state_->observers_.size() == 0) |
| + shared_state_->delegate_->Stop(); |
| +} |
| + |
| +ProxyConfigService::ConfigAvailability |
| +ProxyConfigServiceAndroid::GetLatestProxyConfig(ProxyConfig* config) { |
| + if (!config) |
| + return ProxyConfigService::CONFIG_UNSET; |
| + GetLatestProxyConfigInternal(shared_state_->delegate_.get(), config); |
| + return ProxyConfigService::CONFIG_VALID; |
| +} |
| + |
| +void ProxyConfigServiceAndroid::ProxySettingsChangedInternal() { |
| + observer_runner_->PostTask( |
| + FROM_HERE, base::Bind(&ProxySettingsChangedCallback, shared_state_)); |
| +} |
| + |
| +ProxyConfigServiceAndroid::ProxyConfigServiceAndroid( |
| + base::SingleThreadTaskRunner* observer_runner, |
| + ProxyConfigServiceAndroid::Delegate* delegate) |
| + : shared_state_(new SharedState(delegate)), |
| + observer_runner_(observer_runner) { |
| + DCHECK(observer_runner_.get()); |
| +} |
| + |
| +// static |
| +void ProxyConfigServiceAndroid::ProxySettingsChangedCallback( |
| + const scoped_refptr<SharedState>& callback_state) { |
| + // Allow another task to be scheduled (before running observers). |
| + ProxyConfig config; |
| + GetLatestProxyConfigInternal(callback_state->delegate_.get(), &config); |
| + FOR_EACH_OBSERVER(Observer, callback_state->observers_, |
| + OnProxyConfigChanged(config, |
| + ProxyConfigService::CONFIG_VALID)); |
|
Ryan Sleevi
2012/05/19 01:26:31
I don't believe the threading is correct here.
Th
Philippe
2012/05/22 15:23:05
Indeed.
Now I fetch the latest proxy config in Pr
|
| +} |
| + |
| +bool ProxyConfigServiceAndroid::OnObserverThread() const { |
| + return observer_runner_->BelongsToCurrentThread(); |
| +} |
| + |
| +} // namespace net |