| Index: net/proxy/proxy_config_service_linux.cc
|
| ===================================================================
|
| --- net/proxy/proxy_config_service_linux.cc (revision 16488)
|
| +++ net/proxy/proxy_config_service_linux.cc (working copy)
|
| @@ -5,12 +5,12 @@
|
| #include "net/proxy/proxy_config_service_linux.h"
|
|
|
| #include <gconf/gconf-client.h>
|
| -#include <gdk/gdk.h>
|
| #include <stdlib.h>
|
|
|
| #include "base/logging.h"
|
| #include "base/string_tokenizer.h"
|
| #include "base/string_util.h"
|
| +#include "base/task.h"
|
| #include "googleurl/src/url_canon.h"
|
| #include "net/base/net_errors.h"
|
| #include "net/http/http_util.h"
|
| @@ -71,8 +71,7 @@
|
| std::string::size_type at_sign = host.find("@");
|
| // Should this be supported?
|
| if (at_sign != std::string::npos) {
|
| - LOG(ERROR) << "ProxyConfigServiceLinux: proxy authentication "
|
| - "not supported";
|
| + LOG(ERROR) << "Proxy authentication not supported";
|
| // Disregard the authentication parameters and continue with this hostname.
|
| host = host.substr(at_sign + 1);
|
| }
|
| @@ -88,7 +87,7 @@
|
|
|
| } // namespace
|
|
|
| -bool ProxyConfigServiceLinux::GetProxyFromEnvVarForScheme(
|
| +bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVarForScheme(
|
| const char* variable, ProxyServer::Scheme scheme,
|
| ProxyServer* result_server) {
|
| std::string env_value;
|
| @@ -100,21 +99,20 @@
|
| *result_server = proxy_server;
|
| return true;
|
| } else {
|
| - LOG(ERROR) << "ProxyConfigServiceLinux: failed to parse "
|
| - << "environment variable " << variable;
|
| + LOG(ERROR) << "Failed to parse environment variable " << variable;
|
| }
|
| }
|
| }
|
| return false;
|
| }
|
|
|
| -bool ProxyConfigServiceLinux::GetProxyFromEnvVar(
|
| +bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVar(
|
| const char* variable, ProxyServer* result_server) {
|
| return GetProxyFromEnvVarForScheme(variable, ProxyServer::SCHEME_HTTP,
|
| result_server);
|
| }
|
|
|
| -bool ProxyConfigServiceLinux::GetConfigFromEnv(ProxyConfig* config) {
|
| +bool ProxyConfigServiceLinux::Delegate::GetConfigFromEnv(ProxyConfig* config) {
|
| // Check for automatic configuration first, in
|
| // "auto_proxy". Possibly only the "environment_proxy" firefox
|
| // extension has ever used this, but it still sounds like a good
|
| @@ -160,7 +158,7 @@
|
| ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS4;
|
| std::string env_version;
|
| if (env_var_getter_->Getenv("SOCKS_VERSION", &env_version)
|
| - && env_version.compare("5") == 0)
|
| + && env_version == "5")
|
| scheme = ProxyServer::SCHEME_SOCKS5;
|
| if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_server)) {
|
| config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
|
| @@ -183,37 +181,101 @@
|
|
|
| namespace {
|
|
|
| +// static
|
| +// gconf notification callback, dispatched from the default
|
| +// glib main loop.
|
| +void OnGConfChangeNotification(
|
| + GConfClient* client, guint cnxn_id,
|
| + GConfEntry* entry, gpointer user_data) {
|
| + // It would be nice to debounce multiple callbacks in quick
|
| + // succession, since I guess we'll get one for each changed key. As
|
| + // it is we will read settings from gconf once for each callback.
|
| + LOG(INFO) << "gconf change notification for key "
|
| + << gconf_entry_get_key(entry);
|
| + // We don't track which key has changed, just that something did change.
|
| + // Forward to a method on the proxy config service delegate object.
|
| + ProxyConfigServiceLinux::Delegate* config_service_delegate =
|
| + reinterpret_cast<ProxyConfigServiceLinux::Delegate*>(user_data);
|
| + config_service_delegate->OnCheckProxyConfigSettings();
|
| +}
|
| +
|
| class GConfSettingGetterImpl
|
| : public ProxyConfigServiceLinux::GConfSettingGetter {
|
| public:
|
| - GConfSettingGetterImpl() : client_(NULL) {}
|
| + GConfSettingGetterImpl() : client_(NULL), loop_(NULL) {}
|
| +
|
| virtual ~GConfSettingGetterImpl() {
|
| - if (client_)
|
| - g_object_unref(client_);
|
| + LOG(INFO) << "~GConfSettingGetterImpl called";
|
| + // client_ should have been released before now, from
|
| + // Delegate::OnDestroy(), while running on the UI thread.
|
| + DCHECK(!client_);
|
| }
|
|
|
| - virtual void Enter() {
|
| - gdk_threads_enter();
|
| + virtual bool Init() {
|
| + DCHECK(!client_);
|
| + DCHECK(!loop_);
|
| + loop_ = MessageLoopForUI::current();
|
| + client_ = gconf_client_get_default();
|
| + if (!client_) {
|
| + // It's not clear whether/when this can return NULL.
|
| + LOG(ERROR) << "Unable to create a gconf client";
|
| + loop_ = NULL;
|
| + return false;
|
| + }
|
| + GError* error = NULL;
|
| + // We need to add the directories for which we'll be asking
|
| + // notifications, and we might as well ask to preload them.
|
| + gconf_client_add_dir(client_, "/system/proxy",
|
| + GCONF_CLIENT_PRELOAD_ONELEVEL, &error);
|
| + if (error == NULL) {
|
| + gconf_client_add_dir(client_, "/system/http_proxy",
|
| + GCONF_CLIENT_PRELOAD_ONELEVEL, &error);
|
| + }
|
| + if (error != NULL) {
|
| + LOG(ERROR) << "Error requesting gconf directory: " << error->message;
|
| + g_error_free(error);
|
| + Release();
|
| + return false;
|
| + }
|
| + return true;
|
| }
|
| - virtual void Leave() {
|
| - gdk_threads_leave();
|
| +
|
| + void Release() {
|
| + if (client_) {
|
| + DCHECK(MessageLoop::current() == loop_);
|
| + // This also disables gconf notifications.
|
| + g_object_unref(client_);
|
| + client_ = NULL;
|
| + loop_ = NULL;
|
| + }
|
| }
|
|
|
| - virtual bool InitIfNeeded() {
|
| - if (!client_) {
|
| - Enter();
|
| - client_ = gconf_client_get_default();
|
| - Leave();
|
| - // It's not clear whether/when this can return NULL.
|
| - if (!client_)
|
| - LOG(ERROR) << "ProxyConfigServiceLinux: Unable to create "
|
| - "a gconf client";
|
| + bool SetupNotification(void* callback_user_data) {
|
| + DCHECK(client_);
|
| + DCHECK(MessageLoop::current() == loop_);
|
| + GError* error = NULL;
|
| + gconf_client_notify_add(
|
| + client_, "/system/proxy",
|
| + OnGConfChangeNotification, callback_user_data,
|
| + NULL, &error);
|
| + if (error == NULL) {
|
| + gconf_client_notify_add(
|
| + client_, "/system/http_proxy",
|
| + OnGConfChangeNotification, callback_user_data,
|
| + NULL, &error);
|
| }
|
| - return client_ != NULL;
|
| + if (error != NULL) {
|
| + LOG(ERROR) << "Error requesting gconf notifications: " << error->message;
|
| + g_error_free(error);
|
| + Release();
|
| + return false;
|
| + }
|
| + return true;
|
| }
|
|
|
| virtual bool GetString(const char* key, std::string* result) {
|
| - CHECK(client_);
|
| + DCHECK(client_);
|
| + DCHECK(MessageLoop::current() == loop_);
|
| GError* error = NULL;
|
| gchar* value = gconf_client_get_string(client_, key, &error);
|
| if (HandleGError(error, key))
|
| @@ -225,7 +287,8 @@
|
| return true;
|
| }
|
| virtual bool GetBoolean(const char* key, bool* result) {
|
| - CHECK(client_);
|
| + DCHECK(client_);
|
| + DCHECK(MessageLoop::current() == loop_);
|
| GError* error = NULL;
|
| // We want to distinguish unset values from values defaulting to
|
| // false. For that we need to use the type-generic
|
| @@ -247,7 +310,8 @@
|
| return true;
|
| }
|
| virtual bool GetInt(const char* key, int* result) {
|
| - CHECK(client_);
|
| + DCHECK(client_);
|
| + DCHECK(MessageLoop::current() == loop_);
|
| GError* error = NULL;
|
| int value = gconf_client_get_int(client_, key, &error);
|
| if (HandleGError(error, key))
|
| @@ -259,7 +323,8 @@
|
| }
|
| virtual bool GetStringList(const char* key,
|
| std::vector<std::string>* result) {
|
| - CHECK(client_);
|
| + DCHECK(client_);
|
| + DCHECK(MessageLoop::current() == loop_);
|
| GError* error = NULL;
|
| GSList* list = gconf_client_get_list(client_, key,
|
| GCONF_VALUE_STRING, &error);
|
| @@ -282,8 +347,8 @@
|
| // (error is NULL).
|
| bool HandleGError(GError* error, const char* key) {
|
| if (error != NULL) {
|
| - LOG(ERROR) << "ProxyConfigServiceLinux: error getting gconf value for "
|
| - << key << ": " << error->message;
|
| + LOG(ERROR) << "Error getting gconf value for " << key
|
| + << ": " << error->message;
|
| g_error_free(error);
|
| return true;
|
| }
|
| @@ -292,12 +357,17 @@
|
|
|
| GConfClient* client_;
|
|
|
| + // Message loop of the thread that we make gconf calls on. It should
|
| + // be the UI thread and all our methods should be called on this
|
| + // thread. Only for assertions.
|
| + MessageLoop* loop_;
|
| +
|
| DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImpl);
|
| };
|
|
|
| } // namespace
|
|
|
| -bool ProxyConfigServiceLinux::GetProxyFromGConf(
|
| +bool ProxyConfigServiceLinux::Delegate::GetProxyFromGConf(
|
| const char* key_prefix, bool is_socks, ProxyServer* result_server) {
|
| std::string key(key_prefix);
|
| std::string host;
|
| @@ -324,7 +394,8 @@
|
| return false;
|
| }
|
|
|
| -bool ProxyConfigServiceLinux::GetConfigFromGConf(ProxyConfig* config) {
|
| +bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf(
|
| + ProxyConfig* config) {
|
| std::string mode;
|
| if (!gconf_getter_->GetString("/system/proxy/mode", &mode)) {
|
| // We expect this to always be set, so if we don't see it then we
|
| @@ -332,11 +403,12 @@
|
| // proxy config.
|
| return false;
|
| }
|
| - if (mode.compare("none") == 0)
|
| + if (mode == "none") {
|
| // Specifically specifies no proxy.
|
| return true;
|
| + }
|
|
|
| - if (mode.compare("auto") == 0) {
|
| + if (mode == "auto") {
|
| // automatic proxy config
|
| std::string pac_url_str;
|
| if (gconf_getter_->GetString("/system/proxy/autoconfig_url",
|
| @@ -353,7 +425,7 @@
|
| return true;
|
| }
|
|
|
| - if (mode.compare("manual") != 0) {
|
| + if (mode != "manual") {
|
| // Mode is unrecognized.
|
| return false;
|
| }
|
| @@ -419,8 +491,7 @@
|
| gconf_getter_->GetBoolean("/system/http_proxy/use_authentication",
|
| &use_auth);
|
| if (use_auth)
|
| - LOG(ERROR) << "ProxyConfigServiceLinux: proxy authentication "
|
| - "not supported";
|
| + LOG(ERROR) << "Proxy authentication not supported";
|
|
|
| // Now the bypass list.
|
| gconf_getter_->GetStringList("/system/http_proxy/ignore_hosts",
|
| @@ -431,64 +502,160 @@
|
| return true;
|
| }
|
|
|
| -ProxyConfigServiceLinux::ProxyConfigServiceLinux(
|
| +ProxyConfigServiceLinux::Delegate::Delegate(
|
| EnvironmentVariableGetter* env_var_getter,
|
| GConfSettingGetter* gconf_getter)
|
| - : env_var_getter_(env_var_getter), gconf_getter_(gconf_getter) {
|
| + : env_var_getter_(env_var_getter), gconf_getter_(gconf_getter),
|
| + glib_default_loop_(NULL), io_loop_(NULL) {
|
| }
|
|
|
| -ProxyConfigServiceLinux::ProxyConfigServiceLinux()
|
| - : env_var_getter_(new EnvironmentVariableGetterImpl()),
|
| - gconf_getter_(new GConfSettingGetterImpl()) {
|
| -}
|
| -
|
| -int ProxyConfigServiceLinux::GetProxyConfig(ProxyConfig* config) {
|
| +bool ProxyConfigServiceLinux::Delegate::ShouldTryGConf() {
|
| // GNOME_DESKTOP_SESSION_ID being defined is a good indication that
|
| // we are probably running under GNOME.
|
| // Note: KDE_FULL_SESSION is a corresponding env var to recognize KDE.
|
| std::string dummy, desktop_session;
|
| - bool ok = false;
|
| -#if 0 // gconf temporarily disabled because of races.
|
| - // See http://crbug.com/11442.
|
| - if (env_var_getter_->Getenv("GNOME_DESKTOP_SESSION_ID", &dummy)
|
| + return env_var_getter_->Getenv("GNOME_DESKTOP_SESSION_ID", &dummy)
|
| || (env_var_getter_->Getenv("DESKTOP_SESSION", &desktop_session)
|
| - && desktop_session.compare("gnome") == 0)) {
|
| - // Get settings from gconf.
|
| - //
|
| - // I (sdoyon) would have liked to prioritize environment variables
|
| - // and only fallback to gconf if env vars were unset. But
|
| - // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
|
| - // does so even if the proxy mode is set to auto, which would
|
| - // mislead us.
|
| - //
|
| - // We could introduce a CHROME_PROXY_OBEY_ENV_VARS variable...??
|
| - if (gconf_getter_->InitIfNeeded()) {
|
| - gconf_getter_->Enter();
|
| - ok = GetConfigFromGConf(config);
|
| - gconf_getter_->Leave();
|
| - if (ok)
|
| - LOG(INFO) << "ProxyConfigServiceLinux: obtained proxy setting "
|
| - "from gconf";
|
| + && desktop_session == "gnome");
|
| + // I (sdoyon) would have liked to prioritize environment variables
|
| + // and only fallback to gconf if env vars were unset. But
|
| + // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
|
| + // does so even if the proxy mode is set to auto, which would
|
| + // mislead us.
|
| + //
|
| + // We could introduce a CHROME_PROXY_OBEY_ENV_VARS variable...??
|
| +}
|
| +
|
| +void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig(
|
| + MessageLoop* glib_default_loop, MessageLoop* io_loop) {
|
| + // We should be running on the default glib main loop thread right
|
| + // now. gconf can only be accessed from this thread.
|
| + DCHECK(MessageLoop::current() == glib_default_loop);
|
| + glib_default_loop_ = glib_default_loop;
|
| + io_loop_ = io_loop;
|
| +
|
| + // If we are passed a NULL io_loop, then we don't setup gconf
|
| + // notifications. This should not be the usual case but is intended
|
| + // to simplify test setups.
|
| + if (!io_loop_)
|
| + LOG(INFO) << "Monitoring of gconf setting changes is disabled";
|
| +
|
| + // Fetch and cache the current proxy config. The config is left in
|
| + // cached_config_, where GetProxyConfig() running on the IO thread
|
| + // will expect to find it. This is safe to do because we return
|
| + // before this ProxyConfigServiceLinux is passed on to
|
| + // the ProxyService.
|
| + bool got_config = false;
|
| + if (ShouldTryGConf() &&
|
| + gconf_getter_->Init() &&
|
| + (!io_loop || gconf_getter_->SetupNotification(this))) {
|
| + if (GetConfigFromGConf(&cached_config_)) {
|
| + cached_config_.set_id(1); // mark it as valid
|
| + got_config = true;
|
| + LOG(INFO) << "Obtained proxy setting from gconf";
|
| // If gconf proxy mode is "none", meaning direct, then we take
|
| - // that to be a valid config and will not check environment variables.
|
| - // The alternative would have been to look for a proxy whereever
|
| - // we can find one.
|
| + // that to be a valid config and will not check environment
|
| + // variables. The alternative would have been to look for a proxy
|
| + // where ever we can find one.
|
| //
|
| - // TODO(sdoyon): Consider wiring in the gconf notification
|
| - // system. Cache this result config to return on subsequent calls,
|
| - // and only call GetConfigFromGConf() when we know things have
|
| - // actually changed.
|
| + // Keep a copy of the config for use from this thread for
|
| + // comparison with updated settings when we get notifications.
|
| + reference_config_ = cached_config_;
|
| + reference_config_.set_id(1); // mark it as valid
|
| + } else {
|
| + gconf_getter_->Release(); // Stop notifications
|
| }
|
| }
|
| -#endif // 0 (gconf disabled)
|
| - // An implementation for KDE settings would be welcome here.
|
| - if (!ok) {
|
| - ok = GetConfigFromEnv(config);
|
| - if (ok)
|
| - LOG(INFO) << "ProxyConfigServiceLinux: obtained proxy setting "
|
| - "from environment variables";
|
| + if (!got_config) {
|
| + // An implementation for KDE settings would be welcome here.
|
| + //
|
| + // Consulting environment variables doesn't need to be done from
|
| + // the default glib main loop, but it's a tiny enough amount of
|
| + // work.
|
| + if (GetConfigFromEnv(&cached_config_)) {
|
| + cached_config_.set_id(1); // mark it as valid
|
| + LOG(INFO) << "Obtained proxy setting from environment variables";
|
| + }
|
| }
|
| - return ok ? OK : ERR_FAILED;
|
| }
|
|
|
| +void ProxyConfigServiceLinux::Delegate::Reset() {
|
| + DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_);
|
| + gconf_getter_->Release();
|
| + cached_config_ = ProxyConfig();
|
| +}
|
| +
|
| +int ProxyConfigServiceLinux::Delegate::GetProxyConfig(ProxyConfig* config) {
|
| + // This is called from the IO thread.
|
| + DCHECK(!io_loop_ || MessageLoop::current() == io_loop_);
|
| +
|
| + // Simply return the last proxy configuration that glib_default_loop
|
| + // notified us of.
|
| + *config = cached_config_;
|
| + return cached_config_.is_valid() ? OK : ERR_FAILED;
|
| +}
|
| +
|
| +void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() {
|
| + // This should be dispatched from the thread with the default glib
|
| + // main loop, which allows us to access gconf.
|
| + DCHECK(MessageLoop::current() == glib_default_loop_);
|
| +
|
| + ProxyConfig new_config;
|
| + bool valid = GetConfigFromGConf(&new_config);
|
| + if (valid)
|
| + new_config.set_id(1); // mark it as valid
|
| +
|
| + // See if it is different than what we had before.
|
| + if (new_config.is_valid() != reference_config_.is_valid() ||
|
| + !new_config.Equals(reference_config_)) {
|
| + // Post a task to |io_loop| with the new configuration, so it can
|
| + // update |cached_config_|.
|
| + io_loop_->PostTask(
|
| + FROM_HERE,
|
| + NewRunnableMethod(
|
| + this,
|
| + &ProxyConfigServiceLinux::Delegate::SetNewProxyConfig,
|
| + new_config));
|
| + }
|
| +}
|
| +
|
| +void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig(
|
| + const ProxyConfig& new_config) {
|
| + DCHECK(MessageLoop::current() == io_loop_);
|
| + LOG(INFO) << "Proxy configuration changed";
|
| + cached_config_ = new_config;
|
| +}
|
| +
|
| +void ProxyConfigServiceLinux::Delegate::PostDestroyTask() {
|
| + if (MessageLoop::current() == glib_default_loop_) {
|
| + // Already on the right thread, call directly.
|
| + // This is the case for the unittests.
|
| + OnDestroy();
|
| + } else {
|
| + // Post to UI thread. Note that on browser shutdown, we may quit
|
| + // the UI MessageLoop and exit the program before ever running
|
| + // this.
|
| + glib_default_loop_->PostTask(
|
| + FROM_HERE,
|
| + NewRunnableMethod(
|
| + this,
|
| + &ProxyConfigServiceLinux::Delegate::OnDestroy));
|
| + }
|
| +}
|
| +void ProxyConfigServiceLinux::Delegate::OnDestroy() {
|
| + DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_);
|
| + gconf_getter_->Release();
|
| +}
|
| +
|
| +ProxyConfigServiceLinux::ProxyConfigServiceLinux()
|
| + : delegate_(new Delegate(new EnvironmentVariableGetterImpl(),
|
| + new GConfSettingGetterImpl())) {
|
| +}
|
| +
|
| +ProxyConfigServiceLinux::ProxyConfigServiceLinux(
|
| + EnvironmentVariableGetter* env_var_getter,
|
| + GConfSettingGetter* gconf_getter)
|
| + : delegate_(new Delegate(env_var_getter, gconf_getter)) {
|
| +}
|
| +
|
| } // namespace net
|
|
|