1cf2cfa174ca878c144e17e9fc60ca8e9070d7dededisonn@google.com// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2cf2cfa174ca878c144e17e9fc60ca8e9070d7dededisonn@google.com// Use of this source code is governed by a BSD-style license that can be
38cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com// found in the LICENSE file.
4cf2cfa174ca878c144e17e9fc60ca8e9070d7dededisonn@google.com
5cf2cfa174ca878c144e17e9fc60ca8e9070d7dededisonn@google.com#include "net/proxy/proxy_config_service_android.h"
6cf2cfa174ca878c144e17e9fc60ca8e9070d7dededisonn@google.com
78cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include <sys/system_properties.h>
8cf2cfa174ca878c144e17e9fc60ca8e9070d7dededisonn@google.com
98cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/android/jni_array.h"
10cf2cfa174ca878c144e17e9fc60ca8e9070d7dededisonn@google.com#include "base/android/jni_string.h"
118cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/basictypes.h"
128cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/bind.h"
138cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/callback.h"
148cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/compiler_specific.h"
158cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/location.h"
168cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/logging.h"
178cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/memory/ref_counted.h"
188cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/observer_list.h"
198cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/sequenced_task_runner.h"
208cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/strings/string_tokenizer.h"
218cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/strings/string_util.h"
228cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "base/strings/stringprintf.h"
238cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "jni/ProxyChangeListener_jni.h"
248cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "net/base/host_port_pair.h"
258cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "net/proxy/proxy_config.h"
268cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com#include "url/url_parse.h"
278cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com
288cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comusing base::android::AttachCurrentThread;
298cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comusing base::android::ConvertUTF8ToJavaString;
308cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comusing base::android::ConvertJavaStringToUTF8;
318cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comusing base::android::CheckException;
328cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comusing base::android::ClearException;
338cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comusing base::android::ScopedJavaGlobalRef;
348cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com
358cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comnamespace net {
368cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com
378cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comnamespace {
388cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com
398cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.comtypedef ProxyConfigServiceAndroid::GetPropertyCallback GetPropertyCallback;
408cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com
418cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com// Returns whether the provided string was successfully converted to a port.
428cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.combool ConvertStringToPort(const std::string& port, int* output) {
438cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com  url::Component component(0, port.size());
448cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com  int result = url::ParsePort(port.c_str(), component);
458cee797901763ab0922eb9ef484cfdcbc94bee54edisonn@google.com  if (result == url::PORT_INVALID || result == url::PORT_UNSPECIFIED)
46    return false;
47  *output = result;
48  return true;
49}
50
51ProxyServer 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
66ProxyServer 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
84ProxyServer 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
94void 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.
125bool 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
152void GetLatestProxyConfigInternal(const GetPropertyCallback& get_property,
153                                  ProxyConfig* config) {
154  if (!GetProxyRules(get_property, &config->proxy_rules()))
155    *config = ProxyConfig::CreateDirect();
156}
157
158std::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
169void 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
197class 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
368ProxyConfigServiceAndroid::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
377ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() {
378  delegate_->Shutdown();
379}
380
381// static
382bool ProxyConfigServiceAndroid::Register(JNIEnv* env) {
383  return RegisterNativesImpl(env);
384}
385
386void ProxyConfigServiceAndroid::set_exclude_pac_url(bool enabled) {
387  delegate_->set_exclude_pac_url(enabled);
388}
389
390void ProxyConfigServiceAndroid::AddObserver(Observer* observer) {
391  delegate_->AddObserver(observer);
392}
393
394void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) {
395  delegate_->RemoveObserver(observer);
396}
397
398ProxyConfigService::ConfigAvailability
399ProxyConfigServiceAndroid::GetLatestProxyConfig(ProxyConfig* config) {
400  return delegate_->GetLatestProxyConfig(config);
401}
402
403ProxyConfigServiceAndroid::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
413void ProxyConfigServiceAndroid::ProxySettingsChanged() {
414  delegate_->ProxySettingsChanged();
415}
416
417} // namespace net
418