summaryrefslogtreecommitdiffstats
path: root/net/proxy
diff options
context:
space:
mode:
authormdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-28 23:13:43 +0000
committermdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-28 23:13:43 +0000
commitd7395e73bc35be3247bdcf5c68fd2fff7497a78c (patch)
tree7bb0daf83b0675100a6b974e2342be68fdf1028e /net/proxy
parentf9bcd26a1e7bbe4d4f4bbbc72ba40e59d3be1fbb (diff)
downloadchromium_src-d7395e73bc35be3247bdcf5c68fd2fff7497a78c.zip
chromium_src-d7395e73bc35be3247bdcf5c68fd2fff7497a78c.tar.gz
chromium_src-d7395e73bc35be3247bdcf5c68fd2fff7497a78c.tar.bz2
Linux: get GNOME or KDE proxy settings.
BUG=17363, 20407 TEST=none Review URL: http://codereview.chromium.org/174327 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24831 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/proxy')
-rw-r--r--net/proxy/proxy_config_service_linux.cc628
-rw-r--r--net/proxy/proxy_config_service_linux.h45
-rw-r--r--net/proxy/proxy_config_service_linux_unittest.cc55
-rw-r--r--net/proxy/proxy_service.cc20
-rw-r--r--net/proxy/proxy_service.h9
5 files changed, 615 insertions, 142 deletions
diff --git a/net/proxy/proxy_config_service_linux.cc b/net/proxy/proxy_config_service_linux.cc
index 2998a0c..a3a9993 100644
--- a/net/proxy/proxy_config_service_linux.cc
+++ b/net/proxy/proxy_config_service_linux.cc
@@ -4,13 +4,23 @@
#include "net/proxy/proxy_config_service_linux.h"
+#include <errno.h>
+#include <fcntl.h>
#include <gconf/gconf-client.h>
+#include <limits.h>
+#include <stdio.h>
#include <stdlib.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include "base/file_path.h"
+#include "base/file_util.h"
#include "base/logging.h"
+#include "base/message_loop.h"
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "base/task.h"
+#include "base/timer.h"
#include "googleurl/src/url_canon.h"
#include "net/base/net_errors.h"
#include "net/http/http_util.h"
@@ -55,6 +65,10 @@ std::string FixupProxyHostScheme(ProxyServer::Scheme scheme,
host = "socks4://" + host;
else if (scheme == ProxyServer::SCHEME_SOCKS5)
host = "socks5://" + host;
+ // If there is a trailing slash, remove it so |host| will parse correctly
+ // even if it includes a port number (since the slash is not numeric).
+ if (host.length() && host[host.length() - 1] == '/')
+ host.resize(host.length() - 1);
return host;
}
@@ -155,30 +169,16 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromEnv(ProxyConfig* config) {
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();
-}
+const int kDebounceTimeoutMilliseconds = 250;
-class GConfSettingGetterImpl
+// This is the "real" gconf version that actually uses gconf.
+class GConfSettingGetterImplGConf
: public ProxyConfigServiceLinux::GConfSettingGetter {
public:
- GConfSettingGetterImpl() : client_(NULL), loop_(NULL) {}
+ GConfSettingGetterImplGConf()
+ : client_(NULL), notify_delegate_(NULL), loop_(NULL) {}
- virtual ~GConfSettingGetterImpl() {
+ virtual ~GConfSettingGetterImplGConf() {
// 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
@@ -191,20 +191,22 @@ class GConfSettingGetterImpl
// 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.
- LOG(INFO) << "~GConfSettingGetterImpl: releasing gconf client";
- Release();
+ LOG(INFO) << "~GConfSettingGetterImplGConf: releasing gconf client";
+ Shutdown();
} else {
- LOG(WARNING) << "~GConfSettingGetterImpl: leaking gconf client";
+ LOG(WARNING) << "~GConfSettingGetterImplGConf: leaking gconf client";
client_ = NULL;
}
}
DCHECK(!client_);
}
- virtual bool Init() {
+ virtual bool Init(MessageLoop* glib_default_loop,
+ MessageLoopForIO* file_loop) {
+ DCHECK(MessageLoop::current() == glib_default_loop);
DCHECK(!client_);
DCHECK(!loop_);
- loop_ = MessageLoopForUI::current();
+ loop_ = glib_default_loop;
client_ = gconf_client_get_default();
if (!client_) {
// It's not clear whether/when this can return NULL.
@@ -224,13 +226,13 @@ class GConfSettingGetterImpl
if (error != NULL) {
LOG(ERROR) << "Error requesting gconf directory: " << error->message;
g_error_free(error);
- Release();
+ Shutdown();
return false;
}
return true;
}
- void Release() {
+ void Shutdown() {
if (client_) {
DCHECK(MessageLoop::current() == loop_);
// This also disables gconf notifications.
@@ -240,29 +242,38 @@ class GConfSettingGetterImpl
}
}
- bool SetupNotification(void* callback_user_data) {
+ bool SetupNotification(ProxyConfigServiceLinux::Delegate* delegate) {
DCHECK(client_);
DCHECK(MessageLoop::current() == loop_);
GError* error = NULL;
+ notify_delegate_ = delegate;
gconf_client_notify_add(
client_, "/system/proxy",
- OnGConfChangeNotification, callback_user_data,
+ OnGConfChangeNotification, this,
NULL, &error);
if (error == NULL) {
gconf_client_notify_add(
client_, "/system/http_proxy",
- OnGConfChangeNotification, callback_user_data,
+ OnGConfChangeNotification, this,
NULL, &error);
}
if (error != NULL) {
LOG(ERROR) << "Error requesting gconf notifications: " << error->message;
g_error_free(error);
- Release();
+ Shutdown();
return false;
}
return true;
}
+ MessageLoop* GetNotificationLoop() {
+ return loop_;
+ }
+
+ virtual const char* GetDataSource() {
+ return "gconf";
+ }
+
virtual bool GetString(const char* key, std::string* result) {
DCHECK(client_);
DCHECK(MessageLoop::current() == loop_);
@@ -345,14 +356,451 @@ class GConfSettingGetterImpl
return false;
}
+ // This is the callback from the debounce timer.
+ void OnDebouncedNotification() {
+ DCHECK(MessageLoop::current() == loop_);
+ DCHECK(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,
+ &GConfSettingGetterImplGConf::OnDebouncedNotification);
+ }
+
+ // gconf notification callback, dispatched from the default glib main loop.
+ static void OnGConfChangeNotification(
+ GConfClient* client, guint cnxn_id,
+ GConfEntry* entry, gpointer user_data) {
+ 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.
+ GConfSettingGetterImplGConf* setting_getter =
+ reinterpret_cast<GConfSettingGetterImplGConf*>(user_data);
+ setting_getter->OnChangeNotification();
+ }
+
GConfClient* client_;
+ ProxyConfigServiceLinux::Delegate* notify_delegate_;
+ base::OneShotTimer<GConfSettingGetterImplGConf> debounce_timer_;
// 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);
+ DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImplGConf);
+};
+
+// 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.
+class GConfSettingGetterImplKDE
+ : public ProxyConfigServiceLinux::GConfSettingGetter,
+ public base::MessagePumpLibevent::Watcher {
+ public:
+ explicit GConfSettingGetterImplKDE(
+ base::EnvironmentVariableGetter* env_var_getter)
+ : inotify_fd_(-1), notify_delegate_(NULL), indirect_manual_(false),
+ auto_no_pac_(false), reversed_exception_(false), file_loop_(NULL) {
+ // We don't save the env var getter for later use since we don't own it.
+ // Instead we use it here and save the result we actually care about.
+ std::string kde_home;
+ if (!env_var_getter->Getenv("KDE_HOME", &kde_home)) {
+ if (!env_var_getter->Getenv("HOME", &kde_home))
+ // User has no $HOME? Give up. Later we'll report the failure.
+ return;
+ kde_home = FilePath(kde_home).Append(FILE_PATH_LITERAL(".kde")).value();
+ }
+ kde_config_dir_ = FilePath(kde_home).Append(
+ FILE_PATH_LITERAL("share")).Append(FILE_PATH_LITERAL("config"));
+ }
+
+ virtual ~GConfSettingGetterImplKDE() {
+ // inotify_fd_ should have been closed before now, from
+ // Delegate::OnDestroy(), while running on the file thread. However
+ // on exiting the process, it may happen that Delegate::OnDestroy()
+ // task is left pending on the file loop after the loop was quit,
+ // and pending tasks may then be deleted without being run.
+ // Here in the KDE version, we can safely close the file descriptor
+ // anyway. (Not that it really matters; the process is exiting.)
+ if (inotify_fd_ >= 0)
+ Shutdown();
+ DCHECK(inotify_fd_ < 0);
+ }
+
+ virtual bool Init(MessageLoop* glib_default_loop,
+ MessageLoopForIO* file_loop) {
+ DCHECK(inotify_fd_ < 0);
+ inotify_fd_ = inotify_init();
+ if (inotify_fd_ < 0) {
+ LOG(ERROR) << "inotify_init failed: " << strerror(errno);
+ return false;
+ }
+ int flags = fcntl(inotify_fd_, F_GETFL);
+ if (fcntl(inotify_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
+ LOG(ERROR) << "fcntl failed: " << strerror(errno);
+ close(inotify_fd_);
+ inotify_fd_ = -1;
+ return false;
+ }
+ file_loop_ = file_loop;
+ // The initial read is done on the current thread, not |file_loop_|,
+ // since we will need to have it for SetupAndFetchInitialConfig().
+ UpdateCachedSettings();
+ return true;
+ }
+
+ void Shutdown() {
+ if (inotify_fd_ >= 0) {
+ ResetCachedSettings();
+ inotify_watcher_.StopWatchingFileDescriptor();
+ close(inotify_fd_);
+ inotify_fd_ = -1;
+ }
+ }
+
+ bool SetupNotification(ProxyConfigServiceLinux::Delegate* delegate) {
+ DCHECK(inotify_fd_ >= 0);
+ DCHECK(file_loop_);
+ // We can't just watch the kioslaverc file directly, since KDE will write
+ // a new copy of it and then rename it whenever settings are changed and
+ // inotify watches inodes (so we'll be watching the old deleted file after
+ // the first change, and it will never change again). So, we watch the
+ // directory instead. We then act only on changes to the kioslaverc entry.
+ if (inotify_add_watch(inotify_fd_, kde_config_dir_.value().c_str(),
+ IN_MODIFY | IN_MOVED_TO) < 0)
+ return false;
+ notify_delegate_ = delegate;
+ return file_loop_->WatchFileDescriptor(inotify_fd_, true,
+ MessageLoopForIO::WATCH_READ, &inotify_watcher_, this);
+ }
+
+ MessageLoop* GetNotificationLoop() {
+ return file_loop_;
+ }
+
+ // Implement base::MessagePumpLibevent::Delegate.
+ void OnFileCanReadWithoutBlocking(int fd) {
+ DCHECK(fd == inotify_fd_);
+ DCHECK(MessageLoop::current() == file_loop_);
+ OnChangeNotification();
+ }
+ void OnFileCanWriteWithoutBlocking(int fd) {
+ NOTREACHED();
+ }
+
+ virtual const char* GetDataSource() {
+ return "KDE";
+ }
+
+ virtual bool GetString(const char* key, std::string* result) {
+ string_map_type::iterator it = string_table_.find(key);
+ if (it == string_table_.end())
+ return false;
+ *result = it->second;
+ return true;
+ }
+ virtual bool GetBoolean(const char* key, bool* result) {
+ // We don't ever have any booleans.
+ return false;
+ }
+ virtual bool GetInt(const char* key, int* result) {
+ // We don't ever have any integers. (See AddProxy() below about ports.)
+ return false;
+ }
+ virtual bool GetStringList(const char* key,
+ std::vector<std::string>* result) {
+ strings_map_type::iterator it = strings_table_.find(key);
+ if (it == strings_table_.end())
+ return false;
+ *result = it->second;
+ return true;
+ }
+
+ private:
+ void ResetCachedSettings() {
+ string_table_.clear();
+ strings_table_.clear();
+ indirect_manual_ = false;
+ auto_no_pac_ = false;
+ reversed_exception_ = false;
+ }
+
+ void AddProxy(std::string prefix, std::string value) {
+ if (value.empty() || value.substr(0, 3) == "//:")
+ // No proxy.
+ return;
+ // We don't need to parse the port number out; GetProxyFromGConf()
+ // would only append it right back again. So we just leave the port
+ // number right in the host string.
+ string_table_[prefix + "host"] = value;
+ }
+
+ void AddKDESetting(std::string key, const char* value) {
+ // The astute reader may notice that there is no mention of SOCKS
+ // here. That's because KDE handles socks is a strange way, and we
+ // don't support it. Rather than just a setting for the SOCKS server,
+ // it has a setting for a library to LD_PRELOAD in all your programs
+ // that will transparently SOCKSify them. Such libraries each have
+ // their own configuration, and thus, we can't get it from KDE.
+ if (key == "ProxyType") {
+ const char* mode = "none";
+ indirect_manual_ = false;
+ auto_no_pac_ = false;
+ switch (StringToInt(value)) {
+ case 0: // No proxy, or maybe kioslaverc syntax error.
+ break;
+ case 1: // Manual configuration.
+ mode = "manual";
+ break;
+ case 2: // PAC URL.
+ mode = "auto";
+ break;
+ case 3: // WPAD.
+ mode = "auto";
+ auto_no_pac_ = true;
+ break;
+ case 4: // Indirect manual via environment variables.
+ mode = "manual";
+ indirect_manual_ = true;
+ break;
+ }
+ string_table_["/system/proxy/mode"] = mode;
+ } else if (key == "Proxy Config Script") {
+ string_table_["/system/proxy/autoconfig_url"] = value;
+ } else if (key == "httpProxy") {
+ AddProxy("/system/http_proxy/", value);
+ } else if (key == "httpsProxy") {
+ AddProxy("/system/proxy/secure_", value);
+ } else if (key == "ftpProxy") {
+ AddProxy("/system/proxy/ftp_", value);
+ } else if (key == "ReversedException") {
+ // We count "true" or any nonzero number as true, otherwise false.
+ // Note that if the value is not actually numeric StringToInt()
+ // will return 0, which we count as false.
+ reversed_exception_ = !strcmp(value, "true") || StringToInt(value);
+ } else if (key == "NoProxyFor") {
+ std::vector<std::string> exceptions;
+ StringTokenizer tk(value, ",");
+ while (tk.GetNext()) {
+ std::string token = tk.token();
+ if (!token.empty())
+ exceptions.push_back(token);
+ }
+ strings_table_["/system/http_proxy/ignore_hosts"] = exceptions;
+ } else if (key == "AuthMode") {
+ // Check for authentication, just so we can warn.
+ int mode = StringToInt(value);
+ if (mode) {
+ // ProxyConfig does not support authentication parameters, but
+ // Chrome will prompt for the password later. So we ignore this.
+ LOG(WARNING) <<
+ "Proxy authentication parameters ignored, see bug 16709";
+ }
+ }
+ }
+
+ void ResolveIndirect(std::string key) {
+ // We can't save the environment variable getter that was passed
+ // when this object was constructed, but this setting is likely
+ // to be pretty unusual and the actual values it would return can
+ // be tested without using it. So we just use getenv() here.
+ string_map_type::iterator it = string_table_.find(key);
+ if (it != string_table_.end()) {
+ char* value = getenv(it->second.c_str());
+ if (value)
+ it->second = value;
+ }
+ }
+
+ // The settings in kioslaverc could occur in any order, but some affect
+ // others. Rather than read the whole file in and then query them in an
+ // order that allows us to handle that, we read the settings in whatever
+ // order they occur and do any necessary tweaking after we finish.
+ void ResolveModeEffects() {
+ if (indirect_manual_) {
+ ResolveIndirect("/system/http_proxy/host");
+ ResolveIndirect("/system/proxy/secure_host");
+ ResolveIndirect("/system/proxy/ftp_host");
+ }
+ if (auto_no_pac_) {
+ // Remove the PAC URL; we're not supposed to use it.
+ string_table_.erase("/system/proxy/autoconfig_url");
+ }
+ if (reversed_exception_) {
+ // We don't actually support this setting. (It means to use the proxy
+ // *only* for the exception list, rather than everything but them.)
+ // Nevertheless we can do better than *exactly the opposite* of the
+ // desired behavior by clearing the exception list and warning.
+ strings_table_.erase("/system/http_proxy/ignore_hosts");
+ LOG(WARNING) << "KDE reversed proxy exception list not supported";
+ }
+ }
+
+ // Reads kioslaverc one line at a time and calls AddKDESetting() to add
+ // each relevant name-value pair to the appropriate value table.
+ void UpdateCachedSettings() {
+ FilePath kioslaverc = kde_config_dir_.Append(
+ FILE_PATH_LITERAL("kioslaverc"));
+ file_util::ScopedFILE input(file_util::OpenFile(kioslaverc, "r"));
+ if (!input.get())
+ return;
+ ResetCachedSettings();
+ bool in_proxy_settings = false;
+ bool line_too_long = false;
+ char line[512];
+ // feof() and ferror() only tell you if you have hit EOF or an error
+ // after you try to read some data that encounters that condition. So,
+ // the initialize statement of this loop is the same as the update
+ // statement. We need to start out each iteration with fgets().
+ while (fgets(line, sizeof(line), input.get())) {
+ // fgets() guarantees the line will be properly terminated.
+ size_t length = strlen(line);
+ if (!length)
+ continue;
+ // This should be true even with CRLF endings.
+ if (line[length - 1] != '\n') {
+ line_too_long = true;
+ continue;
+ }
+ if (line_too_long) {
+ // The previous line had no line ending, but this done does. This is
+ // the end of the line that was too long, so warn here and skip it.
+ LOG(WARNING) << "skipped very long line in " << kioslaverc.value();
+ line_too_long = false;
+ continue;
+ }
+ // Remove the LF at the end, and the CR if there is one.
+ line[--length] = '\0';
+ if (length && line[length - 1] == '\r')
+ line[--length] = '\0';
+ // Now parse the line.
+ if (line[0] == '[') {
+ // Switching sections. All we care about is whether this is
+ // the (a?) proxy settings section, for both KDE3 and KDE4.
+ in_proxy_settings = !strncmp(line, "[Proxy Settings]", 16);
+ } else if (in_proxy_settings) {
+ // A regular line, in the (a?) proxy settings section.
+ char* value = strchr(line, '=');
+ if (!value)
+ continue;
+ // The length of the value name.
+ length = value - line;
+ if (!length)
+ continue;
+ // Is the value name localized?
+ if (line[length - 1] == ']') {
+ // Find the matching '['.
+ for (; length && line[length - 1] != '['; --length);
+ if (!length)
+ continue;
+ // Trim the localization indicator off.
+ line[length - 1] = '\0';
+ }
+ // Split the string on the = sign, and advance |value| to the value.
+ *(value++) = '\0';
+ // Now fill in the tables.
+ AddKDESetting(line, value);
+ }
+ }
+ if (ferror(input.get()))
+ LOG(ERROR) << "error reading " << kioslaverc.value();
+ ResolveModeEffects();
+ }
+
+ // This is the callback from the debounce timer.
+ void OnDebouncedNotification() {
+ DCHECK(MessageLoop::current() == file_loop_);
+ LOG(INFO) << "inotify change notification for kioslaverc";
+ UpdateCachedSettings();
+ DCHECK(notify_delegate_);
+ // Forward to a method on the proxy config service delegate object.
+ notify_delegate_->OnCheckProxyConfigSettings();
+ }
+
+ // Called by OnFileCanReadWithoutBlocking() on the file thread. Reads
+ // from the inotify file descriptor and starts up a debounce timer if
+ // an event for kioslaverc is seen.
+ void OnChangeNotification() {
+ DCHECK(inotify_fd_ >= 0);
+ DCHECK(MessageLoop::current() == file_loop_);
+ char event_buf[(sizeof(inotify_event) + NAME_MAX + 1) * 4];
+ bool kioslaverc_touched = false;
+ ssize_t r;
+ while ((r = read(inotify_fd_, event_buf, sizeof(event_buf))) > 0) {
+ // inotify returns variable-length structures, which is why we have
+ // this strange-looking loop instead of iterating through an array.
+ char* event_ptr = event_buf;
+ while (event_ptr < event_buf + r) {
+ inotify_event* event = reinterpret_cast<inotify_event*>(event_ptr);
+ // The kernel always feeds us whole events.
+ CHECK(event_ptr + sizeof(inotify_event) <= event_buf + r);
+ CHECK(event->name + event->len <= event_buf + r);
+ if (!strcmp(event->name, "kioslaverc"))
+ kioslaverc_touched = true;
+ // Advance the pointer just past the end of the filename.
+ event_ptr = event->name + event->len;
+ }
+ // We keep reading even if |kioslaverc_touched| is true to drain the
+ // inotify event queue.
+ }
+ if (!r)
+ // Instead of returning -1 and setting errno to EINVAL if there is not
+ // enough buffer space, older kernels (< 2.6.21) return 0. Simulate the
+ // new behavior (EINVAL) so we can reuse the code below.
+ errno = EINVAL;
+ if (errno != EAGAIN) {
+ LOG(WARNING) << "error reading inotify file descriptor: "
+ << strerror(errno);
+ if (errno == EINVAL) {
+ // Our buffer is not large enough to read the next event. This should
+ // not happen (because its size is calculated to always be sufficiently
+ // large), but if it does we'd warn continuously since |inotify_fd_|
+ // would be forever ready to read. Close it and stop watching instead.
+ LOG(ERROR) << "inotify failure; no longer watching kioslaverc!";
+ inotify_watcher_.StopWatchingFileDescriptor();
+ close(inotify_fd_);
+ inotify_fd_ = -1;
+ }
+ }
+ if (kioslaverc_touched) {
+ // 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,
+ &GConfSettingGetterImplKDE::OnDebouncedNotification);
+ }
+ }
+
+ typedef std::map<std::string, std::string> string_map_type;
+ typedef std::map<std::string, std::vector<std::string> > strings_map_type;
+
+ int inotify_fd_;
+ base::MessagePumpLibevent::FileDescriptorWatcher inotify_watcher_;
+ ProxyConfigServiceLinux::Delegate* notify_delegate_;
+ base::OneShotTimer<GConfSettingGetterImplKDE> debounce_timer_;
+ FilePath kde_config_dir_;
+ bool indirect_manual_;
+ bool auto_no_pac_;
+ bool reversed_exception_;
+
+ // We cache these settings whenever we re-read the kioslaverc file.
+ string_map_type string_table_;
+ strings_map_type strings_table_;
+
+ // Message loop of the file thread, for reading kioslaverc. If NULL,
+ // just read it directly (for testing). We also handle inotify events
+ // on this thread.
+ MessageLoopForIO* file_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImplKDE);
};
} // namespace
@@ -367,7 +815,7 @@ bool ProxyConfigServiceLinux::Delegate::GetProxyFromGConf(
return false;
}
// Check for an optional port.
- int port;
+ int port = 0;
gconf_getter_->GetInt((key + "port").c_str(), &port);
if (port != 0) {
// If a port is set and non-zero:
@@ -478,7 +926,7 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf(
}
// Check for authentication, just so we can warn.
- bool use_auth;
+ bool use_auth = false;
gconf_getter_->GetBoolean("/system/http_proxy/use_authentication",
&use_auth);
if (use_auth) {
@@ -498,6 +946,24 @@ bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf(
}
ProxyConfigServiceLinux::Delegate::Delegate(
+ base::EnvironmentVariableGetter* env_var_getter)
+ : env_var_getter_(env_var_getter),
+ glib_default_loop_(NULL), io_loop_(NULL) {
+ // Figure out which GConfSettingGetterImpl to use, if any.
+ switch (base::GetDesktopEnvironment(env_var_getter)) {
+ case base::DESKTOP_ENVIRONMENT_GNOME:
+ gconf_getter_.reset(new GConfSettingGetterImplGConf());
+ break;
+ case base::DESKTOP_ENVIRONMENT_KDE3:
+ case base::DESKTOP_ENVIRONMENT_KDE4:
+ gconf_getter_.reset(new GConfSettingGetterImplKDE(env_var_getter));
+ break;
+ case base::DESKTOP_ENVIRONMENT_OTHER:
+ break;
+ }
+}
+
+ProxyConfigServiceLinux::Delegate::Delegate(
base::EnvironmentVariableGetter* env_var_getter,
GConfSettingGetter* gconf_getter)
: env_var_getter_(env_var_getter), gconf_getter_(gconf_getter),
@@ -505,18 +971,19 @@ ProxyConfigServiceLinux::Delegate::Delegate(
}
void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig(
- MessageLoop* glib_default_loop, MessageLoop* io_loop) {
+ MessageLoop* glib_default_loop, MessageLoop* io_loop,
+ MessageLoopForIO* file_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 set up 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";
+ // If we are passed a NULL io_loop or file_loop, then we don't set up
+ // proxy setting change notifications. This should not be the usual
+ // case but is intended to simplify test setups.
+ if (!io_loop_ || !file_loop)
+ LOG(INFO) << "Monitoring of proxy 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
@@ -531,36 +998,27 @@ void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig(
// mislead us.
bool got_config = false;
- switch (base::GetDesktopEnvironment(env_var_getter_.get())) {
- case base::DESKTOP_ENVIRONMENT_GNOME:
- if (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
- // where ever we can find one.
- //
- // 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
- }
+ if (gconf_getter_.get()) {
+ if (gconf_getter_->Init(glib_default_loop, file_loop) &&
+ (!io_loop || !file_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 settings from " <<
+ gconf_getter_->GetDataSource();
+ // 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.
+ //
+ // 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_->Shutdown(); // Stop notifications
}
- break;
-
- case base::DESKTOP_ENVIRONMENT_KDE3:
- case base::DESKTOP_ENVIRONMENT_KDE4:
- NOTIMPLEMENTED() << "Bug 17363: obey KDE proxy settings.";
- break;
-
- case base::DESKTOP_ENVIRONMENT_OTHER:
- break;
+ }
}
if (!got_config) {
@@ -571,14 +1029,14 @@ void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig(
// work.
if (GetConfigFromEnv(&cached_config_)) {
cached_config_.set_id(1); // mark it as valid
- LOG(INFO) << "Obtained proxy setting from environment variables";
+ LOG(INFO) << "Obtained proxy settings from environment variables";
}
}
}
void ProxyConfigServiceLinux::Delegate::Reset() {
DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_);
- gconf_getter_->Release();
+ gconf_getter_->Shutdown();
cached_config_ = ProxyConfig();
}
@@ -592,11 +1050,11 @@ int ProxyConfigServiceLinux::Delegate::GetProxyConfig(ProxyConfig* config) {
return cached_config_.is_valid() ? OK : ERR_FAILED;
}
+// Depending on the GConfSettingGetter in use, this method will be called
+// on either the UI thread (GConf) or the file thread (KDE).
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_);
-
+ MessageLoop* required_loop = gconf_getter_->GetNotificationLoop();
+ DCHECK(!required_loop || MessageLoop::current() == required_loop);
ProxyConfig new_config;
bool valid = GetConfigFromGConf(&new_config);
if (valid)
@@ -626,15 +1084,17 @@ void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig(
}
void ProxyConfigServiceLinux::Delegate::PostDestroyTask() {
- if (MessageLoop::current() == glib_default_loop_) {
+ if (!gconf_getter_.get())
+ return;
+ MessageLoop* shutdown_loop = gconf_getter_->GetNotificationLoop();
+ if (!shutdown_loop || MessageLoop::current() == shutdown_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(
+ // Post to shutdown thread. Note that on browser shutdown, we may quit
+ // this MessageLoop and exit the program before ever running this.
+ shutdown_loop->PostTask(
FROM_HERE,
NewRunnableMethod(
this,
@@ -642,13 +1102,13 @@ void ProxyConfigServiceLinux::Delegate::PostDestroyTask() {
}
}
void ProxyConfigServiceLinux::Delegate::OnDestroy() {
- DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_);
- gconf_getter_->Release();
+ MessageLoop* shutdown_loop = gconf_getter_->GetNotificationLoop();
+ DCHECK(!shutdown_loop || MessageLoop::current() == shutdown_loop);
+ gconf_getter_->Shutdown();
}
ProxyConfigServiceLinux::ProxyConfigServiceLinux()
- : delegate_(new Delegate(base::EnvironmentVariableGetter::Create(),
- new GConfSettingGetterImpl())) {
+ : delegate_(new Delegate(base::EnvironmentVariableGetter::Create())) {
}
ProxyConfigServiceLinux::ProxyConfigServiceLinux(
diff --git a/net/proxy/proxy_config_service_linux.h b/net/proxy/proxy_config_service_linux.h
index a95a288..1da3749 100644
--- a/net/proxy/proxy_config_service_linux.h
+++ b/net/proxy/proxy_config_service_linux.h
@@ -24,22 +24,38 @@ namespace net {
class ProxyConfigServiceLinux : public ProxyConfigService {
public:
+ // Forward declaration of Delegate.
+ class Delegate;
+
class GConfSettingGetter {
public:
virtual ~GConfSettingGetter() {}
- // Initializes the class: obtains a gconf client, in the concrete
- // implementation. Returns true on success. Must be called before
- // using other methods.
- virtual bool Init() = 0;
+ // Initializes the class: obtains a gconf client, or simulates one, in
+ // the concrete implementations. Returns true on success. Must be called
+ // before using other methods, and should be called on the thread running
+ // the glib main loop.
+ // One of |glib_default_loop| and |file_loop| will be used for gconf calls
+ // or reading necessary files, depending on the implementation.
+ virtual bool Init(MessageLoop* glib_default_loop,
+ MessageLoopForIO* file_loop) = 0;
// Releases the gconf client, which clears cached directories and
// stops notifications.
- virtual void Release() = 0;
+ virtual void Shutdown() = 0;
// Requests notification of gconf setting changes for proxy
// settings. Returns true on success.
- virtual bool SetupNotification(void* callback_user_data) = 0;
+ virtual bool SetupNotification(Delegate* delegate) = 0;
+
+ // Returns the message loop for the thread on which this object
+ // handles notifications, and also on which it must be destroyed.
+ // Returns NULL if it does not matter.
+ virtual MessageLoop* GetNotificationLoop() = 0;
+
+ // Returns the data source's name (e.g. "gconf", "KDE", "test").
+ // Used only for diagnostic purposes (e.g. LOG(INFO) etc.).
+ virtual const char* GetDataSource() = 0;
// Gets a string type value from gconf and stores it in
// result. Returns false if the key is unset or on error. Must
@@ -79,8 +95,11 @@ class ProxyConfigServiceLinux : public ProxyConfigService {
class Delegate : public base::RefCountedThreadSafe<Delegate> {
public:
+ // Constructor receives env var getter implementation to use, and
+ // takes ownership of it. This is the normal constructor.
+ explicit Delegate(base::EnvironmentVariableGetter* env_var_getter);
// Constructor receives gconf and env var getter implementations
- // to use, and takes ownership of them.
+ // to use, and takes ownership of them. Used for testing.
Delegate(base::EnvironmentVariableGetter* env_var_getter,
GConfSettingGetter* gconf_getter);
// Synchronously obtains the proxy configuration. If gconf is
@@ -89,9 +108,11 @@ class ProxyConfigServiceLinux : public ProxyConfigService {
// the default glib main loop, and so this method must be called
// from the UI thread. The message loop for the IO thread is
// specified so that notifications can post tasks to it (and for
- // assertions).
+ // assertions). The message loop for the file thread is used to
+ // read any files needed to determine proxy settings.
void SetupAndFetchInitialConfig(MessageLoop* glib_default_loop,
- MessageLoop* io_loop);
+ MessageLoop* io_loop,
+ MessageLoopForIO* file_loop);
// Resets cached_config_ and releases the gconf_getter_, making it
// possible to call SetupAndFetchInitialConfig() again. Only used
// in testing.
@@ -182,8 +203,10 @@ class ProxyConfigServiceLinux : public ProxyConfigService {
}
void SetupAndFetchInitialConfig(MessageLoop* glib_default_loop,
- MessageLoop* io_loop) {
- delegate_->SetupAndFetchInitialConfig(glib_default_loop, io_loop);
+ MessageLoop* io_loop,
+ MessageLoopForIO* file_loop) {
+ delegate_->SetupAndFetchInitialConfig(glib_default_loop, io_loop,
+ file_loop);
}
void Reset() {
delegate_->Reset();
diff --git a/net/proxy/proxy_config_service_linux_unittest.cc b/net/proxy/proxy_config_service_linux_unittest.cc
index f208a06..a83694c 100644
--- a/net/proxy/proxy_config_service_linux_unittest.cc
+++ b/net/proxy/proxy_config_service_linux_unittest.cc
@@ -153,16 +153,25 @@ class MockGConfSettingGetter
values = zero_values;
}
- virtual bool Init() {
+ virtual bool Init(MessageLoop* glib_default_loop,
+ MessageLoopForIO* file_loop) {
return true;
}
- virtual void Release() {}
+ virtual void Shutdown() {}
- virtual bool SetupNotification(void* callback_user_data) {
+ virtual bool SetupNotification(ProxyConfigServiceLinux::Delegate* delegate) {
return true;
}
+ virtual MessageLoop* GetNotificationLoop() {
+ return NULL;
+ }
+
+ virtual const char* GetDataSource() {
+ return "test";
+ }
+
virtual bool GetString(const char* key, std::string* result) {
const char* value = strings_table.Get(key);
if (value) {
@@ -243,9 +252,12 @@ class SynchConfigGetter {
// all on the calling thread (meant to be the thread with the
// default glib main loop, which is the UI thread).
void SetupAndInitialFetch() {
+ MessageLoop* file_loop = io_thread_.message_loop();
+ DCHECK_EQ(MessageLoop::TYPE_IO, file_loop->type());
config_service_->Reset();
config_service_->SetupAndFetchInitialConfig(
- MessageLoop::current(), io_thread_.message_loop());
+ MessageLoop::current(), io_thread_.message_loop(),
+ static_cast<MessageLoopForIO*>(file_loop));
}
// Synchronously gets the proxy config.
int SyncGetProxyConfig(net::ProxyConfig* config) {
@@ -479,7 +491,7 @@ TEST(ProxyConfigServiceLinuxTest, BasicGConfTest) {
// Expected result.
false, // auto_detect
- GURL(), // pac_aurl
+ GURL(), // pac_url
MakeSingleProxyRules("www.google.com:88"), // proxy_rules
"", // proxy_bypass_list
false, // bypass_local_names
@@ -488,11 +500,11 @@ TEST(ProxyConfigServiceLinuxTest, BasicGConfTest) {
{
TEST_DESC("Per-scheme proxy rules"),
{ // Input.
- "manual", // mode
+ "manual", // mode
"", // autoconfig_url
"www.google.com", // http_host
"www.foo.com", // secure_host
- "ftpfoo.com", // ftp
+ "ftp.foo.com", // ftp
"", // socks
88, 110, 121, 0, // ports
TRUE, FALSE, FALSE, // use, same, auth
@@ -504,7 +516,7 @@ TEST(ProxyConfigServiceLinuxTest, BasicGConfTest) {
GURL(), // pac_url
MakeProxyPerSchemeRules("www.google.com:88", // proxy_rules
"www.foo.com:110",
- "ftpfoo.com:121"),
+ "ftp.foo.com:121"),
"", // proxy_bypass_list
false, // bypass_local_names
},
@@ -834,33 +846,6 @@ TEST(ProxyConfigServiceLinuxTest, BasicEnvTest) {
}
}
-// Verify that we fall back on consulting the environment when
-// GNOME-specific environment variables aren't available.
-TEST(ProxyConfigServiceLinuxTest, FallbackOnEnv) {
- MockEnvironmentVariableGetter* env_getter =
- new MockEnvironmentVariableGetter;
- MockGConfSettingGetter* gconf_getter = new MockGConfSettingGetter;
- ProxyConfigServiceLinux service(env_getter, gconf_getter);
-
- // Imagine we're:
- // 1) Running a non-GNOME desktop session:
- env_getter->values.DESKTOP_SESSION = "default";
- // 2) Have settings in gconf.
- gconf_getter->values.mode = "auto";
- gconf_getter->values.autoconfig_url = "http://incorrect/wpad.dat";
- // 3) But we have a proxy-specifying environment variable set:
- env_getter->values.auto_proxy = "http://correct/wpad.dat";
-
- ProxyConfig config;
-
- SynchConfigGetter sync_config_getter(&service);
- sync_config_getter.SetupAndInitialFetch();
- sync_config_getter.SyncGetProxyConfig(&config);
-
- // Then we expect the environment variable to win.
- EXPECT_EQ(GURL(env_getter->values.auto_proxy), config.pac_url);
-}
-
TEST(ProxyConfigServiceLinuxTest, GconfNotification) {
MockEnvironmentVariableGetter* env_getter =
new MockEnvironmentVariableGetter;
diff --git a/net/proxy/proxy_service.cc b/net/proxy/proxy_service.cc
index cc08e53..975b7df 100644
--- a/net/proxy/proxy_service.cc
+++ b/net/proxy/proxy_service.cc
@@ -217,11 +217,11 @@ ProxyService* ProxyService::Create(
const ProxyConfig* pc,
bool use_v8_resolver,
URLRequestContext* url_request_context,
- MessageLoop* io_loop) {
+ MessageLoop* io_loop, MessageLoop* file_loop) {
// Choose the system configuration service appropriate for each platform.
ProxyConfigService* proxy_config_service = pc ?
new ProxyConfigServiceFixed(*pc) :
- CreateSystemProxyConfigService(io_loop);
+ CreateSystemProxyConfigService(io_loop, file_loop);
ProxyResolver* proxy_resolver;
@@ -255,7 +255,7 @@ ProxyService* ProxyService::Create(
// static
ProxyService* ProxyService::CreateFixed(const ProxyConfig& pc) {
- return Create(&pc, false, NULL, NULL);
+ return Create(&pc, false, NULL, NULL, NULL);
}
// static
@@ -537,7 +537,7 @@ void ProxyService::DidCompletePacRequest(int config_id, int result_code) {
// static
ProxyConfigService* ProxyService::CreateSystemProxyConfigService(
- MessageLoop* io_loop) {
+ MessageLoop* io_loop, MessageLoop* file_loop) {
#if defined(OS_WIN)
return new ProxyConfigServiceWin();
#elif defined(OS_MACOSX)
@@ -551,11 +551,15 @@ ProxyConfigService* ProxyService::CreateSystemProxyConfigService(
// running gconf calls from.
MessageLoop* glib_default_loop = MessageLoopForUI::current();
+ // The file loop should be a MessageLoopForIO on Linux.
+ DCHECK_EQ(MessageLoop::TYPE_IO, file_loop->type());
+
// Synchronously fetch the current proxy config (since we are
- // running on glib_default_loop). Additionally register for gconf
- // notifications (delivered in |glib_default_loop|) to keep us
- // updated on when the proxy config has changed.
- linux_config_service->SetupAndFetchInitialConfig(glib_default_loop, io_loop);
+ // running on glib_default_loop). Additionally register for
+ // notifications (delivered in either |glib_default_loop| or
+ // |file_loop|) to keep us updated when the proxy config changes.
+ linux_config_service->SetupAndFetchInitialConfig(glib_default_loop, io_loop,
+ static_cast<MessageLoopForIO*>(file_loop));
return linux_config_service;
#else
diff --git a/net/proxy/proxy_service.h b/net/proxy/proxy_service.h
index 0ac3261..cd76453 100644
--- a/net/proxy/proxy_service.h
+++ b/net/proxy/proxy_service.h
@@ -117,8 +117,9 @@ class ProxyService : public base::RefCountedThreadSafe<ProxyService> {
// script needs to be fetched.
// |io_loop| points to the IO thread's message loop. It is only used
// when pc is NULL. If both pc and io_loop are NULL, then monitoring
- // of gconf setting changes will be disabled in
- // ProxyConfigServiceLinux.
+ // of proxy setting changes will be disabled in ProxyConfigServiceLinux.
+ // |file_loop| points to the file thread's message loop. It is used
+ // to read any files necessary to get proxy settings.
// ##########################################################################
// # See the warnings in net/proxy/proxy_resolver_v8.h describing the
// # multi-threading model. In order for this to be safe to use, *ALL* the
@@ -128,7 +129,7 @@ class ProxyService : public base::RefCountedThreadSafe<ProxyService> {
const ProxyConfig* pc,
bool use_v8_resolver,
URLRequestContext* url_request_context,
- MessageLoop* io_loop);
+ MessageLoop* io_loop, MessageLoop* file_loop);
// Convenience method that creates a proxy service using the
// specified fixed settings. |pc| must not be NULL.
@@ -152,7 +153,7 @@ class ProxyService : public base::RefCountedThreadSafe<ProxyService> {
// Creates a config service appropriate for this platform that fetches the
// system proxy settings.
static ProxyConfigService* CreateSystemProxyConfigService(
- MessageLoop* io_loop);
+ MessageLoop* io_loop, MessageLoop* file_loop);
// Creates a proxy resolver appropriate for this platform that doesn't rely
// on V8.