Home | History | Annotate | Download | only in proxy
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/proxy/proxy_config_service_android.h"
      6 
      7 #include <sys/system_properties.h>
      8 
      9 #include "base/android/jni_array.h"
     10 #include "base/android/jni_string.h"
     11 #include "base/basictypes.h"
     12 #include "base/bind.h"
     13 #include "base/callback.h"
     14 #include "base/compiler_specific.h"
     15 #include "base/location.h"
     16 #include "base/logging.h"
     17 #include "base/memory/ref_counted.h"
     18 #include "base/observer_list.h"
     19 #include "base/sequenced_task_runner.h"
     20 #include "base/strings/string_tokenizer.h"
     21 #include "base/strings/string_util.h"
     22 #include "base/strings/stringprintf.h"
     23 #include "jni/ProxyChangeListener_jni.h"
     24 #include "net/base/host_port_pair.h"
     25 #include "net/proxy/proxy_config.h"
     26 #include "url/url_parse.h"
     27 
     28 using base::android::AttachCurrentThread;
     29 using base::android::ConvertUTF8ToJavaString;
     30 using base::android::ConvertJavaStringToUTF8;
     31 using base::android::CheckException;
     32 using base::android::ClearException;
     33 using base::android::ScopedJavaGlobalRef;
     34 
     35 namespace net {
     36 
     37 namespace {
     38 
     39 typedef ProxyConfigServiceAndroid::GetPropertyCallback GetPropertyCallback;
     40 
     41 // Returns whether the provided string was successfully converted to a port.
     42 bool ConvertStringToPort(const std::string& port, int* output) {
     43   url::Component component(0, port.size());
     44   int result = url::ParsePort(port.c_str(), component);
     45   if (result == url::PORT_INVALID || result == url::PORT_UNSPECIFIED)
     46     return false;
     47   *output = result;
     48   return true;
     49 }
     50 
     51 ProxyServer ConstructProxyServer(ProxyServer::Scheme scheme,
     52                                  const std::string& proxy_host,
     53                                  const std::string& proxy_port) {
     54   DCHECK(!proxy_host.empty());
     55   int port_as_int = 0;
     56   if (proxy_port.empty())
     57     port_as_int = ProxyServer::GetDefaultPortForScheme(scheme);
     58   else if (!ConvertStringToPort(proxy_port, &port_as_int))
     59     return ProxyServer();
     60   DCHECK(port_as_int > 0);
     61   return ProxyServer(
     62       scheme,
     63       HostPortPair(proxy_host, static_cast<uint16>(port_as_int)));
     64 }
     65 
     66 ProxyServer LookupProxy(const std::string& prefix,
     67                         const GetPropertyCallback& get_property,
     68                         ProxyServer::Scheme scheme) {
     69   DCHECK(!prefix.empty());
     70   std::string proxy_host = get_property.Run(prefix + ".proxyHost");
     71   if (!proxy_host.empty()) {
     72     std::string proxy_port = get_property.Run(prefix + ".proxyPort");
     73     return ConstructProxyServer(scheme, proxy_host, proxy_port);
     74   }
     75   // Fall back to default proxy, if any.
     76   proxy_host = get_property.Run("proxyHost");
     77   if (!proxy_host.empty()) {
     78     std::string proxy_port = get_property.Run("proxyPort");
     79     return ConstructProxyServer(scheme, proxy_host, proxy_port);
     80   }
     81   return ProxyServer();
     82 }
     83 
     84 ProxyServer LookupSocksProxy(const GetPropertyCallback& get_property) {
     85   std::string proxy_host = get_property.Run("socksProxyHost");
     86   if (!proxy_host.empty()) {
     87     std::string proxy_port = get_property.Run("socksProxyPort");
     88     return ConstructProxyServer(ProxyServer::SCHEME_SOCKS5, proxy_host,
     89                                 proxy_port);
     90   }
     91   return ProxyServer();
     92 }
     93 
     94 void AddBypassRules(const std::string& scheme,
     95                     const GetPropertyCallback& get_property,
     96                     ProxyBypassRules* bypass_rules) {
     97   // The format of a hostname pattern is a list of hostnames that are separated
     98   // by | and that use * as a wildcard. For example, setting the
     99   // http.nonProxyHosts property to *.android.com|*.kernel.org will cause
    100   // requests to http://developer.android.com to be made without a proxy.
    101 
    102   // Force localhost to be on the proxy exclusion list;
    103   // otherwise all localhost traffic is routed through
    104   // the proxy which is not desired.
    105   bypass_rules->AddRuleToBypassLocal();
    106 
    107   std::string non_proxy_hosts =
    108       get_property.Run(scheme + ".nonProxyHosts");
    109   if (non_proxy_hosts.empty())
    110     return;
    111   base::StringTokenizer tokenizer(non_proxy_hosts, "|");
    112   while (tokenizer.GetNext()) {
    113     std::string token = tokenizer.token();
    114     std::string pattern;
    115     base::TrimWhitespaceASCII(token, base::TRIM_ALL, &pattern);
    116     if (pattern.empty())
    117       continue;
    118     // '?' is not one of the specified pattern characters above.
    119     DCHECK_EQ(std::string::npos, pattern.find('?'));
    120     bypass_rules->AddRuleForHostname(scheme, pattern, -1);
    121   }
    122 }
    123 
    124 // Returns true if a valid proxy was found.
    125 bool GetProxyRules(const GetPropertyCallback& get_property,
    126                    ProxyConfig::ProxyRules* rules) {
    127   // See libcore/luni/src/main/java/java/net/ProxySelectorImpl.java for the
    128   // mostly equivalent Android implementation.  There is one intentional
    129   // difference: by default Chromium uses the HTTP port (80) for HTTPS
    130   // connections via proxy.  This default is identical on other platforms.
    131   // On the opposite, Java spec suggests to use HTTPS port (443) by default (the
    132   // default value of https.proxyPort).
    133   rules->type = ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME;
    134   rules->proxies_for_http.SetSingleProxyServer(
    135       LookupProxy("http", get_property, ProxyServer::SCHEME_HTTP));
    136   rules->proxies_for_https.SetSingleProxyServer(
    137       LookupProxy("https", get_property, ProxyServer::SCHEME_HTTP));
    138   rules->proxies_for_ftp.SetSingleProxyServer(
    139       LookupProxy("ftp", get_property, ProxyServer::SCHEME_HTTP));
    140   rules->fallback_proxies.SetSingleProxyServer(LookupSocksProxy(get_property));
    141   rules->bypass_rules.Clear();
    142   AddBypassRules("ftp", get_property, &rules->bypass_rules);
    143   AddBypassRules("http", get_property, &rules->bypass_rules);
    144   AddBypassRules("https", get_property, &rules->bypass_rules);
    145   // We know a proxy was found if not all of the proxy lists are empty.
    146   return !(rules->proxies_for_http.IsEmpty() &&
    147       rules->proxies_for_https.IsEmpty() &&
    148       rules->proxies_for_ftp.IsEmpty() &&
    149       rules->fallback_proxies.IsEmpty());
    150 };
    151 
    152 void GetLatestProxyConfigInternal(const GetPropertyCallback& get_property,
    153                                   ProxyConfig* config) {
    154   if (!GetProxyRules(get_property, &config->proxy_rules()))
    155     *config = ProxyConfig::CreateDirect();
    156 }
    157 
    158 std::string GetJavaProperty(const std::string& property) {
    159   // Use Java System.getProperty to get configuration information.
    160   // TODO(pliard): Conversion to/from UTF8 ok here?
    161   JNIEnv* env = AttachCurrentThread();
    162   ScopedJavaLocalRef<jstring> str = ConvertUTF8ToJavaString(env, property);
    163   ScopedJavaLocalRef<jstring> result =
    164       Java_ProxyChangeListener_getProperty(env, str.obj());
    165   return result.is_null() ?
    166       std::string() : ConvertJavaStringToUTF8(env, result.obj());
    167 }
    168 
    169 void CreateStaticProxyConfig(const std::string& host,
    170                              int port,
    171                              const std::string& pac_url,
    172                              const std::vector<std::string>& exclusion_list,
    173                              ProxyConfig* config) {
    174   if (!pac_url.empty()) {
    175     config->set_pac_url(GURL(pac_url));
    176     config->set_pac_mandatory(false);
    177   } else if (port != 0) {
    178     std::string rules = base::StringPrintf("%s:%d", host.c_str(), port);
    179     config->proxy_rules().ParseFromString(rules);
    180     config->proxy_rules().bypass_rules.Clear();
    181 
    182     std::vector<std::string>::const_iterator it;
    183     for (it = exclusion_list.begin(); it != exclusion_list.end(); ++it) {
    184       std::string pattern;
    185       base::TrimWhitespaceASCII(*it, base::TRIM_ALL, &pattern);
    186       if (pattern.empty())
    187           continue;
    188       config->proxy_rules().bypass_rules.AddRuleForHostname("", pattern, -1);
    189     }
    190   } else {
    191     *config = ProxyConfig::CreateDirect();
    192   }
    193 }
    194 
    195 }  // namespace
    196 
    197 class ProxyConfigServiceAndroid::Delegate
    198     : public base::RefCountedThreadSafe<Delegate> {
    199  public:
    200   Delegate(const scoped_refptr<base::SequencedTaskRunner>& network_task_runner,
    201            const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner,
    202            const GetPropertyCallback& get_property_callback)
    203       : jni_delegate_(this),
    204         network_task_runner_(network_task_runner),
    205         jni_task_runner_(jni_task_runner),
    206         get_property_callback_(get_property_callback),
    207         exclude_pac_url_(false) {
    208   }
    209 
    210   void SetupJNI() {
    211     DCHECK(OnJNIThread());
    212     JNIEnv* env = AttachCurrentThread();
    213     if (java_proxy_change_listener_.is_null()) {
    214       java_proxy_change_listener_.Reset(
    215           Java_ProxyChangeListener_create(
    216               env, base::android::GetApplicationContext()));
    217       CHECK(!java_proxy_change_listener_.is_null());
    218     }
    219     Java_ProxyChangeListener_start(
    220         env,
    221         java_proxy_change_listener_.obj(),
    222         reinterpret_cast<intptr_t>(&jni_delegate_));
    223   }
    224 
    225   void FetchInitialConfig() {
    226     DCHECK(OnJNIThread());
    227     ProxyConfig proxy_config;
    228     GetLatestProxyConfigInternal(get_property_callback_, &proxy_config);
    229     network_task_runner_->PostTask(
    230         FROM_HERE,
    231         base::Bind(&Delegate::SetNewConfigOnNetworkThread, this, proxy_config));
    232   }
    233 
    234   void Shutdown() {
    235     if (OnJNIThread()) {
    236       ShutdownOnJNIThread();
    237     } else {
    238       jni_task_runner_->PostTask(
    239           FROM_HERE,
    240           base::Bind(&Delegate::ShutdownOnJNIThread, this));
    241     }
    242   }
    243 
    244   // Called only on the network thread.
    245   void AddObserver(Observer* observer) {
    246     DCHECK(OnNetworkThread());
    247     observers_.AddObserver(observer);
    248   }
    249 
    250   void RemoveObserver(Observer* observer) {
    251     DCHECK(OnNetworkThread());
    252     observers_.RemoveObserver(observer);
    253   }
    254 
    255   ConfigAvailability GetLatestProxyConfig(ProxyConfig* config) {
    256     DCHECK(OnNetworkThread());
    257     if (!config)
    258       return ProxyConfigService::CONFIG_UNSET;
    259     *config = proxy_config_;
    260     return ProxyConfigService::CONFIG_VALID;
    261   }
    262 
    263   // Called on the JNI thread.
    264   void ProxySettingsChanged() {
    265     DCHECK(OnJNIThread());
    266     ProxyConfig proxy_config;
    267     GetLatestProxyConfigInternal(get_property_callback_, &proxy_config);
    268     network_task_runner_->PostTask(
    269         FROM_HERE,
    270         base::Bind(
    271             &Delegate::SetNewConfigOnNetworkThread, this, proxy_config));
    272   }
    273 
    274   // Called on the JNI thread.
    275   void ProxySettingsChangedTo(const std::string& host,
    276                               int port,
    277                               const std::string& pac_url,
    278                               const std::vector<std::string>& exclusion_list) {
    279     DCHECK(OnJNIThread());
    280     ProxyConfig proxy_config;
    281     if (exclude_pac_url_) {
    282       CreateStaticProxyConfig(host, port, "", exclusion_list, &proxy_config);
    283     } else {
    284       CreateStaticProxyConfig(host, port, pac_url, exclusion_list,
    285           &proxy_config);
    286     }
    287     network_task_runner_->PostTask(
    288         FROM_HERE,
    289         base::Bind(
    290             &Delegate::SetNewConfigOnNetworkThread, this, proxy_config));
    291   }
    292 
    293   void set_exclude_pac_url(bool enabled) {
    294     exclude_pac_url_ = enabled;
    295   }
    296 
    297  private:
    298   friend class base::RefCountedThreadSafe<Delegate>;
    299 
    300   class JNIDelegateImpl : public ProxyConfigServiceAndroid::JNIDelegate {
    301    public:
    302     explicit JNIDelegateImpl(Delegate* delegate) : delegate_(delegate) {}
    303 
    304     // ProxyConfigServiceAndroid::JNIDelegate overrides.
    305     virtual void ProxySettingsChangedTo(JNIEnv* env,
    306                                         jobject jself,
    307                                         jstring jhost,
    308                                         jint jport,
    309                                         jstring jpac_url,
    310                                         jobjectArray jexclusion_list) OVERRIDE {
    311       std::string host = ConvertJavaStringToUTF8(env, jhost);
    312       std::string pac_url;
    313       if (jpac_url)
    314         ConvertJavaStringToUTF8(env, jpac_url, &pac_url);
    315       std::vector<std::string> exclusion_list;
    316       base::android::AppendJavaStringArrayToStringVector(
    317           env, jexclusion_list, &exclusion_list);
    318       delegate_->ProxySettingsChangedTo(host, jport, pac_url, exclusion_list);
    319     }
    320 
    321     virtual void ProxySettingsChanged(JNIEnv* env, jobject self) OVERRIDE {
    322       delegate_->ProxySettingsChanged();
    323     }
    324 
    325    private:
    326     Delegate* const delegate_;
    327   };
    328 
    329   virtual ~Delegate() {}
    330 
    331   void ShutdownOnJNIThread() {
    332     if (java_proxy_change_listener_.is_null())
    333       return;
    334     JNIEnv* env = AttachCurrentThread();
    335     Java_ProxyChangeListener_stop(env, java_proxy_change_listener_.obj());
    336   }
    337 
    338   // Called on the network thread.
    339   void SetNewConfigOnNetworkThread(const ProxyConfig& proxy_config) {
    340     DCHECK(OnNetworkThread());
    341     proxy_config_ = proxy_config;
    342     FOR_EACH_OBSERVER(Observer, observers_,
    343                       OnProxyConfigChanged(proxy_config,
    344                                            ProxyConfigService::CONFIG_VALID));
    345   }
    346 
    347   bool OnJNIThread() const {
    348     return jni_task_runner_->RunsTasksOnCurrentThread();
    349   }
    350 
    351   bool OnNetworkThread() const {
    352     return network_task_runner_->RunsTasksOnCurrentThread();
    353   }
    354 
    355   ScopedJavaGlobalRef<jobject> java_proxy_change_listener_;
    356 
    357   JNIDelegateImpl jni_delegate_;
    358   ObserverList<Observer> observers_;
    359   scoped_refptr<base::SequencedTaskRunner> network_task_runner_;
    360   scoped_refptr<base::SequencedTaskRunner> jni_task_runner_;
    361   GetPropertyCallback get_property_callback_;
    362   ProxyConfig proxy_config_;
    363   bool exclude_pac_url_;
    364 
    365   DISALLOW_COPY_AND_ASSIGN(Delegate);
    366 };
    367 
    368 ProxyConfigServiceAndroid::ProxyConfigServiceAndroid(
    369     const scoped_refptr<base::SequencedTaskRunner>& network_task_runner,
    370     const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner)
    371     : delegate_(new Delegate(
    372         network_task_runner, jni_task_runner, base::Bind(&GetJavaProperty))) {
    373   delegate_->SetupJNI();
    374   delegate_->FetchInitialConfig();
    375 }
    376 
    377 ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() {
    378   delegate_->Shutdown();
    379 }
    380 
    381 // static
    382 bool ProxyConfigServiceAndroid::Register(JNIEnv* env) {
    383   return RegisterNativesImpl(env);
    384 }
    385 
    386 void ProxyConfigServiceAndroid::set_exclude_pac_url(bool enabled) {
    387   delegate_->set_exclude_pac_url(enabled);
    388 }
    389 
    390 void ProxyConfigServiceAndroid::AddObserver(Observer* observer) {
    391   delegate_->AddObserver(observer);
    392 }
    393 
    394 void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) {
    395   delegate_->RemoveObserver(observer);
    396 }
    397 
    398 ProxyConfigService::ConfigAvailability
    399 ProxyConfigServiceAndroid::GetLatestProxyConfig(ProxyConfig* config) {
    400   return delegate_->GetLatestProxyConfig(config);
    401 }
    402 
    403 ProxyConfigServiceAndroid::ProxyConfigServiceAndroid(
    404     const scoped_refptr<base::SequencedTaskRunner>& network_task_runner,
    405     const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner,
    406     GetPropertyCallback get_property_callback)
    407     : delegate_(new Delegate(
    408         network_task_runner, jni_task_runner, get_property_callback)) {
    409   delegate_->SetupJNI();
    410   delegate_->FetchInitialConfig();
    411 }
    412 
    413 void ProxyConfigServiceAndroid::ProxySettingsChanged() {
    414   delegate_->ProxySettingsChanged();
    415 }
    416 
    417 } // namespace net
    418