1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/proxy_resolution/proxy_config_service_linux.h"
11
12 #include <errno.h>
13 #include <limits.h>
14 #include <sys/inotify.h>
15 #include <unistd.h>
16
17 #include <map>
18 #include <memory>
19 #include <utility>
20
21 #include "base/files/file_descriptor_watcher_posix.h"
22 #include "base/files/file_path.h"
23 #include "base/files/file_util.h"
24 #include "base/files/scoped_file.h"
25 #include "base/functional/bind.h"
26 #include "base/logging.h"
27 #include "base/memory/ptr_util.h"
28 #include "base/memory/raw_ptr.h"
29 #include "base/nix/xdg_util.h"
30 #include "base/observer_list.h"
31 #include "base/strings/string_number_conversions.h"
32 #include "base/strings/string_split.h"
33 #include "base/strings/string_tokenizer.h"
34 #include "base/strings/string_util.h"
35 #include "base/task/sequenced_task_runner.h"
36 #include "base/task/single_thread_task_runner.h"
37 #include "base/task/task_traits.h"
38 #include "base/task/thread_pool.h"
39 #include "base/threading/thread_restrictions.h"
40 #include "base/timer/timer.h"
41 #include "net/base/proxy_server.h"
42 #include "net/base/proxy_string_util.h"
43
44 #if defined(USE_GIO)
45 #include <gio/gio.h>
46 #endif // defined(USE_GIO)
47
48 namespace net {
49
50 class ScopedAllowBlockingForSettingGetter : public base::ScopedAllowBlocking {};
51
52 namespace {
53
54 // This turns all rules with a hostname into wildcard matches, which will
55 // match not just the indicated hostname but also any hostname that ends with
56 // it.
RewriteRulesForSuffixMatching(ProxyBypassRules * out)57 void RewriteRulesForSuffixMatching(ProxyBypassRules* out) {
58 // Prepend a wildcard (*) to any hostname based rules, provided it isn't an IP
59 // address.
60 for (size_t i = 0; i < out->rules().size(); ++i) {
61 if (!out->rules()[i]->IsHostnamePatternRule())
62 continue;
63
64 const SchemeHostPortMatcherHostnamePatternRule* prev_rule =
65 static_cast<const SchemeHostPortMatcherHostnamePatternRule*>(
66 out->rules()[i].get());
67 out->ReplaceRule(i, prev_rule->GenerateSuffixMatchingRule());
68 }
69 }
70
71 // Given a proxy hostname from a setting, returns that hostname with
72 // an appropriate proxy server scheme prefix.
73 // scheme indicates the desired proxy scheme: usually http, with
74 // socks 4 or 5 as special cases.
75 // TODO(arindam): Remove URI string manipulation by using MapUrlSchemeToProxy.
FixupProxyHostScheme(ProxyServer::Scheme scheme,std::string host)76 std::string FixupProxyHostScheme(ProxyServer::Scheme scheme,
77 std::string host) {
78 if (scheme == ProxyServer::SCHEME_SOCKS5 &&
79 base::StartsWith(host, "socks4://",
80 base::CompareCase::INSENSITIVE_ASCII)) {
81 // We default to socks 5, but if the user specifically set it to
82 // socks4://, then use that.
83 scheme = ProxyServer::SCHEME_SOCKS4;
84 }
85 // Strip the scheme if any.
86 std::string::size_type colon = host.find("://");
87 if (colon != std::string::npos)
88 host = host.substr(colon + 3);
89 // If a username and perhaps password are specified, give a warning.
90 std::string::size_type at_sign = host.find("@");
91 // Should this be supported?
92 if (at_sign != std::string::npos) {
93 // ProxyConfig does not support authentication parameters, but Chrome
94 // will prompt for the password later. Disregard the
95 // authentication parameters and continue with this hostname.
96 LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709";
97 host = host.substr(at_sign + 1);
98 }
99 // If this is a socks proxy, prepend a scheme so as to tell
100 // ProxyServer. This also allows ProxyServer to choose the right
101 // default port.
102 if (scheme == ProxyServer::SCHEME_SOCKS4)
103 host = "socks4://" + host;
104 else if (scheme == ProxyServer::SCHEME_SOCKS5)
105 host = "socks5://" + host;
106 // If there is a trailing slash, remove it so |host| will parse correctly
107 // even if it includes a port number (since the slash is not numeric).
108 if (!host.empty() && host.back() == '/')
109 host.resize(host.length() - 1);
110 return host;
111 }
112
GetConfigOrDirect(const std::optional<ProxyConfigWithAnnotation> & optional_config)113 ProxyConfigWithAnnotation GetConfigOrDirect(
114 const std::optional<ProxyConfigWithAnnotation>& optional_config) {
115 if (optional_config)
116 return optional_config.value();
117
118 ProxyConfigWithAnnotation config = ProxyConfigWithAnnotation::CreateDirect();
119 return config;
120 }
121
122 } // namespace
123
124 ProxyConfigServiceLinux::Delegate::~Delegate() = default;
125
GetProxyFromEnvVarForScheme(std::string_view variable,ProxyServer::Scheme scheme,ProxyChain * result_chain)126 bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVarForScheme(
127 std::string_view variable,
128 ProxyServer::Scheme scheme,
129 ProxyChain* result_chain) {
130 std::string env_value;
131 if (!env_var_getter_->GetVar(variable, &env_value))
132 return false;
133
134 if (env_value.empty())
135 return false;
136
137 env_value = FixupProxyHostScheme(scheme, std::move(env_value));
138 ProxyChain proxy_chain =
139 ProxyUriToProxyChain(env_value, ProxyServer::SCHEME_HTTP);
140 if (proxy_chain.IsValid() &&
141 (proxy_chain.is_direct() || proxy_chain.is_single_proxy())) {
142 *result_chain = proxy_chain;
143 return true;
144 }
145 LOG(ERROR) << "Failed to parse environment variable " << variable;
146 return false;
147 }
148
GetProxyFromEnvVar(std::string_view variable,ProxyChain * result_chain)149 bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVar(
150 std::string_view variable,
151 ProxyChain* result_chain) {
152 return GetProxyFromEnvVarForScheme(variable, ProxyServer::SCHEME_HTTP,
153 result_chain);
154 }
155
156 std::optional<ProxyConfigWithAnnotation>
GetConfigFromEnv()157 ProxyConfigServiceLinux::Delegate::GetConfigFromEnv() {
158 ProxyConfig config;
159
160 // Check for automatic configuration first, in
161 // "auto_proxy". Possibly only the "environment_proxy" firefox
162 // extension has ever used this, but it still sounds like a good
163 // idea.
164 std::string auto_proxy;
165 if (env_var_getter_->GetVar("auto_proxy", &auto_proxy)) {
166 if (auto_proxy.empty()) {
167 // Defined and empty => autodetect
168 config.set_auto_detect(true);
169 } else {
170 // specified autoconfig URL
171 config.set_pac_url(GURL(auto_proxy));
172 }
173 return ProxyConfigWithAnnotation(
174 config, NetworkTrafficAnnotationTag(traffic_annotation_));
175 }
176 // "all_proxy" is a shortcut to avoid defining {http,https,ftp}_proxy.
177 ProxyChain proxy_chain;
178 if (GetProxyFromEnvVar("all_proxy", &proxy_chain)) {
179 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
180 config.proxy_rules().single_proxies.SetSingleProxyChain(proxy_chain);
181 } else {
182 bool have_http = GetProxyFromEnvVar("http_proxy", &proxy_chain);
183 if (have_http)
184 config.proxy_rules().proxies_for_http.SetSingleProxyChain(proxy_chain);
185 // It would be tempting to let http_proxy apply for all protocols
186 // if https_proxy and ftp_proxy are not defined. Googling turns up
187 // several documents that mention only http_proxy. But then the
188 // user really might not want to proxy https. And it doesn't seem
189 // like other apps do this. So we will refrain.
190 bool have_https = GetProxyFromEnvVar("https_proxy", &proxy_chain);
191 if (have_https)
192 config.proxy_rules().proxies_for_https.SetSingleProxyChain(proxy_chain);
193 bool have_ftp = GetProxyFromEnvVar("ftp_proxy", &proxy_chain);
194 if (have_ftp)
195 config.proxy_rules().proxies_for_ftp.SetSingleProxyChain(proxy_chain);
196 if (have_http || have_https || have_ftp) {
197 // mustn't change type unless some rules are actually set.
198 config.proxy_rules().type =
199 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
200 }
201 }
202 if (config.proxy_rules().empty()) {
203 // If the above were not defined, try for socks.
204 // For environment variables, we default to version 5, per the gnome
205 // documentation: http://library.gnome.org/devel/gnet/stable/gnet-socks.html
206 ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS5;
207 std::string env_version;
208 if (env_var_getter_->GetVar("SOCKS_VERSION", &env_version)
209 && env_version == "4")
210 scheme = ProxyServer::SCHEME_SOCKS4;
211 if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_chain)) {
212 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
213 config.proxy_rules().single_proxies.SetSingleProxyChain(proxy_chain);
214 }
215 }
216 // Look for the proxy bypass list.
217 std::string no_proxy;
218 env_var_getter_->GetVar("no_proxy", &no_proxy);
219 if (config.proxy_rules().empty()) {
220 // Having only "no_proxy" set, presumably to "*", makes it
221 // explicit that env vars do specify a configuration: having no
222 // rules specified only means the user explicitly asks for direct
223 // connections.
224 return !no_proxy.empty()
225 ? ProxyConfigWithAnnotation(
226 config, NetworkTrafficAnnotationTag(traffic_annotation_))
227 : std::optional<ProxyConfigWithAnnotation>();
228 }
229 // Note that this uses "suffix" matching. So a bypass of "google.com"
230 // is understood to mean a bypass of "*google.com".
231 config.proxy_rules().bypass_rules.ParseFromString(no_proxy);
232 RewriteRulesForSuffixMatching(&config.proxy_rules().bypass_rules);
233
234 return ProxyConfigWithAnnotation(
235 config, NetworkTrafficAnnotationTag(traffic_annotation_));
236 }
237
238 namespace {
239
240 const int kDebounceTimeoutMilliseconds = 250;
241
242 #if defined(USE_GIO)
243 const char kProxyGSettingsSchema[] = "org.gnome.system.proxy";
244
245 // This setting getter uses gsettings, as used in most GNOME 3 desktops.
246 class SettingGetterImplGSettings
247 : public ProxyConfigServiceLinux::SettingGetter {
248 public:
SettingGetterImplGSettings()249 SettingGetterImplGSettings()
250 : debounce_timer_(std::make_unique<base::OneShotTimer>()) {}
251
252 SettingGetterImplGSettings(const SettingGetterImplGSettings&) = delete;
253 SettingGetterImplGSettings& operator=(const SettingGetterImplGSettings&) =
254 delete;
255
~SettingGetterImplGSettings()256 ~SettingGetterImplGSettings() override {
257 // client_ should have been released before now, from
258 // Delegate::OnDestroy(), while running on the UI thread. However
259 // on exiting the process, it may happen that
260 // Delegate::OnDestroy() task is left pending on the glib loop
261 // after the loop was quit, and pending tasks may then be deleted
262 // without being run.
263 if (client_) {
264 // gsettings client was not cleaned up.
265 if (task_runner_->RunsTasksInCurrentSequence()) {
266 // We are on the UI thread so we can clean it safely.
267 VLOG(1) << "~SettingGetterImplGSettings: releasing gsettings client";
268 ShutDown();
269 } else {
270 LOG(WARNING) << "~SettingGetterImplGSettings: leaking gsettings client";
271 client_.ExtractAsDangling();
272 }
273 }
274 DCHECK(!client_);
275 }
276
277 // CheckVersion() must be called *before* Init()!
278 bool CheckVersion(base::Environment* env);
279
Init(const scoped_refptr<base::SingleThreadTaskRunner> & glib_task_runner)280 bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner)
281 override {
282 DCHECK(glib_task_runner->RunsTasksInCurrentSequence());
283 DCHECK(!client_);
284 DCHECK(!task_runner_.get());
285
286 if (!g_settings_schema_source_lookup(g_settings_schema_source_get_default(),
287 kProxyGSettingsSchema, TRUE) ||
288 !(client_ = g_settings_new(kProxyGSettingsSchema))) {
289 // It's not clear whether/when this can return NULL.
290 LOG(ERROR) << "Unable to create a gsettings client";
291 return false;
292 }
293 task_runner_ = glib_task_runner;
294 // We assume these all work if the above call worked.
295 http_client_ = g_settings_get_child(client_, "http");
296 https_client_ = g_settings_get_child(client_, "https");
297 ftp_client_ = g_settings_get_child(client_, "ftp");
298 socks_client_ = g_settings_get_child(client_, "socks");
299 DCHECK(http_client_ && https_client_ && ftp_client_ && socks_client_);
300 return true;
301 }
302
ShutDown()303 void ShutDown() override {
304 if (client_) {
305 DCHECK(task_runner_->RunsTasksInCurrentSequence());
306 // This also disables gsettings notifications.
307 g_object_unref(socks_client_.ExtractAsDangling());
308 g_object_unref(ftp_client_.ExtractAsDangling());
309 g_object_unref(https_client_.ExtractAsDangling());
310 g_object_unref(http_client_.ExtractAsDangling());
311 g_object_unref(client_.ExtractAsDangling());
312 // We only need to null client_ because it's the only one that we check.
313 client_ = nullptr;
314 task_runner_ = nullptr;
315 }
316 debounce_timer_.reset();
317 }
318
SetUpNotifications(ProxyConfigServiceLinux::Delegate * delegate)319 bool SetUpNotifications(
320 ProxyConfigServiceLinux::Delegate* delegate) override {
321 DCHECK(client_);
322 DCHECK(task_runner_->RunsTasksInCurrentSequence());
323 notify_delegate_ = delegate;
324 // We could watch for the change-event signal instead of changed, but
325 // since we have to watch more than one object, we'd still have to
326 // debounce change notifications. This is conceptually simpler.
327 g_signal_connect(G_OBJECT(client_.get()), "changed",
328 G_CALLBACK(OnGSettingsChangeNotification), this);
329 g_signal_connect(G_OBJECT(http_client_.get()), "changed",
330 G_CALLBACK(OnGSettingsChangeNotification), this);
331 g_signal_connect(G_OBJECT(https_client_.get()), "changed",
332 G_CALLBACK(OnGSettingsChangeNotification), this);
333 g_signal_connect(G_OBJECT(ftp_client_.get()), "changed",
334 G_CALLBACK(OnGSettingsChangeNotification), this);
335 g_signal_connect(G_OBJECT(socks_client_.get()), "changed",
336 G_CALLBACK(OnGSettingsChangeNotification), this);
337 // Simulate a change to avoid possibly losing updates before this point.
338 OnChangeNotification();
339 return true;
340 }
341
GetNotificationTaskRunner()342 const scoped_refptr<base::SequencedTaskRunner>& GetNotificationTaskRunner()
343 override {
344 return task_runner_;
345 }
346
GetString(StringSetting key,std::string * result)347 bool GetString(StringSetting key, std::string* result) override {
348 DCHECK(client_);
349 switch (key) {
350 case PROXY_MODE:
351 return GetStringByPath(client_, "mode", result);
352 case PROXY_AUTOCONF_URL:
353 return GetStringByPath(client_, "autoconfig-url", result);
354 case PROXY_HTTP_HOST:
355 return GetStringByPath(http_client_, "host", result);
356 case PROXY_HTTPS_HOST:
357 return GetStringByPath(https_client_, "host", result);
358 case PROXY_FTP_HOST:
359 return GetStringByPath(ftp_client_, "host", result);
360 case PROXY_SOCKS_HOST:
361 return GetStringByPath(socks_client_, "host", result);
362 }
363 return false; // Placate compiler.
364 }
GetBool(BoolSetting key,bool * result)365 bool GetBool(BoolSetting key, bool* result) override {
366 DCHECK(client_);
367 switch (key) {
368 case PROXY_USE_HTTP_PROXY:
369 // Although there is an "enabled" boolean in http_client_, it is not set
370 // to true by the proxy config utility. We ignore it and return false.
371 return false;
372 case PROXY_USE_SAME_PROXY:
373 // Similarly, although there is a "use-same-proxy" boolean in client_,
374 // it is never set to false by the proxy config utility. We ignore it.
375 return false;
376 case PROXY_USE_AUTHENTICATION:
377 // There is also no way to set this in the proxy config utility, but it
378 // doesn't hurt us to get the actual setting (unlike the two above).
379 return GetBoolByPath(http_client_, "use-authentication", result);
380 }
381 return false; // Placate compiler.
382 }
GetInt(IntSetting key,int * result)383 bool GetInt(IntSetting key, int* result) override {
384 DCHECK(client_);
385 switch (key) {
386 case PROXY_HTTP_PORT:
387 return GetIntByPath(http_client_, "port", result);
388 case PROXY_HTTPS_PORT:
389 return GetIntByPath(https_client_, "port", result);
390 case PROXY_FTP_PORT:
391 return GetIntByPath(ftp_client_, "port", result);
392 case PROXY_SOCKS_PORT:
393 return GetIntByPath(socks_client_, "port", result);
394 }
395 return false; // Placate compiler.
396 }
GetStringList(StringListSetting key,std::vector<std::string> * result)397 bool GetStringList(StringListSetting key,
398 std::vector<std::string>* result) override {
399 DCHECK(client_);
400 switch (key) {
401 case PROXY_IGNORE_HOSTS:
402 return GetStringListByPath(client_, "ignore-hosts", result);
403 }
404 return false; // Placate compiler.
405 }
406
BypassListIsReversed()407 bool BypassListIsReversed() override {
408 // This is a KDE-specific setting.
409 return false;
410 }
411
UseSuffixMatching()412 bool UseSuffixMatching() override { return false; }
413
414 private:
GetStringByPath(GSettings * client,std::string_view key,std::string * result)415 bool GetStringByPath(GSettings* client,
416 std::string_view key,
417 std::string* result) {
418 DCHECK(task_runner_->RunsTasksInCurrentSequence());
419 gchar* value = g_settings_get_string(client, key.data());
420 if (!value)
421 return false;
422 *result = value;
423 g_free(value);
424 return true;
425 }
GetBoolByPath(GSettings * client,std::string_view key,bool * result)426 bool GetBoolByPath(GSettings* client, std::string_view key, bool* result) {
427 DCHECK(task_runner_->RunsTasksInCurrentSequence());
428 *result = static_cast<bool>(g_settings_get_boolean(client, key.data()));
429 return true;
430 }
GetIntByPath(GSettings * client,std::string_view key,int * result)431 bool GetIntByPath(GSettings* client, std::string_view key, int* result) {
432 DCHECK(task_runner_->RunsTasksInCurrentSequence());
433 *result = g_settings_get_int(client, key.data());
434 return true;
435 }
GetStringListByPath(GSettings * client,std::string_view key,std::vector<std::string> * result)436 bool GetStringListByPath(GSettings* client,
437 std::string_view key,
438 std::vector<std::string>* result) {
439 DCHECK(task_runner_->RunsTasksInCurrentSequence());
440 gchar** list = g_settings_get_strv(client, key.data());
441 if (!list)
442 return false;
443 for (size_t i = 0; list[i]; ++i) {
444 result->push_back(static_cast<char*>(list[i]));
445 g_free(list[i]);
446 }
447 g_free(list);
448 return true;
449 }
450
451 // This is the callback from the debounce timer.
OnDebouncedNotification()452 void OnDebouncedNotification() {
453 DCHECK(task_runner_->RunsTasksInCurrentSequence());
454 CHECK(notify_delegate_);
455 // Forward to a method on the proxy config service delegate object.
456 notify_delegate_->OnCheckProxyConfigSettings();
457 }
458
OnChangeNotification()459 void OnChangeNotification() {
460 // We don't use Reset() because the timer may not yet be running.
461 // (In that case Stop() is a no-op.)
462 debounce_timer_->Stop();
463 debounce_timer_->Start(
464 FROM_HERE, base::Milliseconds(kDebounceTimeoutMilliseconds), this,
465 &SettingGetterImplGSettings::OnDebouncedNotification);
466 }
467
468 // gsettings notification callback, dispatched on the default glib main loop.
OnGSettingsChangeNotification(GSettings * client,gchar * key,gpointer user_data)469 static void OnGSettingsChangeNotification(GSettings* client, gchar* key,
470 gpointer user_data) {
471 VLOG(1) << "gsettings change notification for key " << key;
472 // We don't track which key has changed, just that something did change.
473 SettingGetterImplGSettings* setting_getter =
474 reinterpret_cast<SettingGetterImplGSettings*>(user_data);
475 setting_getter->OnChangeNotification();
476 }
477
478 raw_ptr<GSettings> client_ = nullptr;
479 raw_ptr<GSettings> http_client_ = nullptr;
480 raw_ptr<GSettings> https_client_ = nullptr;
481 raw_ptr<GSettings> ftp_client_ = nullptr;
482 raw_ptr<GSettings> socks_client_ = nullptr;
483 raw_ptr<ProxyConfigServiceLinux::Delegate> notify_delegate_ = nullptr;
484 std::unique_ptr<base::OneShotTimer> debounce_timer_;
485
486 // Task runner for the thread that we make gsettings calls on. It should
487 // be the UI thread and all our methods should be called on this
488 // thread. Only for assertions.
489 scoped_refptr<base::SequencedTaskRunner> task_runner_;
490 };
491
CheckVersion(base::Environment * env)492 bool SettingGetterImplGSettings::CheckVersion(
493 base::Environment* env) {
494 // CheckVersion() must be called *before* Init()!
495 DCHECK(!client_);
496
497 GSettings* client = nullptr;
498 if (g_settings_schema_source_lookup(g_settings_schema_source_get_default(),
499 kProxyGSettingsSchema, TRUE)) {
500 client = g_settings_new(kProxyGSettingsSchema);
501 }
502 if (!client) {
503 VLOG(1) << "Cannot create gsettings client.";
504 return false;
505 }
506 g_object_unref(client);
507
508 VLOG(1) << "All gsettings tests OK. Will get proxy config from gsettings.";
509 return true;
510 }
511 #endif // defined(USE_GIO)
512
513 // Converts |value| from a decimal string to an int. If there was a failure
514 // parsing, returns |default_value|.
StringToIntOrDefault(std::string_view value,int default_value)515 int StringToIntOrDefault(std::string_view value, int default_value) {
516 int result;
517 if (base::StringToInt(value, &result))
518 return result;
519 return default_value;
520 }
521
522 // This is the KDE version that reads kioslaverc and simulates gsettings.
523 // Doing this allows the main Delegate code, as well as the unit tests
524 // for it, to stay the same - and the settings map fairly well besides.
525 class SettingGetterImplKDE : public ProxyConfigServiceLinux::SettingGetter {
526 public:
SettingGetterImplKDE(base::Environment * env_var_getter)527 explicit SettingGetterImplKDE(base::Environment* env_var_getter)
528 : debounce_timer_(std::make_unique<base::OneShotTimer>()),
529 env_var_getter_(env_var_getter) {
530 // This has to be called on the UI thread (http://crbug.com/69057).
531 ScopedAllowBlockingForSettingGetter allow_blocking;
532
533 // Derive the location(s) of the kde config dir from the environment.
534 std::string home;
535 if (env_var_getter->GetVar("KDEHOME", &home) && !home.empty()) {
536 // $KDEHOME is set. Use it unconditionally.
537 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(base::FilePath(home)));
538 } else {
539 // $KDEHOME is unset. Try to figure out what to use. This seems to be
540 // the common case on most distributions.
541 if (!env_var_getter->GetVar(base::env_vars::kHome, &home))
542 // User has no $HOME? Give up. Later we'll report the failure.
543 return;
544 auto desktop = base::nix::GetDesktopEnvironment(env_var_getter);
545 if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE3) {
546 // KDE3 always uses .kde for its configuration.
547 base::FilePath kde_path = base::FilePath(home).Append(".kde");
548 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(kde_path));
549 } else if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE4) {
550 // Some distributions patch KDE4 to use .kde4 instead of .kde, so that
551 // both can be installed side-by-side. Sadly they don't all do this, and
552 // they don't always do this: some distributions have started switching
553 // back as well. So if there is a .kde4 directory, check the timestamps
554 // of the config directories within and use the newest one.
555 // Note that we should currently be running in the UI thread, because in
556 // the gsettings version, that is the only thread that can access the
557 // proxy settings (a gsettings restriction). As noted below, the initial
558 // read of the proxy settings will be done in this thread anyway, so we
559 // check for .kde4 here in this thread as well.
560 base::FilePath kde3_path = base::FilePath(home).Append(".kde");
561 base::FilePath kde3_config = KDEHomeToConfigPath(kde3_path);
562 base::FilePath kde4_path = base::FilePath(home).Append(".kde4");
563 base::FilePath kde4_config = KDEHomeToConfigPath(kde4_path);
564 bool use_kde4 = false;
565 if (base::DirectoryExists(kde4_path)) {
566 base::File::Info kde3_info;
567 base::File::Info kde4_info;
568 if (base::GetFileInfo(kde4_config, &kde4_info)) {
569 if (base::GetFileInfo(kde3_config, &kde3_info)) {
570 use_kde4 = kde4_info.last_modified >= kde3_info.last_modified;
571 } else {
572 use_kde4 = true;
573 }
574 }
575 }
576 if (use_kde4) {
577 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(kde4_path));
578 } else {
579 kde_config_dirs_.emplace_back(KDEHomeToConfigPath(kde3_path));
580 }
581 } else if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE5 ||
582 desktop == base::nix::DESKTOP_ENVIRONMENT_KDE6) {
583 // KDE 5 migrated to ~/.config for storing kioslaverc.
584 kde_config_dirs_.emplace_back(base::FilePath(home).Append(".config"));
585
586 // kioslaverc also can be stored in any of XDG_CONFIG_DIRS
587 std::string config_dirs;
588 if (env_var_getter_->GetVar("XDG_CONFIG_DIRS", &config_dirs)) {
589 auto dirs = base::SplitString(config_dirs, ":", base::KEEP_WHITESPACE,
590 base::SPLIT_WANT_NONEMPTY);
591 for (const auto& dir : dirs) {
592 kde_config_dirs_.emplace_back(dir);
593 }
594 }
595
596 // Reverses the order of paths to store them in ascending order of
597 // priority
598 std::reverse(kde_config_dirs_.begin(), kde_config_dirs_.end());
599 }
600 }
601 }
602
603 SettingGetterImplKDE(const SettingGetterImplKDE&) = delete;
604 SettingGetterImplKDE& operator=(const SettingGetterImplKDE&) = delete;
605
~SettingGetterImplKDE()606 ~SettingGetterImplKDE() override {
607 // inotify_fd_ should have been closed before now, from
608 // Delegate::OnDestroy(), while running on the file thread. However
609 // on exiting the process, it may happen that Delegate::OnDestroy()
610 // task is left pending on the file loop after the loop was quit,
611 // and pending tasks may then be deleted without being run.
612 // Here in the KDE version, we can safely close the file descriptor
613 // anyway. (Not that it really matters; the process is exiting.)
614 if (inotify_fd_ >= 0)
615 ShutDown();
616 DCHECK_LT(inotify_fd_, 0);
617 }
618
Init(const scoped_refptr<base::SingleThreadTaskRunner> & glib_task_runner)619 bool Init(const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner)
620 override {
621 // This has to be called on the UI thread (http://crbug.com/69057).
622 ScopedAllowBlockingForSettingGetter allow_blocking;
623 DCHECK_LT(inotify_fd_, 0);
624 inotify_fd_ = inotify_init();
625 if (inotify_fd_ < 0) {
626 PLOG(ERROR) << "inotify_init failed";
627 return false;
628 }
629 if (!base::SetNonBlocking(inotify_fd_)) {
630 PLOG(ERROR) << "base::SetNonBlocking failed";
631 close(inotify_fd_);
632 inotify_fd_ = -1;
633 return false;
634 }
635
636 constexpr base::TaskTraits kTraits = {base::TaskPriority::USER_VISIBLE,
637 base::MayBlock()};
638 file_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(kTraits);
639
640 // The initial read is done on the current thread, not
641 // |file_task_runner_|, since we will need to have it for
642 // SetUpAndFetchInitialConfig().
643 UpdateCachedSettings();
644 return true;
645 }
646
ShutDown()647 void ShutDown() override {
648 if (inotify_fd_ >= 0) {
649 ResetCachedSettings();
650 inotify_watcher_.reset();
651 close(inotify_fd_);
652 inotify_fd_ = -1;
653 }
654 debounce_timer_.reset();
655 }
656
SetUpNotifications(ProxyConfigServiceLinux::Delegate * delegate)657 bool SetUpNotifications(
658 ProxyConfigServiceLinux::Delegate* delegate) override {
659 DCHECK_GE(inotify_fd_, 0);
660 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
661 // We can't just watch the kioslaverc file directly, since KDE will write
662 // a new copy of it and then rename it whenever settings are changed and
663 // inotify watches inodes (so we'll be watching the old deleted file after
664 // the first change, and it will never change again). So, we watch the
665 // directory instead. We then act only on changes to the kioslaverc entry.
666 // TODO(eroman): What if the file is deleted? (handle with IN_DELETE).
667 size_t failed_dirs = 0;
668 for (const auto& kde_config_dir : kde_config_dirs_) {
669 if (inotify_add_watch(inotify_fd_, kde_config_dir.value().c_str(),
670 IN_MODIFY | IN_MOVED_TO) < 0) {
671 ++failed_dirs;
672 }
673 }
674 // Fail if inotify_add_watch failed with every directory
675 if (failed_dirs == kde_config_dirs_.size()) {
676 return false;
677 }
678 notify_delegate_ = delegate;
679 inotify_watcher_ = base::FileDescriptorWatcher::WatchReadable(
680 inotify_fd_,
681 base::BindRepeating(&SettingGetterImplKDE::OnChangeNotification,
682 base::Unretained(this)));
683 // Simulate a change to avoid possibly losing updates before this point.
684 OnChangeNotification();
685 return true;
686 }
687
GetNotificationTaskRunner()688 const scoped_refptr<base::SequencedTaskRunner>& GetNotificationTaskRunner()
689 override {
690 return file_task_runner_;
691 }
692
GetString(StringSetting key,std::string * result)693 bool GetString(StringSetting key, std::string* result) override {
694 auto it = string_table_.find(key);
695 if (it == string_table_.end())
696 return false;
697 *result = it->second;
698 return true;
699 }
GetBool(BoolSetting key,bool * result)700 bool GetBool(BoolSetting key, bool* result) override {
701 // We don't ever have any booleans.
702 return false;
703 }
GetInt(IntSetting key,int * result)704 bool GetInt(IntSetting key, int* result) override {
705 // We don't ever have any integers. (See AddProxy() below about ports.)
706 return false;
707 }
GetStringList(StringListSetting key,std::vector<std::string> * result)708 bool GetStringList(StringListSetting key,
709 std::vector<std::string>* result) override {
710 auto it = strings_table_.find(key);
711 if (it == strings_table_.end())
712 return false;
713 *result = it->second;
714 return true;
715 }
716
BypassListIsReversed()717 bool BypassListIsReversed() override { return reversed_bypass_list_; }
718
UseSuffixMatching()719 bool UseSuffixMatching() override { return true; }
720
721 private:
ResetCachedSettings()722 void ResetCachedSettings() {
723 string_table_.clear();
724 strings_table_.clear();
725 indirect_manual_ = false;
726 auto_no_pac_ = false;
727 reversed_bypass_list_ = false;
728 }
729
KDEHomeToConfigPath(const base::FilePath & kde_home)730 base::FilePath KDEHomeToConfigPath(const base::FilePath& kde_home) {
731 return kde_home.Append("share").Append("config");
732 }
733
AddProxy(StringSetting host_key,const std::string & value)734 void AddProxy(StringSetting host_key, const std::string& value) {
735 if (value.empty() || value.substr(0, 3) == "//:")
736 // No proxy.
737 return;
738 size_t space = value.find(' ');
739 if (space != std::string::npos) {
740 // Newer versions of KDE use a space rather than a colon to separate the
741 // port number from the hostname. If we find this, we need to convert it.
742 std::string fixed = value;
743 fixed[space] = ':';
744 string_table_[host_key] = std::move(fixed);
745 } else {
746 // We don't need to parse the port number out; GetProxyFromSettings()
747 // would only append it right back again. So we just leave the port
748 // number right in the host string.
749 string_table_[host_key] = value;
750 }
751 }
752
AddHostList(StringListSetting key,const std::string & value)753 void AddHostList(StringListSetting key, const std::string& value) {
754 std::vector<std::string> tokens;
755 base::StringTokenizer tk(value, ", ");
756 while (tk.GetNext()) {
757 std::string token = tk.token();
758 if (!token.empty())
759 tokens.push_back(token);
760 }
761 strings_table_[key] = tokens;
762 }
763
AddKDESetting(const std::string & key,const std::string & value)764 void AddKDESetting(const std::string& key, const std::string& value) {
765 if (key == "ProxyType") {
766 const char* mode = "none";
767 indirect_manual_ = false;
768 auto_no_pac_ = false;
769 int int_value = StringToIntOrDefault(value, 0);
770 switch (int_value) {
771 case 1: // Manual configuration.
772 mode = "manual";
773 break;
774 case 2: // PAC URL.
775 mode = "auto";
776 break;
777 case 3: // WPAD.
778 mode = "auto";
779 auto_no_pac_ = true;
780 break;
781 case 4: // Indirect manual via environment variables.
782 mode = "manual";
783 indirect_manual_ = true;
784 break;
785 default: // No proxy, or maybe kioslaverc syntax error.
786 break;
787 }
788 string_table_[PROXY_MODE] = mode;
789 } else if (key == "Proxy Config Script") {
790 string_table_[PROXY_AUTOCONF_URL] = value;
791 } else if (key == "httpProxy") {
792 AddProxy(PROXY_HTTP_HOST, value);
793 } else if (key == "httpsProxy") {
794 AddProxy(PROXY_HTTPS_HOST, value);
795 } else if (key == "ftpProxy") {
796 AddProxy(PROXY_FTP_HOST, value);
797 } else if (key == "socksProxy") {
798 // Older versions of KDE configure SOCKS in a weird way involving
799 // LD_PRELOAD and a library that intercepts network calls to SOCKSify
800 // them. We don't support it. KDE 4.8 added a proper SOCKS setting.
801 AddProxy(PROXY_SOCKS_HOST, value);
802 } else if (key == "ReversedException") {
803 // We count "true" or any nonzero number as true, otherwise false.
804 // A failure parsing the integer will also mean false.
805 reversed_bypass_list_ =
806 (value == "true" || StringToIntOrDefault(value, 0) != 0);
807 } else if (key == "NoProxyFor") {
808 AddHostList(PROXY_IGNORE_HOSTS, value);
809 } else if (key == "AuthMode") {
810 // Check for authentication, just so we can warn.
811 int mode = StringToIntOrDefault(value, 0);
812 if (mode) {
813 // ProxyConfig does not support authentication parameters, but
814 // Chrome will prompt for the password later. So we ignore this.
815 LOG(WARNING) <<
816 "Proxy authentication parameters ignored, see bug 16709";
817 }
818 }
819 }
820
ResolveIndirect(StringSetting key)821 void ResolveIndirect(StringSetting key) {
822 auto it = string_table_.find(key);
823 if (it != string_table_.end()) {
824 std::string value;
825 if (env_var_getter_->GetVar(it->second.c_str(), &value))
826 it->second = value;
827 else
828 string_table_.erase(it);
829 }
830 }
831
ResolveIndirectList(StringListSetting key)832 void ResolveIndirectList(StringListSetting key) {
833 auto it = strings_table_.find(key);
834 if (it != strings_table_.end()) {
835 std::string value;
836 if (!it->second.empty() &&
837 env_var_getter_->GetVar(it->second[0].c_str(), &value))
838 AddHostList(key, value);
839 else
840 strings_table_.erase(it);
841 }
842 }
843
844 // The settings in kioslaverc could occur in any order, but some affect
845 // others. Rather than read the whole file in and then query them in an
846 // order that allows us to handle that, we read the settings in whatever
847 // order they occur and do any necessary tweaking after we finish.
ResolveModeEffects()848 void ResolveModeEffects() {
849 if (indirect_manual_) {
850 ResolveIndirect(PROXY_HTTP_HOST);
851 ResolveIndirect(PROXY_HTTPS_HOST);
852 ResolveIndirect(PROXY_FTP_HOST);
853 ResolveIndirect(PROXY_SOCKS_HOST);
854 ResolveIndirectList(PROXY_IGNORE_HOSTS);
855 }
856 if (auto_no_pac_) {
857 // Remove the PAC URL; we're not supposed to use it.
858 string_table_.erase(PROXY_AUTOCONF_URL);
859 }
860 }
861
862 // Reads kioslaverc from all paths one line at a time and calls
863 // AddKDESetting() to add each relevant name-value pair to the appropriate
864 // value table. Each value can be overwritten by values from configs from
865 // the following paths.
UpdateCachedSettings()866 void UpdateCachedSettings() {
867 bool at_least_one_kioslaverc_opened = false;
868 for (const auto& kde_config_dir : kde_config_dirs_) {
869 base::FilePath kioslaverc = kde_config_dir.Append("kioslaverc");
870 base::ScopedFILE input(base::OpenFile(kioslaverc, "r"));
871 if (!input.get())
872 continue;
873
874 // Reset cached settings once only if some config was successfully opened
875 if (!at_least_one_kioslaverc_opened) {
876 ResetCachedSettings();
877 }
878 at_least_one_kioslaverc_opened = true;
879 bool in_proxy_settings = false;
880 bool line_too_long = false;
881 char line[BUFFER_SIZE];
882 // fgets() will return NULL on EOF or error.
883 while (fgets(line, sizeof(line), input.get())) {
884 // fgets() guarantees the line will be properly terminated.
885 size_t length = strlen(line);
886 if (!length)
887 continue;
888 // This should be true even with CRLF endings.
889 if (line[length - 1] != '\n') {
890 line_too_long = true;
891 continue;
892 }
893 if (line_too_long) {
894 // The previous line had no line ending, but this one does. This is
895 // the end of the line that was too long, so warn here and skip it.
896 LOG(WARNING) << "skipped very long line in " << kioslaverc.value();
897 line_too_long = false;
898 continue;
899 }
900 // Remove the LF at the end, and the CR if there is one.
901 line[--length] = '\0';
902 if (length && line[length - 1] == '\r')
903 line[--length] = '\0';
904 // Now parse the line.
905 if (line[0] == '[') {
906 // Switching sections. All we care about is whether this is
907 // the (a?) proxy settings section, for both KDE3 and KDE4.
908 in_proxy_settings = !strncmp(line, "[Proxy Settings]", 16);
909 } else if (in_proxy_settings) {
910 // A regular line, in the (a?) proxy settings section.
911 char* split = strchr(line, '=');
912 // Skip this line if it does not contain an = sign.
913 if (!split)
914 continue;
915 // Split the line on the = and advance |split|.
916 *(split++) = 0;
917 std::string key = line;
918 std::string value = split;
919 base::TrimWhitespaceASCII(key, base::TRIM_ALL, &key);
920 base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
921 // Skip this line if the key name is empty.
922 if (key.empty())
923 continue;
924 // Is the value name localized?
925 if (key[key.length() - 1] == ']') {
926 // Find the matching bracket.
927 length = key.rfind('[');
928 // Skip this line if the localization indicator is malformed.
929 if (length == std::string::npos)
930 continue;
931 // Trim the localization indicator off.
932 key.resize(length);
933 // Remove any resulting trailing whitespace.
934 base::TrimWhitespaceASCII(key, base::TRIM_TRAILING, &key);
935 // Skip this line if the key name is now empty.
936 if (key.empty())
937 continue;
938 }
939 // Now fill in the tables.
940 AddKDESetting(key, value);
941 }
942 }
943 if (ferror(input.get()))
944 LOG(ERROR) << "error reading " << kioslaverc.value();
945 }
946 if (at_least_one_kioslaverc_opened) {
947 ResolveModeEffects();
948 }
949 }
950
951 // This is the callback from the debounce timer.
OnDebouncedNotification()952 void OnDebouncedNotification() {
953 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
954 VLOG(1) << "inotify change notification for kioslaverc";
955 UpdateCachedSettings();
956 CHECK(notify_delegate_);
957 // Forward to a method on the proxy config service delegate object.
958 notify_delegate_->OnCheckProxyConfigSettings();
959 }
960
961 // Called by OnFileCanReadWithoutBlocking() on the file thread. Reads
962 // from the inotify file descriptor and starts up a debounce timer if
963 // an event for kioslaverc is seen.
OnChangeNotification()964 void OnChangeNotification() {
965 DCHECK_GE(inotify_fd_, 0);
966 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
967 char event_buf[(sizeof(inotify_event) + NAME_MAX + 1) * 4];
968 bool kioslaverc_touched = false;
969 ssize_t r;
970 while ((r = read(inotify_fd_, event_buf, sizeof(event_buf))) > 0) {
971 // inotify returns variable-length structures, which is why we have
972 // this strange-looking loop instead of iterating through an array.
973 char* event_ptr = event_buf;
974 while (event_ptr < event_buf + r) {
975 inotify_event* event = reinterpret_cast<inotify_event*>(event_ptr);
976 // The kernel always feeds us whole events.
977 CHECK_LE(event_ptr + sizeof(inotify_event), event_buf + r);
978 CHECK_LE(event->name + event->len, event_buf + r);
979 if (!strcmp(event->name, "kioslaverc"))
980 kioslaverc_touched = true;
981 // Advance the pointer just past the end of the filename.
982 event_ptr = event->name + event->len;
983 }
984 // We keep reading even if |kioslaverc_touched| is true to drain the
985 // inotify event queue.
986 }
987 if (!r)
988 // Instead of returning -1 and setting errno to EINVAL if there is not
989 // enough buffer space, older kernels (< 2.6.21) return 0. Simulate the
990 // new behavior (EINVAL) so we can reuse the code below.
991 errno = EINVAL;
992 if (errno != EAGAIN) {
993 PLOG(WARNING) << "error reading inotify file descriptor";
994 if (errno == EINVAL) {
995 // Our buffer is not large enough to read the next event. This should
996 // not happen (because its size is calculated to always be sufficiently
997 // large), but if it does we'd warn continuously since |inotify_fd_|
998 // would be forever ready to read. Close it and stop watching instead.
999 LOG(ERROR) << "inotify failure; no longer watching kioslaverc!";
1000 inotify_watcher_.reset();
1001 close(inotify_fd_);
1002 inotify_fd_ = -1;
1003 }
1004 }
1005 if (kioslaverc_touched) {
1006 LOG(ERROR) << "kioslaverc_touched";
1007 // We don't use Reset() because the timer may not yet be running.
1008 // (In that case Stop() is a no-op.)
1009 debounce_timer_->Stop();
1010 debounce_timer_->Start(
1011 FROM_HERE, base::Milliseconds(kDebounceTimeoutMilliseconds), this,
1012 &SettingGetterImplKDE::OnDebouncedNotification);
1013 }
1014 }
1015
1016 typedef std::map<StringSetting, std::string> string_map_type;
1017 typedef std::map<StringListSetting,
1018 std::vector<std::string> > strings_map_type;
1019
1020 int inotify_fd_ = -1;
1021 std::unique_ptr<base::FileDescriptorWatcher::Controller> inotify_watcher_;
1022 raw_ptr<ProxyConfigServiceLinux::Delegate> notify_delegate_ = nullptr;
1023 std::unique_ptr<base::OneShotTimer> debounce_timer_;
1024 std::vector<base::FilePath> kde_config_dirs_;
1025 bool indirect_manual_ = false;
1026 bool auto_no_pac_ = false;
1027 bool reversed_bypass_list_ = false;
1028 // We don't own |env_var_getter_|. It's safe to hold a pointer to it, since
1029 // both it and us are owned by ProxyConfigServiceLinux::Delegate, and have the
1030 // same lifetime.
1031 raw_ptr<base::Environment> env_var_getter_;
1032
1033 // We cache these settings whenever we re-read the kioslaverc file.
1034 string_map_type string_table_;
1035 strings_map_type strings_table_;
1036
1037 // Task runner for doing blocking file IO on, as well as handling inotify
1038 // events on.
1039 scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
1040 };
1041
1042 } // namespace
1043
GetProxyFromSettings(SettingGetter::StringSetting host_key,ProxyServer * result_server)1044 bool ProxyConfigServiceLinux::Delegate::GetProxyFromSettings(
1045 SettingGetter::StringSetting host_key,
1046 ProxyServer* result_server) {
1047 std::string host;
1048 if (!setting_getter_->GetString(host_key, &host) || host.empty()) {
1049 // Unset or empty.
1050 return false;
1051 }
1052 // Check for an optional port.
1053 int port = 0;
1054 SettingGetter::IntSetting port_key =
1055 SettingGetter::HostSettingToPortSetting(host_key);
1056 setting_getter_->GetInt(port_key, &port);
1057 if (port != 0) {
1058 // If a port is set and non-zero:
1059 host += ":" + base::NumberToString(port);
1060 }
1061
1062 // gsettings settings do not appear to distinguish between SOCKS version. We
1063 // default to version 5. For more information on this policy decision, see:
1064 // http://code.google.com/p/chromium/issues/detail?id=55912#c2
1065 ProxyServer::Scheme scheme = host_key == SettingGetter::PROXY_SOCKS_HOST
1066 ? ProxyServer::SCHEME_SOCKS5
1067 : ProxyServer::SCHEME_HTTP;
1068 host = FixupProxyHostScheme(scheme, std::move(host));
1069 ProxyServer proxy_server =
1070 ProxyUriToProxyServer(host, ProxyServer::SCHEME_HTTP);
1071 if (proxy_server.is_valid()) {
1072 *result_server = proxy_server;
1073 return true;
1074 }
1075 return false;
1076 }
1077
1078 std::optional<ProxyConfigWithAnnotation>
GetConfigFromSettings()1079 ProxyConfigServiceLinux::Delegate::GetConfigFromSettings() {
1080 ProxyConfig config;
1081 config.set_from_system(true);
1082
1083 std::string mode;
1084 if (!setting_getter_->GetString(SettingGetter::PROXY_MODE, &mode)) {
1085 // We expect this to always be set, so if we don't see it then we probably
1086 // have a gsettings problem, and so we don't have a valid proxy config.
1087 return std::nullopt;
1088 }
1089 if (mode == "none") {
1090 // Specifically specifies no proxy.
1091 return ProxyConfigWithAnnotation(
1092 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1093 }
1094
1095 if (mode == "auto") {
1096 // Automatic proxy config.
1097 std::string pac_url_str;
1098 if (setting_getter_->GetString(SettingGetter::PROXY_AUTOCONF_URL,
1099 &pac_url_str)) {
1100 if (!pac_url_str.empty()) {
1101 // If the PAC URL is actually a file path, then put file:// in front.
1102 if (pac_url_str[0] == '/')
1103 pac_url_str = "file://" + pac_url_str;
1104 GURL pac_url(pac_url_str);
1105 if (!pac_url.is_valid())
1106 return std::nullopt;
1107 config.set_pac_url(pac_url);
1108 return ProxyConfigWithAnnotation(
1109 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1110 }
1111 }
1112 config.set_auto_detect(true);
1113 return ProxyConfigWithAnnotation(
1114 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1115 }
1116
1117 if (mode != "manual") {
1118 // Mode is unrecognized.
1119 return std::nullopt;
1120 }
1121 bool use_http_proxy;
1122 if (setting_getter_->GetBool(SettingGetter::PROXY_USE_HTTP_PROXY,
1123 &use_http_proxy)
1124 && !use_http_proxy) {
1125 // Another master switch for some reason. If set to false, then no
1126 // proxy. But we don't panic if the key doesn't exist.
1127 return ProxyConfigWithAnnotation(
1128 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1129 }
1130
1131 bool same_proxy = false;
1132 // Indicates to use the http proxy for all protocols. This one may
1133 // not exist (presumably on older versions); we assume false in that
1134 // case.
1135 setting_getter_->GetBool(SettingGetter::PROXY_USE_SAME_PROXY,
1136 &same_proxy);
1137
1138 ProxyServer proxy_for_http;
1139 ProxyServer proxy_for_https;
1140 ProxyServer proxy_for_ftp;
1141 ProxyServer socks_proxy; // (socks)
1142
1143 // This counts how many of the above ProxyServers were defined and valid.
1144 size_t num_proxies_specified = 0;
1145
1146 // Extract the per-scheme proxies. If we failed to parse it, or no proxy was
1147 // specified for the scheme, then the resulting ProxyServer will be invalid.
1148 if (GetProxyFromSettings(SettingGetter::PROXY_HTTP_HOST, &proxy_for_http))
1149 num_proxies_specified++;
1150 if (GetProxyFromSettings(SettingGetter::PROXY_HTTPS_HOST, &proxy_for_https))
1151 num_proxies_specified++;
1152 if (GetProxyFromSettings(SettingGetter::PROXY_FTP_HOST, &proxy_for_ftp))
1153 num_proxies_specified++;
1154 if (GetProxyFromSettings(SettingGetter::PROXY_SOCKS_HOST, &socks_proxy))
1155 num_proxies_specified++;
1156
1157 if (same_proxy) {
1158 if (proxy_for_http.is_valid()) {
1159 // Use the http proxy for all schemes.
1160 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
1161 config.proxy_rules().single_proxies.SetSingleProxyServer(proxy_for_http);
1162 }
1163 } else if (num_proxies_specified > 0) {
1164 if (socks_proxy.is_valid() && num_proxies_specified == 1) {
1165 // If the only proxy specified was for SOCKS, use it for all schemes.
1166 config.proxy_rules().type = ProxyConfig::ProxyRules::Type::PROXY_LIST;
1167 config.proxy_rules().single_proxies.SetSingleProxyServer(socks_proxy);
1168 } else {
1169 // Otherwise use the indicated proxies per-scheme.
1170 config.proxy_rules().type =
1171 ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;
1172 config.proxy_rules().proxies_for_http.SetSingleProxyServer(
1173 proxy_for_http);
1174 config.proxy_rules().proxies_for_https.SetSingleProxyServer(
1175 proxy_for_https);
1176 config.proxy_rules().proxies_for_ftp.SetSingleProxyServer(proxy_for_ftp);
1177 config.proxy_rules().fallback_proxies.SetSingleProxyServer(socks_proxy);
1178 }
1179 }
1180
1181 if (config.proxy_rules().empty()) {
1182 // Manual mode but we couldn't parse any rules.
1183 return std::nullopt;
1184 }
1185
1186 // Check for authentication, just so we can warn.
1187 bool use_auth = false;
1188 setting_getter_->GetBool(SettingGetter::PROXY_USE_AUTHENTICATION,
1189 &use_auth);
1190 if (use_auth) {
1191 // ProxyConfig does not support authentication parameters, but
1192 // Chrome will prompt for the password later. So we ignore
1193 // /system/http_proxy/*auth* settings.
1194 LOG(WARNING) << "Proxy authentication parameters ignored, see bug 16709";
1195 }
1196
1197 // Now the bypass list.
1198 std::vector<std::string> ignore_hosts_list;
1199 config.proxy_rules().bypass_rules.Clear();
1200 if (setting_getter_->GetStringList(SettingGetter::PROXY_IGNORE_HOSTS,
1201 &ignore_hosts_list)) {
1202 for (const auto& rule : ignore_hosts_list) {
1203 config.proxy_rules().bypass_rules.AddRuleFromString(rule);
1204 }
1205 }
1206
1207 if (setting_getter_->UseSuffixMatching()) {
1208 RewriteRulesForSuffixMatching(&config.proxy_rules().bypass_rules);
1209 }
1210
1211 // Note that there are no settings with semantics corresponding to
1212 // bypass of local names in GNOME. In KDE, "<local>" is supported
1213 // as a hostname rule.
1214
1215 // KDE allows one to reverse the bypass rules.
1216 config.proxy_rules().reverse_bypass = setting_getter_->BypassListIsReversed();
1217
1218 return ProxyConfigWithAnnotation(
1219 config, NetworkTrafficAnnotationTag(traffic_annotation_));
1220 }
1221
Delegate(std::unique_ptr<base::Environment> env_var_getter,std::optional<std::unique_ptr<SettingGetter>> setting_getter,std::optional<NetworkTrafficAnnotationTag> traffic_annotation)1222 ProxyConfigServiceLinux::Delegate::Delegate(
1223 std::unique_ptr<base::Environment> env_var_getter,
1224 std::optional<std::unique_ptr<SettingGetter>> setting_getter,
1225 std::optional<NetworkTrafficAnnotationTag> traffic_annotation)
1226 : env_var_getter_(std::move(env_var_getter)) {
1227 if (traffic_annotation) {
1228 traffic_annotation_ =
1229 MutableNetworkTrafficAnnotationTag(traffic_annotation.value());
1230 }
1231
1232 if (setting_getter) {
1233 setting_getter_ = std::move(setting_getter.value());
1234 return;
1235 }
1236
1237 // Figure out which SettingGetterImpl to use, if any.
1238 switch (base::nix::GetDesktopEnvironment(env_var_getter_.get())) {
1239 case base::nix::DESKTOP_ENVIRONMENT_CINNAMON:
1240 case base::nix::DESKTOP_ENVIRONMENT_DEEPIN:
1241 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
1242 case base::nix::DESKTOP_ENVIRONMENT_PANTHEON:
1243 case base::nix::DESKTOP_ENVIRONMENT_UKUI:
1244 case base::nix::DESKTOP_ENVIRONMENT_UNITY:
1245 #if defined(USE_GIO)
1246 {
1247 auto gs_getter = std::make_unique<SettingGetterImplGSettings>();
1248 // We have to load symbols and check the GNOME version in use to decide
1249 // if we should use the gsettings getter. See CheckVersion().
1250 if (gs_getter->CheckVersion(env_var_getter_.get()))
1251 setting_getter_ = std::move(gs_getter);
1252 }
1253 #endif
1254 break;
1255 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
1256 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
1257 case base::nix::DESKTOP_ENVIRONMENT_KDE5:
1258 case base::nix::DESKTOP_ENVIRONMENT_KDE6:
1259 setting_getter_ =
1260 std::make_unique<SettingGetterImplKDE>(env_var_getter_.get());
1261 break;
1262 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
1263 case base::nix::DESKTOP_ENVIRONMENT_LXQT:
1264 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
1265 break;
1266 }
1267 }
1268
SetUpAndFetchInitialConfig(const scoped_refptr<base::SingleThreadTaskRunner> & glib_task_runner,const scoped_refptr<base::SequencedTaskRunner> & main_task_runner,const NetworkTrafficAnnotationTag & traffic_annotation)1269 void ProxyConfigServiceLinux::Delegate::SetUpAndFetchInitialConfig(
1270 const scoped_refptr<base::SingleThreadTaskRunner>& glib_task_runner,
1271 const scoped_refptr<base::SequencedTaskRunner>& main_task_runner,
1272 const NetworkTrafficAnnotationTag& traffic_annotation) {
1273 traffic_annotation_ = MutableNetworkTrafficAnnotationTag(traffic_annotation);
1274
1275 // We should be running on the default glib main loop thread right
1276 // now. gsettings can only be accessed from this thread.
1277 DCHECK(glib_task_runner->RunsTasksInCurrentSequence());
1278 glib_task_runner_ = glib_task_runner;
1279 main_task_runner_ = main_task_runner;
1280
1281 // If we are passed a NULL |main_task_runner|, then don't set up proxy
1282 // setting change notifications. This should not be the usual case but is
1283 // intended to/ simplify test setups.
1284 if (!main_task_runner_.get())
1285 VLOG(1) << "Monitoring of proxy setting changes is disabled";
1286
1287 // Fetch and cache the current proxy config. The config is left in
1288 // cached_config_, where GetLatestProxyConfig() running on the main TaskRunner
1289 // will expect to find it. This is safe to do because we return
1290 // before this ProxyConfigServiceLinux is passed on to
1291 // the ConfiguredProxyResolutionService.
1292
1293 // Note: It would be nice to prioritize environment variables
1294 // and only fall back to gsettings if env vars were unset. But
1295 // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
1296 // does so even if the proxy mode is set to auto, which would
1297 // mislead us.
1298
1299 cached_config_ = std::nullopt;
1300 if (setting_getter_ && setting_getter_->Init(glib_task_runner)) {
1301 cached_config_ = GetConfigFromSettings();
1302 }
1303 if (cached_config_) {
1304 VLOG(1) << "Obtained proxy settings from annotation hash code "
1305 << cached_config_->traffic_annotation().unique_id_hash_code;
1306
1307 // If gsettings proxy mode is "none", meaning direct, then we take
1308 // that to be a valid config and will not check environment
1309 // variables. The alternative would have been to look for a proxy
1310 // wherever we can find one.
1311
1312 // Keep a copy of the config for use from this thread for
1313 // comparison with updated settings when we get notifications.
1314 reference_config_ = cached_config_;
1315
1316 // We only set up notifications if we have the main and file loops
1317 // available. We do this after getting the initial configuration so that we
1318 // don't have to worry about cancelling it if the initial fetch above fails.
1319 // Note that setting up notifications has the side effect of simulating a
1320 // change, so that we won't lose any updates that may have happened after
1321 // the initial fetch and before setting up notifications. We'll detect the
1322 // common case of no changes in OnCheckProxyConfigSettings() (or sooner) and
1323 // ignore it.
1324 if (main_task_runner.get()) {
1325 scoped_refptr<base::SequencedTaskRunner> required_loop =
1326 setting_getter_->GetNotificationTaskRunner();
1327 if (!required_loop.get() || required_loop->RunsTasksInCurrentSequence()) {
1328 // In this case we are already on an acceptable thread.
1329 SetUpNotifications();
1330 } else {
1331 // Post a task to set up notifications. We don't wait for success.
1332 required_loop->PostTask(
1333 FROM_HERE,
1334 base::BindOnce(
1335 &ProxyConfigServiceLinux::Delegate::SetUpNotifications, this));
1336 }
1337 }
1338 }
1339
1340 if (!cached_config_) {
1341 // We fall back on environment variables.
1342 //
1343 // Consulting environment variables doesn't need to be done from the
1344 // default glib main loop, but it's a tiny enough amount of work.
1345 cached_config_ = GetConfigFromEnv();
1346 if (cached_config_) {
1347 VLOG(1) << "Obtained proxy settings from environment variables";
1348 }
1349 }
1350 }
1351
1352 // Depending on the SettingGetter in use, this method will be called
1353 // on either the UI thread (GSettings) or the file thread (KDE).
SetUpNotifications()1354 void ProxyConfigServiceLinux::Delegate::SetUpNotifications() {
1355 scoped_refptr<base::SequencedTaskRunner> required_loop =
1356 setting_getter_->GetNotificationTaskRunner();
1357 DCHECK(!required_loop.get() || required_loop->RunsTasksInCurrentSequence());
1358 if (!setting_getter_->SetUpNotifications(this))
1359 LOG(ERROR) << "Unable to set up proxy configuration change notifications";
1360 }
1361
AddObserver(Observer * observer)1362 void ProxyConfigServiceLinux::Delegate::AddObserver(Observer* observer) {
1363 observers_.AddObserver(observer);
1364 }
1365
RemoveObserver(Observer * observer)1366 void ProxyConfigServiceLinux::Delegate::RemoveObserver(Observer* observer) {
1367 observers_.RemoveObserver(observer);
1368 }
1369
1370 ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfigWithAnnotation * config)1371 ProxyConfigServiceLinux::Delegate::GetLatestProxyConfig(
1372 ProxyConfigWithAnnotation* config) {
1373 // This is called from the main TaskRunner.
1374 DCHECK(!main_task_runner_.get() ||
1375 main_task_runner_->RunsTasksInCurrentSequence());
1376
1377 // Simply return the last proxy configuration that glib_default_loop
1378 // notified us of.
1379 *config = GetConfigOrDirect(cached_config_);
1380
1381 // We return CONFIG_VALID to indicate that *config was filled in. It is always
1382 // going to be available since we initialized eagerly on the UI thread.
1383 // TODO(eroman): do lazy initialization instead, so we no longer need
1384 // to construct ProxyConfigServiceLinux on the UI thread.
1385 // In which case, we may return false here.
1386 return CONFIG_VALID;
1387 }
1388
1389 // Depending on the SettingGetter in use, this method will be called
1390 // on either the UI thread (GSettings) or the file thread (KDE).
OnCheckProxyConfigSettings()1391 void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() {
1392 scoped_refptr<base::SequencedTaskRunner> required_loop =
1393 setting_getter_->GetNotificationTaskRunner();
1394 DCHECK(!required_loop.get() || required_loop->RunsTasksInCurrentSequence());
1395 std::optional<ProxyConfigWithAnnotation> new_config = GetConfigFromSettings();
1396
1397 // See if it is different from what we had before.
1398 if (new_config.has_value() != reference_config_.has_value() ||
1399 (new_config.has_value() &&
1400 !new_config->value().Equals(reference_config_->value()))) {
1401 // Post a task to the main TaskRunner with the new configuration, so it can
1402 // update |cached_config_|.
1403 main_task_runner_->PostTask(
1404 FROM_HERE,
1405 base::BindOnce(&ProxyConfigServiceLinux::Delegate::SetNewProxyConfig,
1406 this, new_config));
1407 // Update the thread-private copy in |reference_config_| as well.
1408 reference_config_ = new_config;
1409 } else {
1410 VLOG(1) << "Detected no-op change to proxy settings. Doing nothing.";
1411 }
1412 }
1413
SetNewProxyConfig(const std::optional<ProxyConfigWithAnnotation> & new_config)1414 void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig(
1415 const std::optional<ProxyConfigWithAnnotation>& new_config) {
1416 DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
1417 VLOG(1) << "Proxy configuration changed";
1418 cached_config_ = new_config;
1419 for (auto& observer : observers_) {
1420 observer.OnProxyConfigChanged(GetConfigOrDirect(new_config),
1421 ProxyConfigService::CONFIG_VALID);
1422 }
1423 }
1424
PostDestroyTask()1425 void ProxyConfigServiceLinux::Delegate::PostDestroyTask() {
1426 if (!setting_getter_)
1427 return;
1428
1429 scoped_refptr<base::SequencedTaskRunner> shutdown_loop =
1430 setting_getter_->GetNotificationTaskRunner();
1431 if (!shutdown_loop.get() || shutdown_loop->RunsTasksInCurrentSequence()) {
1432 // Already on the right thread, call directly.
1433 // This is the case for the unittests.
1434 OnDestroy();
1435 } else {
1436 // Post to shutdown thread. Note that on browser shutdown, we may quit
1437 // this MessageLoop and exit the program before ever running this.
1438 shutdown_loop->PostTask(
1439 FROM_HERE,
1440 base::BindOnce(&ProxyConfigServiceLinux::Delegate::OnDestroy, this));
1441 }
1442 }
OnDestroy()1443 void ProxyConfigServiceLinux::Delegate::OnDestroy() {
1444 scoped_refptr<base::SequencedTaskRunner> shutdown_loop =
1445 setting_getter_->GetNotificationTaskRunner();
1446 DCHECK(!shutdown_loop.get() || shutdown_loop->RunsTasksInCurrentSequence());
1447 setting_getter_->ShutDown();
1448 }
1449
ProxyConfigServiceLinux()1450 ProxyConfigServiceLinux::ProxyConfigServiceLinux()
1451 : delegate_(base::MakeRefCounted<Delegate>(base::Environment::Create(),
1452 std::nullopt,
1453 std::nullopt)) {}
1454
~ProxyConfigServiceLinux()1455 ProxyConfigServiceLinux::~ProxyConfigServiceLinux() {
1456 delegate_->PostDestroyTask();
1457 }
1458
ProxyConfigServiceLinux(std::unique_ptr<base::Environment> env_var_getter,const NetworkTrafficAnnotationTag & traffic_annotation)1459 ProxyConfigServiceLinux::ProxyConfigServiceLinux(
1460 std::unique_ptr<base::Environment> env_var_getter,
1461 const NetworkTrafficAnnotationTag& traffic_annotation)
1462 : delegate_(base::MakeRefCounted<Delegate>(std::move(env_var_getter),
1463 std::nullopt,
1464 traffic_annotation)) {}
1465
ProxyConfigServiceLinux(std::unique_ptr<base::Environment> env_var_getter,std::unique_ptr<SettingGetter> setting_getter,const NetworkTrafficAnnotationTag & traffic_annotation)1466 ProxyConfigServiceLinux::ProxyConfigServiceLinux(
1467 std::unique_ptr<base::Environment> env_var_getter,
1468 std::unique_ptr<SettingGetter> setting_getter,
1469 const NetworkTrafficAnnotationTag& traffic_annotation)
1470 : delegate_(base::MakeRefCounted<Delegate>(std::move(env_var_getter),
1471 std::move(setting_getter),
1472 traffic_annotation)) {}
1473
AddObserver(Observer * observer)1474 void ProxyConfigServiceLinux::AddObserver(Observer* observer) {
1475 delegate_->AddObserver(observer);
1476 }
1477
RemoveObserver(Observer * observer)1478 void ProxyConfigServiceLinux::RemoveObserver(Observer* observer) {
1479 delegate_->RemoveObserver(observer);
1480 }
1481
1482 ProxyConfigService::ConfigAvailability
GetLatestProxyConfig(ProxyConfigWithAnnotation * config)1483 ProxyConfigServiceLinux::GetLatestProxyConfig(
1484 ProxyConfigWithAnnotation* config) {
1485 return delegate_->GetLatestProxyConfig(config);
1486 }
1487
1488 } // namespace net
1489