summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/ui/webui/options/advanced_options_utils_gtk.cc50
-rw-r--r--net/proxy/proxy_config_service_linux.cc409
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: