diff options
-rw-r--r-- | chrome/browser/ui/webui/options/advanced_options_utils_gtk.cc | 50 | ||||
-rw-r--r-- | net/proxy/proxy_config_service_linux.cc | 409 |
2 files changed, 435 insertions, 24 deletions
diff --git a/chrome/browser/ui/webui/options/advanced_options_utils_gtk.cc b/chrome/browser/ui/webui/options/advanced_options_utils_gtk.cc index edb0f25..b55454d 100644 --- a/chrome/browser/ui/webui/options/advanced_options_utils_gtk.cc +++ b/chrome/browser/ui/webui/options/advanced_options_utils_gtk.cc @@ -7,18 +7,22 @@ #include "chrome/browser/ui/webui/options/advanced_options_utils.h" #include "base/environment.h" +#include "base/file_path.h" +#include "base/file_util.h" #include "base/nix/xdg_util.h" #include "base/process_util.h" +#include "base/string_util.h" #include "content/browser/browser_thread.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/process_watcher.h" -// Command used to configure GNOME proxy settings. The command was renamed -// in January 2009, so both are used to work on both old and new systems. -// As on April 2011, many systems do not have the old command anymore. -// TODO(thestig) Remove the old command in the future. -const char* kOldGNOMEProxyConfigCommand[] = {"gnome-network-preferences", NULL}; -const char* kGNOMEProxyConfigCommand[] = {"gnome-network-properties", NULL}; +// Command used to configure GNOME 2 proxy settings. +const char* kGNOME2ProxyConfigCommand[] = {"gnome-network-properties", NULL}; +// In GNOME 3, we might need to run gnome-control-center instead. We try this +// only after gnome-network-properties is not found, because older GNOME also +// has this but it doesn't do the same thing. See below where we use it. +const char* kGNOME3ProxyConfigCommand[] = {"gnome-control-center", "network", + NULL}; // KDE3 and KDE4 are only slightly different, but incompatible. Go figure. const char* kKDE3ProxyConfigCommand[] = {"kcmshell", "proxy", NULL}; const char* kKDE4ProxyConfigCommand[] = {"kcmshell4", "proxy", NULL}; @@ -43,6 +47,30 @@ void ShowLinuxProxyConfigUrl(TabContents* tab_contents) { // Start the given proxy configuration utility. bool StartProxyConfigUtil(TabContents* tab_contents, const char* command[]) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + // base::LaunchApp() returns true ("success") if the fork() succeeds, but not + // necessarily the exec(). We'd like to be able to use StartProxyConfigUtil() + // to search possible options and stop on success, so we search $PATH first to + // predict whether the exec is expected to succeed. + // TODO(mdm): this is a useful check, and is very similar to some code in + // proxy_config_service_linux.cc. It should probably be in base:: somewhere. + scoped_ptr<base::Environment> env(base::Environment::Create()); + std::string path; + if (!env->GetVar("PATH", &path)) { + LOG(ERROR) << "No $PATH variable. Assuming no " << command[0] << "."; + return false; + } + std::vector<std::string> paths; + Tokenize(path, ":", &paths); + bool found = false; + for (size_t i = 0; i < paths.size(); ++i) { + FilePath file(paths[i]); + if (file_util::PathExists(file.Append(command[0]))) { + found = true; + break; + } + } + if (!found) + return false; std::vector<std::string> argv; for (size_t i = 0; command[i]; ++i) argv.push_back(command[i]); @@ -65,10 +93,16 @@ void DetectAndStartProxyConfigUtil(TabContents* tab_contents) { bool launched = false; switch (base::nix::GetDesktopEnvironment(env.get())) { case base::nix::DESKTOP_ENVIRONMENT_GNOME: { - launched = StartProxyConfigUtil(tab_contents, kGNOMEProxyConfigCommand); + launched = StartProxyConfigUtil(tab_contents, kGNOME2ProxyConfigCommand); if (!launched) { + // We try this second, even though it's the newer way, because this + // command existed in older versions of GNOME, but it didn't do the + // same thing. The older command is gone though, so this should do + // the right thing. (Also some distributions have blurred the lines + // between GNOME 2 and 3, so we can't necessarily detect what the + // right thing is based on indications of which version we have.) launched = StartProxyConfigUtil(tab_contents, - kOldGNOMEProxyConfigCommand); + kGNOME3ProxyConfigCommand); } break; } diff --git a/net/proxy/proxy_config_service_linux.cc b/net/proxy/proxy_config_service_linux.cc index c2ad145..c575584 100644 --- a/net/proxy/proxy_config_service_linux.cc +++ b/net/proxy/proxy_config_service_linux.cc @@ -8,7 +8,13 @@ #include <fcntl.h> #if defined(USE_GCONF) #include <gconf/gconf-client.h> -#endif +#endif // defined(USE_GCONF) +#if defined(USE_GIO) +#include <gio/gio.h> +#if defined(DLOPEN_GSETTINGS) +#include <dlfcn.h> +#endif // defined(DLOPEN_GSETTINGS) +#endif // defined(USE_GIO) #include <limits.h> #include <stdio.h> #include <stdlib.h> @@ -337,8 +343,7 @@ class SettingGetterImplGConf : public ProxyConfigServiceLinux::SettingGetter { return false; } } - virtual bool GetStringList(Setting key, - std::vector<std::string>* result) { + virtual bool GetStringList(Setting key, std::vector<std::string>* result) { switch (key) { case PROXY_IGNORE_HOSTS: return GetStringListByPath("/system/http_proxy/ignore_hosts", result); @@ -413,10 +418,8 @@ class SettingGetterImplGConf : public ProxyConfigServiceLinux::SettingGetter { GCONF_VALUE_STRING, &error); if (HandleGError(error, key)) return false; - if (!list) { - // unset + if (!list) return false; - } for (GSList *it = list; it; it = it->next) { result->push_back(static_cast<char*>(it->data)); g_free(it->data); @@ -449,15 +452,14 @@ class SettingGetterImplGConf : public ProxyConfigServiceLinux::SettingGetter { // We don't use Reset() because the timer may not yet be running. // (In that case Stop() is a no-op.) debounce_timer_.Stop(); - debounce_timer_.Start(base::TimeDelta::FromMilliseconds( - kDebounceTimeoutMilliseconds), this, - &SettingGetterImplGConf::OnDebouncedNotification); + debounce_timer_.Start( + base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), + this, &SettingGetterImplGConf::OnDebouncedNotification); } - // gconf notification callback, dispatched from the default glib main loop. - static void OnGConfChangeNotification( - GConfClient* client, guint cnxn_id, - GConfEntry* entry, gpointer user_data) { + // gconf notification callback, dispatched on the default glib main loop. + static void OnGConfChangeNotification(GConfClient* client, guint cnxn_id, + GConfEntry* entry, gpointer user_data) { VLOG(1) << "gconf change notification for key " << gconf_entry_get_key(entry); // We don't track which key has changed, just that something did change. @@ -479,6 +481,370 @@ class SettingGetterImplGConf : public ProxyConfigServiceLinux::SettingGetter { }; #endif // defined(USE_GCONF) +#if defined(USE_GIO) +// This setting getter uses gsettings, as used in most GNOME 3 desktops. +class SettingGetterImplGSettings + : public ProxyConfigServiceLinux::SettingGetter { + public: + SettingGetterImplGSettings() + : client_(NULL), notify_delegate_(NULL), loop_(NULL) { +#if defined(DLOPEN_GSETTINGS) + gio_handle_ = NULL; +#endif + } + + virtual ~SettingGetterImplGSettings() { + // client_ should have been released before now, from + // Delegate::OnDestroy(), while running on the UI thread. However + // on exiting the process, it may happen that + // Delegate::OnDestroy() task is left pending on the glib loop + // after the loop was quit, and pending tasks may then be deleted + // without being run. + if (client_) { + // gconf client was not cleaned up. + if (MessageLoop::current() == loop_) { + // We are on the UI thread so we can clean it safely. This is + // the case at least for ui_tests running under Valgrind in + // bug 16076. + VLOG(1) << "~SettingGetterImplGSettings: releasing gsettings client"; + ShutDown(); + } else { + LOG(WARNING) << "~SettingGetterImplGSettings: leaking gsettings client"; + client_ = NULL; + } + } + DCHECK(!client_); +#if defined(DLOPEN_GSETTINGS) + if (gio_handle_) { + dlclose(gio_handle_); + gio_handle_ = NULL; + } +#endif + } + + // LoadAndCheckVersion() must be called *before* Init()! + bool LoadAndCheckVersion(base::Environment* env); + + virtual bool Init(MessageLoop* glib_default_loop, + MessageLoopForIO* file_loop) { + DCHECK(MessageLoop::current() == glib_default_loop); + DCHECK(!client_); + DCHECK(!loop_); + client_ = g_settings_new("org.gnome.system.proxy"); + if (!client_) { + // It's not clear whether/when this can return NULL. + LOG(ERROR) << "Unable to create a gsettings client"; + return false; + } + loop_ = glib_default_loop; + // We assume these all work if the above call worked. + http_client_ = g_settings_get_child(client_, "http"); + https_client_ = g_settings_get_child(client_, "https"); + ftp_client_ = g_settings_get_child(client_, "ftp"); + socks_client_ = g_settings_get_child(client_, "socks"); + DCHECK(http_client_ && https_client_ && ftp_client_ && socks_client_); + return true; + } + + void ShutDown() { + if (client_) { + DCHECK(MessageLoop::current() == loop_); + // This also disables gsettings notifications. + g_object_unref(socks_client_); + g_object_unref(ftp_client_); + g_object_unref(https_client_); + g_object_unref(http_client_); + g_object_unref(client_); + // We only need to null client_ because it's the only one that we check. + client_ = NULL; + loop_ = NULL; + } + } + + bool SetUpNotifications(ProxyConfigServiceLinux::Delegate* delegate) { + DCHECK(client_); + DCHECK(MessageLoop::current() == loop_); + notify_delegate_ = delegate; + // We could watch for the change-event signal instead of changed, but + // since we have to watch more than one object, we'd still have to + // debounce change notifications. This is conceptually simpler. + g_signal_connect(G_OBJECT(client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(http_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(https_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(ftp_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + g_signal_connect(G_OBJECT(socks_client_), "changed", + G_CALLBACK(OnGSettingsChangeNotification), this); + // Simulate a change to avoid possibly losing updates before this point. + OnChangeNotification(); + return true; + } + + virtual MessageLoop* GetNotificationLoop() { + return loop_; + } + + virtual const char* GetDataSource() { + return "gsettings"; + } + + virtual bool GetString(Setting key, std::string* result) { + DCHECK(client_); + switch (key) { + case PROXY_MODE: + return GetStringByPath(client_, "mode", result); + case PROXY_AUTOCONF_URL: + return GetStringByPath(client_, "autoconfig-url", result); + case PROXY_HTTP_HOST: + return GetStringByPath(http_client_, "host", result); + case PROXY_HTTPS_HOST: + return GetStringByPath(https_client_, "host", result); + case PROXY_FTP_HOST: + return GetStringByPath(ftp_client_, "host", result); + case PROXY_SOCKS_HOST: + return GetStringByPath(socks_client_, "host", result); + default: + return false; + } + } + virtual bool GetBool(Setting key, bool* result) { + DCHECK(client_); + switch (key) { + case PROXY_USE_HTTP_PROXY: + // Although there is an "enabled" boolean in http_client_, it is not set + // to true by the proxy config utility. We ignore it and return false. + return false; + case PROXY_USE_SAME_PROXY: + // Similarly, although there is a "use-same-proxy" boolean in client_, + // it is never set to false by the proxy config utility. We ignore it. + return false; + case PROXY_USE_AUTHENTICATION: + // There is also no way to set this in the proxy config utility, but it + // doesn't hurt us to get the actual setting (unlike the two above). + return GetBoolByPath(http_client_, "use-authentication", result); + default: + return false; + } + } + virtual bool GetInt(Setting key, int* result) { + DCHECK(client_); + switch (key) { + case PROXY_HTTP_PORT: + return GetIntByPath(http_client_, "port", result); + case PROXY_HTTPS_PORT: + return GetIntByPath(https_client_, "port", result); + case PROXY_FTP_PORT: + return GetIntByPath(ftp_client_, "port", result); + case PROXY_SOCKS_PORT: + return GetIntByPath(socks_client_, "port", result); + default: + return false; + } + } + virtual bool GetStringList(Setting key, std::vector<std::string>* result) { + DCHECK(client_); + switch (key) { + case PROXY_IGNORE_HOSTS: + return GetStringListByPath(client_, "ignore-hosts", result); + default: + return false; + } + } + + virtual bool BypassListIsReversed() { + // This is a KDE-specific setting. + return false; + } + + virtual bool MatchHostsUsingSuffixMatching() { + return false; + } + + private: +#if defined(DLOPEN_GSETTINGS) + // We replicate the prototypes for the g_settings APIs we need. We may not + // even be compiling on a system that has them. If we are, these won't + // conflict both because they are identical and also due to scoping. The + // scoping will also ensure that these get used instead of the global ones. + struct _GSettings; + typedef struct _GSettings GSettings; + GSettings* (*g_settings_new)(const gchar* schema); + GSettings* (*g_settings_get_child)(GSettings* settings, const gchar* name); + gboolean (*g_settings_get_boolean)(GSettings* settings, const gchar* key); + gchar* (*g_settings_get_string)(GSettings* settings, const gchar* key); + gint (*g_settings_get_int)(GSettings* settings, const gchar* key); + gchar** (*g_settings_get_strv)(GSettings* settings, const gchar* key); + + // The library handle. + void* gio_handle_; + + // Load a symbol from |gio_handle_| and store it into |*func_ptr|. + bool LoadSymbol(const char* name, void** func_ptr) { + dlerror(); + *func_ptr = dlsym(gio_handle_, name); + const char* error = dlerror(); + if (error) { + VLOG(1) << "Unable to load symbol " << name << ": " << error; + return false; + } + return true; + } +#endif // defined(DLOPEN_GSETTINGS) + + bool GetStringByPath(GSettings* client, const char* key, + std::string* result) { + DCHECK(MessageLoop::current() == loop_); + gchar* value = g_settings_get_string(client, key); + if (!value) + return false; + *result = value; + g_free(value); + return true; + } + bool GetBoolByPath(GSettings* client, const char* key, bool* result) { + DCHECK(MessageLoop::current() == loop_); + *result = static_cast<bool>(g_settings_get_boolean(client, key)); + return true; + } + bool GetIntByPath(GSettings* client, const char* key, int* result) { + DCHECK(MessageLoop::current() == loop_); + *result = g_settings_get_int(client, key); + return true; + } + bool GetStringListByPath(GSettings* client, const char* key, + std::vector<std::string>* result) { + DCHECK(MessageLoop::current() == loop_); + gchar** list = g_settings_get_strv(client, key); + if (!list) + return false; + for (size_t i = 0; list[i]; ++i) { + result->push_back(static_cast<char*>(list[i])); + g_free(list[i]); + } + g_free(list); + return true; + } + + // This is the callback from the debounce timer. + void OnDebouncedNotification() { + DCHECK(MessageLoop::current() == loop_); + CHECK(notify_delegate_); + // Forward to a method on the proxy config service delegate object. + notify_delegate_->OnCheckProxyConfigSettings(); + } + + void OnChangeNotification() { + // We don't use Reset() because the timer may not yet be running. + // (In that case Stop() is a no-op.) + debounce_timer_.Stop(); + debounce_timer_.Start( + base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), + this, &SettingGetterImplGSettings::OnDebouncedNotification); + } + + // gsettings notification callback, dispatched on the default glib main loop. + static void OnGSettingsChangeNotification(GSettings* client, gchar* key, + gpointer user_data) { + VLOG(1) << "gsettings change notification for key " << key; + // We don't track which key has changed, just that something did change. + SettingGetterImplGSettings* setting_getter = + reinterpret_cast<SettingGetterImplGSettings*>(user_data); + setting_getter->OnChangeNotification(); + } + + GSettings* client_; + GSettings* http_client_; + GSettings* https_client_; + GSettings* ftp_client_; + GSettings* socks_client_; + ProxyConfigServiceLinux::Delegate* notify_delegate_; + base::OneShotTimer<SettingGetterImplGSettings> debounce_timer_; + + // Message loop of the thread that we make gsettings 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(SettingGetterImplGSettings); +}; + +bool SettingGetterImplGSettings::LoadAndCheckVersion( + base::Environment* env) { + // LoadAndCheckVersion() must be called *before* Init()! + DCHECK(!client_); + + // The APIs to query gsettings were introduced after the minimum glib + // version we target, so we can't link directly against them. We load them + // dynamically at runtime, and if they don't exist, return false here. (We + // support linking directly via gyp flags though.) Additionally, even when + // they are present, we do two additional checks to make sure we should use + // them and not gconf. First, we attempt to load the schema for proxy + // settings. Second, we check for the program that was used in older + // versions of GNOME to configure proxy settings, and return false if it + // exists. Some distributions (e.g. Ubuntu 11.04) have the API and schema + // but don't use gsettings for proxy settings, but they do have the old + // binary, so we detect these systems that way. + +#ifdef DLOPEN_GSETTINGS + gio_handle_ = dlopen("libgio-2.0.so", RTLD_NOW | RTLD_GLOBAL); + if (!gio_handle_) { + VLOG(1) << "Cannot load gio library. Will fall back to gconf."; + return false; + } + if (!LoadSymbol("g_settings_new", + reinterpret_cast<void**>(&g_settings_new)) || + !LoadSymbol("g_settings_get_child", + reinterpret_cast<void**>(&g_settings_get_child)) || + !LoadSymbol("g_settings_get_string", + reinterpret_cast<void**>(&g_settings_get_string)) || + !LoadSymbol("g_settings_get_boolean", + reinterpret_cast<void**>(&g_settings_get_boolean)) || + !LoadSymbol("g_settings_get_int", + reinterpret_cast<void**>(&g_settings_get_int)) || + !LoadSymbol("g_settings_get_strv", + reinterpret_cast<void**>(&g_settings_get_strv))) { + VLOG(1) << "Cannot load gsettings API. Will fall back to gconf."; + dlclose(gio_handle_); + gio_handle_ = NULL; + return false; + } +#endif + + GSettings* client = g_settings_new("org.gnome.system.proxy"); + if (!client) { + VLOG(1) << "Cannot create gsettings client. Will fall back to gconf."; + return false; + } + g_object_unref(client); + + std::string path; + if (!env->GetVar("PATH", &path)) { + LOG(ERROR) << "No $PATH variable. Assuming no gnome-network-properties."; + } else { + // Yes, we're on the UI thread. Yes, we're accessing the file system. + // Sadly, we don't have much choice. We need the proxy settings and we + // need them now, and to figure out where to get them, we have to check + // for this binary. See http://crbug.com/69057 for additional details. + base::ThreadRestrictions::ScopedAllowIO allow_io; + std::vector<std::string> paths; + Tokenize(path, ":", &paths); + for (size_t i = 0; i < paths.size(); ++i) { + FilePath file(paths[i]); + if (file_util::PathExists(file.Append("gnome-network-properties"))) { + VLOG(1) << "Found gnome-network-properties. Will fall back to gconf."; + return false; + } + } + } + + VLOG(1) << "All gsettings tests OK. Will get proxy config from gsettings."; + return true; +} +#endif // defined(USE_GIO) + // This is the KDE version that reads kioslaverc and simulates gconf. // Doing this allows the main Delegate code, as well as the unit tests // for it, to stay the same - and the settings map fairly well besides. @@ -643,8 +1009,7 @@ class SettingGetterImplKDE : public ProxyConfigServiceLinux::SettingGetter, // We don't ever have any integers. (See AddProxy() below about ports.) return false; } - virtual bool GetStringList(Setting key, - std::vector<std::string>* result) { + virtual bool GetStringList(Setting key, std::vector<std::string>* result) { strings_map_type::iterator it = strings_table_.find(key); if (it == strings_table_.end()) return false; @@ -1140,8 +1505,20 @@ ProxyConfigServiceLinux::Delegate::Delegate(base::Environment* env_var_getter) // Figure out which SettingGetterImpl to use, if any. switch (base::nix::GetDesktopEnvironment(env_var_getter)) { case base::nix::DESKTOP_ENVIRONMENT_GNOME: +#if defined(USE_GIO) + { + scoped_ptr<SettingGetterImplGSettings> gs_getter( + new SettingGetterImplGSettings()); + // We have to load symbols and check the GNOME version in use to decide + // if we should use the gsettings getter. See LoadAndCheckVersion(). + if (gs_getter->LoadAndCheckVersion(env_var_getter)) + setting_getter_.reset(gs_getter.release()); + } +#endif #if defined(USE_GCONF) - setting_getter_.reset(new SettingGetterImplGConf()); + // Fall back on gconf if gsettings is unavailable or incorrect. + if (!setting_getter_.get()) + setting_getter_.reset(new SettingGetterImplGConf()); #endif break; case base::nix::DESKTOP_ENVIRONMENT_KDE3: |