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