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