blob: 63c24374f266d938d5d4ed5930c7be92dc96f6ad [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/proxy_resolution/proxy_config_service_android.h"
#include <sys/system_properties.h>
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/observer_list.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "net/base/host_port_pair.h"
#include "net/base/proxy_server.h"
#include "net/base/proxy_string_util.h"
#include "net/net_jni_headers/ProxyChangeListener_jni.h"
#include "net/proxy_resolution/proxy_config_with_annotation.h"
#include "url/third_party/mozilla/url_parse.h"
using base::android::AttachCurrentThread;
using base::android::ConvertUTF8ToJavaString;
using base::android::ConvertJavaStringToUTF8;
using base::android::CheckException;
using base::android::ClearException;
using base::android::JavaParamRef;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
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::Component component(0, port.size());
int result = url::ParsePort(port.c_str(), component);
if (result == url::PORT_INVALID || result == url::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_t>(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;
base::StringTokenizer tokenizer(non_proxy_hosts, "|");
while (tokenizer.GetNext()) {
std::string token = tokenizer.token();
std::string pattern;
base::TrimWhitespaceASCII(token, base::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->AddRuleFromString(scheme + "://" + pattern);
}
}
// 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
// mostly equivalent Android implementation. There is one intentional
// difference: by default Chromium uses the HTTP port (80) for HTTPS
// connections via proxy. This default is identical on other platforms.
// On the opposite, Java spec suggests to use HTTPS port (443) by default (the
// default value of https.proxyPort).
rules->type = ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
rules->proxies_for_http.SetSingleProxyServer(
LookupProxy("http", get_property, ProxyServer::SCHEME_HTTP));
rules->proxies_for_https.SetSingleProxyServer(
LookupProxy("https", get_property, ProxyServer::SCHEME_HTTP));
rules->proxies_for_ftp.SetSingleProxyServer(
LookupProxy("ftp", get_property, ProxyServer::SCHEME_HTTP));
rules->fallback_proxies.SetSingleProxyServer(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);
// We know a proxy was found if not all of the proxy lists are empty.
return !(rules->proxies_for_http.IsEmpty() &&
rules->proxies_for_https.IsEmpty() &&
rules->proxies_for_ftp.IsEmpty() &&
rules->fallback_proxies.IsEmpty());
}
void GetLatestProxyConfigInternal(const GetPropertyCallback& get_property,
ProxyConfigWithAnnotation* config) {
ProxyConfig proxy_config;
proxy_config.set_from_system(true);
if (GetProxyRules(get_property, &proxy_config.proxy_rules())) {
*config =
ProxyConfigWithAnnotation(proxy_config, MISSING_TRAFFIC_ANNOTATION);
} else {
*config = ProxyConfigWithAnnotation::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);
return result.is_null() ?
std::string() : ConvertJavaStringToUTF8(env, result.obj());
}
void CreateStaticProxyConfig(const std::string& host,
int port,
const std::string& pac_url,
const std::vector<std::string>& exclusion_list,
ProxyConfigWithAnnotation* config) {
ProxyConfig proxy_config;
if (!pac_url.empty()) {
proxy_config.set_pac_url(GURL(pac_url));
proxy_config.set_pac_mandatory(false);
*config =
ProxyConfigWithAnnotation(proxy_config, MISSING_TRAFFIC_ANNOTATION);
} else if (port != 0) {
std::string rules = base::StringPrintf("%s:%d", host.c_str(), port);
proxy_config.proxy_rules().ParseFromString(rules);
proxy_config.proxy_rules().bypass_rules.Clear();
std::vector<std::string>::const_iterator it;
for (it = exclusion_list.begin(); it != exclusion_list.end(); ++it) {
std::string pattern;
base::TrimWhitespaceASCII(*it, base::TRIM_ALL, &pattern);
if (pattern.empty())
continue;
proxy_config.proxy_rules().bypass_rules.AddRuleFromString(pattern);
}
*config =
ProxyConfigWithAnnotation(proxy_config, MISSING_TRAFFIC_ANNOTATION);
} else {
*config = ProxyConfigWithAnnotation::CreateDirect();
}
}
std::string ParseOverrideRules(
const std::vector<ProxyConfigServiceAndroid::ProxyOverrideRule>&
override_rules,
ProxyConfig::ProxyRules* proxy_rules) {
// If no rules were specified, use DIRECT for everything.
if (override_rules.empty()) {
DCHECK(proxy_rules->empty());
return "";
}
// Otherwise use a proxy list per URL scheme.
proxy_rules->type = ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
for (const auto& rule : override_rules) {
// Parse the proxy URL.
ProxyChain proxy_chain =
ProxyUriToProxyChain(rule.proxy_url, ProxyServer::Scheme::SCHEME_HTTP);
if (!proxy_chain.IsValid()) {
return "Invalid Proxy URL: " + rule.proxy_url;
} else if (proxy_chain.is_multi_proxy()) {
return "Unsupported multi proxy chain: " + rule.proxy_url;
} else if (proxy_chain.is_single_proxy() &&
proxy_chain.GetProxyServer(/*chain_index=*/0).is_quic()) {
return "Unsupported proxy scheme: " + rule.proxy_url;
}
// Parse the URL scheme.
if (base::EqualsCaseInsensitiveASCII(rule.url_scheme, "http")) {
proxy_rules->proxies_for_http.AddProxyChain(proxy_chain);
} else if (base::EqualsCaseInsensitiveASCII(rule.url_scheme, "https")) {
proxy_rules->proxies_for_https.AddProxyChain(proxy_chain);
} else if (rule.url_scheme == "*") {
proxy_rules->fallback_proxies.AddProxyChain(proxy_chain);
} else {
return "Unsupported URL scheme: " + rule.url_scheme;
}
}
// If there is no per-URL scheme distinction simplify the ProxyRules.
if (proxy_rules->proxies_for_http.IsEmpty() &&
proxy_rules->proxies_for_https.IsEmpty() &&
!proxy_rules->fallback_proxies.IsEmpty()) {
proxy_rules->type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
std::swap(proxy_rules->single_proxies, proxy_rules->fallback_proxies);
}
return "";
}
std::string CreateOverrideProxyConfig(
const std::vector<ProxyConfigServiceAndroid::ProxyOverrideRule>&
proxy_rules,
const std::vector<std::string>& bypass_rules,
const bool reverse_bypass,
ProxyConfigWithAnnotation* config) {
ProxyConfig proxy_config;
auto result = ParseOverrideRules(proxy_rules, &proxy_config.proxy_rules());
if (!result.empty()) {
return result;
}
proxy_config.proxy_rules().reverse_bypass = reverse_bypass;
for (const auto& bypass_rule : bypass_rules) {
if (!proxy_config.proxy_rules().bypass_rules.AddRuleFromString(
bypass_rule)) {
return "Invalid bypass rule " + bypass_rule;
}
}
*config = ProxyConfigWithAnnotation(proxy_config, MISSING_TRAFFIC_ANNOTATION);
return "";
}
} // namespace
class ProxyConfigServiceAndroid::Delegate
: public base::RefCountedThreadSafe<Delegate> {
public:
Delegate(const scoped_refptr<base::SequencedTaskRunner>& main_task_runner,
const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner,
const GetPropertyCallback& get_property_callback)
: jni_delegate_(this),
main_task_runner_(main_task_runner),
jni_task_runner_(jni_task_runner),
get_property_callback_(get_property_callback) {}
Delegate(const Delegate&) = delete;
Delegate& operator=(const Delegate&) = delete;
void SetupJNI() {
DCHECK(InJNISequence());
JNIEnv* env = AttachCurrentThread();
if (java_proxy_change_listener_.is_null()) {
java_proxy_change_listener_.Reset(Java_ProxyChangeListener_create(env));
CHECK(!java_proxy_change_listener_.is_null());
}
Java_ProxyChangeListener_start(env, java_proxy_change_listener_,
reinterpret_cast<intptr_t>(&jni_delegate_));
}
void FetchInitialConfig() {
DCHECK(InJNISequence());
ProxyConfigWithAnnotation proxy_config;
GetLatestProxyConfigInternal(get_property_callback_, &proxy_config);
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Delegate::SetNewConfigInMainSequence, this,
proxy_config));
}
void Shutdown() {
if (InJNISequence()) {
ShutdownInJNISequence();
} else {
jni_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Delegate::ShutdownInJNISequence, this));
}
}
// Called only in the network sequence.
void AddObserver(Observer* observer) {
DCHECK(InMainSequence());
observers_.AddObserver(observer);
}
void RemoveObserver(Observer* observer) {
DCHECK(InMainSequence());
observers_.RemoveObserver(observer);
}
ConfigAvailability GetLatestProxyConfig(ProxyConfigWithAnnotation* config) {
DCHECK(InMainSequence());
if (!config)
return ProxyConfigService::CONFIG_UNSET;
*config = proxy_config_;
return ProxyConfigService::CONFIG_VALID;
}
// Called in the JNI sequence.
void ProxySettingsChanged() {
DCHECK(InJNISequence());
if (has_proxy_override_)
return;
ProxyConfigWithAnnotation proxy_config;
GetLatestProxyConfigInternal(get_property_callback_, &proxy_config);
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Delegate::SetNewConfigInMainSequence, this,
proxy_config));
}
// Called in the JNI sequence.
void ProxySettingsChangedTo(const std::string& host,
int port,
const std::string& pac_url,
const std::vector<std::string>& exclusion_list) {
DCHECK(InJNISequence());
if (has_proxy_override_)
return;
ProxyConfigWithAnnotation proxy_config;
if (exclude_pac_url_) {
CreateStaticProxyConfig(host, port, "", exclusion_list, &proxy_config);
} else {
CreateStaticProxyConfig(host, port, pac_url, exclusion_list,
&proxy_config);
}
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Delegate::SetNewConfigInMainSequence, this,
proxy_config));
}
void set_exclude_pac_url(bool enabled) {
exclude_pac_url_ = enabled;
}
// Called in the JNI sequence.
std::string SetProxyOverride(
const std::vector<ProxyOverrideRule>& proxy_rules,
const std::vector<std::string>& bypass_rules,
const bool reverse_bypass,
base::OnceClosure callback) {
DCHECK(InJNISequence());
has_proxy_override_ = true;
// Creates a new proxy config
ProxyConfigWithAnnotation proxy_config;
std::string result = CreateOverrideProxyConfig(
proxy_rules, bypass_rules, reverse_bypass, &proxy_config);
if (!result.empty()) {
return result;
}
main_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&Delegate::SetNewConfigInMainSequence, this,
proxy_config),
std::move(callback));
return "";
}
// Called in the JNI sequence.
void ClearProxyOverride(base::OnceClosure callback) {
DCHECK(InJNISequence());
if (!has_proxy_override_) {
std::move(callback).Run();
return;
}
ProxyConfigWithAnnotation proxy_config;
GetLatestProxyConfigInternal(get_property_callback_, &proxy_config);
main_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&Delegate::SetNewConfigInMainSequence, this,
proxy_config),
std::move(callback));
has_proxy_override_ = false;
}
private:
friend class base::RefCountedThreadSafe<Delegate>;
class JNIDelegateImpl : public ProxyConfigServiceAndroid::JNIDelegate {
public:
explicit JNIDelegateImpl(Delegate* delegate) : delegate_(delegate) {}
// ProxyConfigServiceAndroid::JNIDelegate overrides.
void ProxySettingsChangedTo(
JNIEnv* env,
const JavaParamRef<jobject>& jself,
const JavaParamRef<jstring>& jhost,
jint jport,
const JavaParamRef<jstring>& jpac_url,
const JavaParamRef<jobjectArray>& jexclusion_list) override {
std::string host = ConvertJavaStringToUTF8(env, jhost);
std::string pac_url;
if (jpac_url)
ConvertJavaStringToUTF8(env, jpac_url, &pac_url);
std::vector<std::string> exclusion_list;
base::android::AppendJavaStringArrayToStringVector(
env, jexclusion_list, &exclusion_list);
delegate_->ProxySettingsChangedTo(host, jport, pac_url, exclusion_list);
}
void ProxySettingsChanged(JNIEnv* env,
const JavaParamRef<jobject>& self) override {
delegate_->ProxySettingsChanged();
}
private:
const raw_ptr<Delegate> delegate_;
};
virtual ~Delegate() = default;
void ShutdownInJNISequence() {
if (java_proxy_change_listener_.is_null())
return;
JNIEnv* env = AttachCurrentThread();
Java_ProxyChangeListener_stop(env, java_proxy_change_listener_);
}
// Called on the network sequence.
void SetNewConfigInMainSequence(
const ProxyConfigWithAnnotation& proxy_config) {
DCHECK(InMainSequence());
proxy_config_ = proxy_config;
for (auto& observer : observers_) {
observer.OnProxyConfigChanged(proxy_config,
ProxyConfigService::CONFIG_VALID);
}
}
bool InJNISequence() const {
return jni_task_runner_->RunsTasksInCurrentSequence();
}
bool InMainSequence() const {
return main_task_runner_->RunsTasksInCurrentSequence();
}
ScopedJavaGlobalRef<jobject> java_proxy_change_listener_;
JNIDelegateImpl jni_delegate_;
base::ObserverList<Observer>::Unchecked observers_;
scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
scoped_refptr<base::SequencedTaskRunner> jni_task_runner_;
GetPropertyCallback get_property_callback_;
ProxyConfigWithAnnotation proxy_config_;
bool exclude_pac_url_ = false;
// This may only be accessed or modified on the JNI thread
bool has_proxy_override_ = false;
};
ProxyConfigServiceAndroid::ProxyConfigServiceAndroid(
const scoped_refptr<base::SequencedTaskRunner>& main_task_runner,
const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner)
: delegate_(base::MakeRefCounted<Delegate>(
main_task_runner,
jni_task_runner,
base::BindRepeating(&GetJavaProperty))) {
delegate_->SetupJNI();
delegate_->FetchInitialConfig();
}
ProxyConfigServiceAndroid::~ProxyConfigServiceAndroid() {
delegate_->Shutdown();
}
void ProxyConfigServiceAndroid::set_exclude_pac_url(bool enabled) {
delegate_->set_exclude_pac_url(enabled);
}
void ProxyConfigServiceAndroid::AddObserver(Observer* observer) {
delegate_->AddObserver(observer);
}
void ProxyConfigServiceAndroid::RemoveObserver(Observer* observer) {
delegate_->RemoveObserver(observer);
}
ProxyConfigService::ConfigAvailability
ProxyConfigServiceAndroid::GetLatestProxyConfig(
ProxyConfigWithAnnotation* config) {
return delegate_->GetLatestProxyConfig(config);
}
ProxyConfigServiceAndroid::ProxyConfigServiceAndroid(
const scoped_refptr<base::SequencedTaskRunner>& main_task_runner,
const scoped_refptr<base::SequencedTaskRunner>& jni_task_runner,
GetPropertyCallback get_property_callback)
: delegate_(base::MakeRefCounted<Delegate>(main_task_runner,
jni_task_runner,
get_property_callback)) {
delegate_->SetupJNI();
delegate_->FetchInitialConfig();
}
void ProxyConfigServiceAndroid::ProxySettingsChangedTo(
const std::string& host,
int port,
const std::string& pac_url,
const std::vector<std::string>& exclusion_list) {
delegate_->ProxySettingsChangedTo(host, port, pac_url, exclusion_list);
}
void ProxyConfigServiceAndroid::ProxySettingsChanged() {
delegate_->ProxySettingsChanged();
}
std::string ProxyConfigServiceAndroid::SetProxyOverride(
const std::vector<ProxyOverrideRule>& proxy_rules,
const std::vector<std::string>& bypass_rules,
const bool reverse_bypass,
base::OnceClosure callback) {
return delegate_->SetProxyOverride(proxy_rules, bypass_rules, reverse_bypass,
std::move(callback));
}
void ProxyConfigServiceAndroid::ClearProxyOverride(base::OnceClosure callback) {
delegate_->ClearProxyOverride(std::move(callback));
}
} // namespace net