Browse Source

feat: allow setting of global fallback user agent (#18110)

Samuel Attard 6 years ago
parent
commit
9a665eb5fb

+ 53 - 3
atom/app/atom_content_client.cc

@@ -7,6 +7,7 @@
 #include <string>
 #include <vector>
 
+#include "atom/browser/browser.h"
 #include "atom/common/atom_version.h"
 #include "atom/common/chrome_version.h"
 #include "atom/common/options_switches.h"
@@ -14,8 +15,10 @@
 #include "base/files/file_util.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "content/public/common/content_constants.h"
+#include "content/public/common/content_switches.h"
 #include "content/public/common/pepper_plugin_info.h"
 #include "content/public/common/user_agent.h"
 #include "electron/buildflags/buildflags.h"
@@ -173,6 +176,27 @@ void ConvertStringWithSeparatorToVector(std::vector<std::string>* vec,
                              base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
 }
 
+std::string RemoveWhitespace(const std::string& str) {
+  std::string trimmed;
+  if (base::RemoveChars(str, " ", &trimmed))
+    return trimmed;
+  else
+    return str;
+}
+
+bool IsBrowserProcess() {
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  std::string process_type =
+      command_line->GetSwitchValueASCII(::switches::kProcessType);
+  return process_type.empty();
+}
+
+std::string BuildDefaultUserAgent() {
+  return "Chrome/" CHROME_VERSION_STRING " " ATOM_PRODUCT_NAME
+         "/" ATOM_VERSION_STRING;
+}
+
 }  // namespace
 
 AtomContentClient::AtomContentClient() {}
@@ -184,9 +208,35 @@ std::string AtomContentClient::GetProduct() const {
 }
 
 std::string AtomContentClient::GetUserAgent() const {
-  return content::BuildUserAgentFromProduct("Chrome/" CHROME_VERSION_STRING
-                                            " " ATOM_PRODUCT_NAME
-                                            "/" ATOM_VERSION_STRING);
+  if (IsBrowserProcess()) {
+    if (user_agent_override_.empty()) {
+      auto* browser = Browser::Get();
+      std::string name = RemoveWhitespace(browser->GetName());
+      std::string user_agent;
+      if (name == ATOM_PRODUCT_NAME) {
+        user_agent = BuildDefaultUserAgent();
+      } else {
+        user_agent = base::StringPrintf(
+            "%s/%s Chrome/%s " ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING,
+            name.c_str(), browser->GetVersion().c_str(), CHROME_VERSION_STRING);
+      }
+      return content::BuildUserAgentFromProduct(user_agent);
+    }
+    return user_agent_override_;
+  }
+  // In a renderer process the user agent should be provided on the CLI
+  // If it's not we just fallback to the default one, this should never happen
+  // but we want to handle it gracefully
+  const base::CommandLine* command_line =
+      base::CommandLine::ForCurrentProcess();
+  std::string cli_user_agent = command_line->GetSwitchValueASCII("user-agent");
+  if (cli_user_agent.empty())
+    return BuildDefaultUserAgent();
+  return cli_user_agent;
+}
+
+void AtomContentClient::SetUserAgent(const std::string& user_agent) {
+  user_agent_override_ = user_agent;
 }
 
 base::string16 AtomContentClient::GetLocalizedString(int message_id) const {

+ 5 - 1
atom/app/atom_content_client.h

@@ -18,10 +18,12 @@ class AtomContentClient : public brightray::ContentClient {
   AtomContentClient();
   ~AtomContentClient() override;
 
+  std::string GetUserAgent() const override;
+  void SetUserAgent(const std::string& user_agent);
+
  protected:
   // content::ContentClient:
   std::string GetProduct() const override;
-  std::string GetUserAgent() const override;
   base::string16 GetLocalizedString(int message_id) const override;
   void AddAdditionalSchemes(Schemes* schemes) override;
   void AddPepperPlugins(
@@ -31,6 +33,8 @@ class AtomContentClient : public brightray::ContentClient {
       std::vector<media::CdmHostFilePath>* cdm_host_file_paths) override;
 
  private:
+  std::string user_agent_override_ = "";
+
   DISALLOW_COPY_AND_ASSIGN(AtomContentClient);
 };
 

+ 16 - 0
atom/browser/api/atom_api_app.cc

@@ -7,6 +7,7 @@
 #include <string>
 #include <vector>
 
+#include "atom/app/atom_content_client.h"
 #include "atom/browser/api/atom_api_menu.h"
 #include "atom/browser/api/atom_api_session.h"
 #include "atom/browser/api/atom_api_web_contents.h"
@@ -43,6 +44,7 @@
 #include "content/public/browser/client_certificate_delegate.h"
 #include "content/public/browser/gpu_data_manager.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
 #include "media/audio/audio_manager.h"
 #include "native_mate/object_template_builder.h"
@@ -1236,6 +1238,18 @@ void App::EnableMixedSandbox(mate::Arguments* args) {
   command_line->AppendSwitch(switches::kEnableMixedSandbox);
 }
 
+void App::SetUserAgentFallback(const std::string& user_agent) {
+  AtomContentClient* client =
+      static_cast<AtomContentClient*>(content::GetContentClient());
+  client->SetUserAgent(user_agent);
+}
+
+std::string App::GetUserAgentFallback() {
+  AtomContentClient* client =
+      static_cast<AtomContentClient*>(content::GetContentClient());
+  return client->GetUserAgent();
+}
+
 #if defined(OS_MACOSX)
 bool App::MoveToApplicationsFolder(mate::Arguments* args) {
   return ui::cocoa::AtomBundleMover::Move(args);
@@ -1342,6 +1356,8 @@ void App::BuildPrototype(v8::Isolate* isolate,
       .SetMethod("startAccessingSecurityScopedResource",
                  &App::StartAccessingSecurityScopedResource)
 #endif
+      .SetProperty("userAgentFallback", &App::GetUserAgentFallback,
+                   &App::SetUserAgentFallback)
       .SetMethod("enableSandbox", &App::EnableSandbox)
       .SetMethod("enableMixedSandbox", &App::EnableMixedSandbox);
 }

+ 2 - 0
atom/browser/api/atom_api_app.h

@@ -205,6 +205,8 @@ class App : public AtomBrowserClient::Delegate,
                                     const std::string& info_type);
   void EnableSandbox(mate::Arguments* args);
   void EnableMixedSandbox(mate::Arguments* args);
+  void SetUserAgentFallback(const std::string& user_agent);
+  std::string GetUserAgentFallback();
 
 #if defined(OS_MACOSX)
   bool MoveToApplicationsFolder(mate::Arguments* args);

+ 5 - 21
atom/browser/atom_browser_context.cc

@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "atom/app/atom_content_client.h"
 #include "atom/browser/atom_blob_reader.h"
 #include "atom/browser/atom_browser_main_parts.h"
 #include "atom/browser/atom_download_manager_delegate.h"
@@ -42,6 +43,7 @@
 #include "components/proxy_config/proxy_config_pref_names.h"
 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/common/content_client.h"
 #include "content/public/common/user_agent.h"
 #include "net/base/escape.h"
 
@@ -51,14 +53,6 @@ namespace atom {
 
 namespace {
 
-std::string RemoveWhitespace(const std::string& str) {
-  std::string trimmed;
-  if (base::RemoveChars(str, " ", &trimmed))
-    return trimmed;
-  else
-    return str;
-}
-
 // Convert string to lower case and escape it.
 std::string MakePartitionName(const std::string& input) {
   return net::EscapePath(base::ToLowerASCII(input));
@@ -78,19 +72,9 @@ AtomBrowserContext::AtomBrowserContext(const std::string& partition,
       storage_policy_(new SpecialStoragePolicy),
       in_memory_(in_memory),
       weak_factory_(this) {
-  // Construct user agent string.
-  Browser* browser = Browser::Get();
-  std::string name = RemoveWhitespace(browser->GetName());
-  std::string user_agent;
-  if (name == ATOM_PRODUCT_NAME) {
-    user_agent = "Chrome/" CHROME_VERSION_STRING " " ATOM_PRODUCT_NAME
-                 "/" ATOM_VERSION_STRING;
-  } else {
-    user_agent = base::StringPrintf(
-        "%s/%s Chrome/%s " ATOM_PRODUCT_NAME "/" ATOM_VERSION_STRING,
-        name.c_str(), browser->GetVersion().c_str(), CHROME_VERSION_STRING);
-  }
-  user_agent_ = content::BuildUserAgentFromProduct(user_agent);
+  AtomContentClient* client =
+      static_cast<AtomContentClient*>(content::GetContentClient());
+  user_agent_ = client->GetUserAgent();
 
   // Read options.
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

+ 4 - 0
atom/browser/web_contents_preferences.cc

@@ -20,6 +20,7 @@
 #include "cc/base/switches.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_client.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/web_preferences.h"
 #include "native_mate/dictionary.h"
@@ -229,6 +230,9 @@ WebContentsPreferences* WebContentsPreferences::From(
 
 void WebContentsPreferences::AppendCommandLineSwitches(
     base::CommandLine* command_line) {
+  // Append UA Override
+  command_line->AppendSwitchASCII("user-agent",
+                                  content::GetContentClient()->GetUserAgent());
   // Check if plugins are enabled.
   if (IsEnabled(options::kPlugins))
     command_line->AppendSwitch(switches::kEnablePlugins);

+ 0 - 8
brightray/common/content_client.cc

@@ -20,10 +20,6 @@ std::string GetProductInternal() {
                             GetApplicationVersion().c_str());
 }
 
-std::string GetBrightrayUserAgent() {
-  return content::BuildUserAgentFromProduct(GetProductInternal());
-}
-
 ContentClient::ContentClient() {}
 
 ContentClient::~ContentClient() {}
@@ -32,10 +28,6 @@ std::string ContentClient::GetProduct() const {
   return GetProductInternal();
 }
 
-std::string ContentClient::GetUserAgent() const {
-  return GetBrightrayUserAgent();
-}
-
 base::string16 ContentClient::GetLocalizedString(int message_id) const {
   return l10n_util::GetStringUTF16(message_id);
 }

+ 0 - 1
brightray/common/content_client.h

@@ -21,7 +21,6 @@ class ContentClient : public content::ContentClient {
 
  private:
   std::string GetProduct() const override;
-  std::string GetUserAgent() const override;
   base::string16 GetLocalizedString(int message_id) const override;
   base::StringPiece GetDataResource(int resource_id,
                                     ui::ScaleFactor) const override;

+ 9 - 0
docs/api/app.md

@@ -1261,6 +1261,15 @@ Sets the `image` associated with this dock icon.
 
 ## Properties
 
+### `app.userAgentFallback`
+
+A `String` which is the user agent string Electron will use as a global fallback.
+
+This is the user agent that will be used when no user agent is set at the
+`webContents` or `session` level.  Useful for ensuring your entire
+app has the same user agent.  Set to a custom value as early as possible
+in your apps initialization to ensure that your overridden value is used.
+
 ### `app.isPackaged`
 
 A `Boolean` property that returns  `true` if the app is packaged, `false` otherwise. For many apps, this property can be used to distinguish development and production environments.

+ 1 - 0
patches/common/chromium/.patches

@@ -99,3 +99,4 @@ keyboard-lock-service-impl.patch
 make_--explicitly-allowed-ports_work_with_networkservice.patch
 fix_crashes_in_renderframeimpl_onselectpopupmenuitem_s.patch
 fix_re-entracy_problem_with_invalidateframesinkid.patch
+chore_expose_getcontentclient_to_embedders.patch

+ 24 - 0
patches/common/chromium/chore_expose_getcontentclient_to_embedders.patch

@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Samuel Attard <[email protected]>
+Date: Wed, 1 May 2019 18:04:41 -0700
+Subject: chore: expose GetContentClient to embedders
+
+
+diff --git a/content/public/common/content_client.h b/content/public/common/content_client.h
+index 528fd6abf6a623b8076803fddf5616d88f0978e8..e03bfeff5dabd55fe548ba828ae30065ba3f42d6 100644
+--- a/content/public/common/content_client.h
++++ b/content/public/common/content_client.h
+@@ -58,10 +58,10 @@ struct PepperPluginInfo;
+ // content code is called.
+ CONTENT_EXPORT void SetContentClient(ContentClient* client);
+ 
+-#if defined(CONTENT_IMPLEMENTATION)
++//#if defined(CONTENT_IMPLEMENTATION)
+ // Content's embedder API should only be used by content.
+-ContentClient* GetContentClient();
+-#endif
++CONTENT_EXPORT ContentClient* GetContentClient();
++//#endif
+ 
+ // Used for tests to override the relevant embedder interfaces. Each method
+ // returns the old value.

+ 24 - 0
spec/api-app-spec.js

@@ -1100,4 +1100,28 @@ describe('app module', () => {
       return expect(app.whenReady()).to.be.eventually.fulfilled
     })
   })
+
+  describe('user agent fallback', () => {
+    let initialValue
+
+    before(() => {
+      initialValue = app.userAgentFallback
+    })
+
+    it('should have a reasonable default', () => {
+      expect(initialValue).to.include(`Electron/${process.versions.electron}`)
+      expect(initialValue).to.include(`Chrome/${process.versions.chrome}`)
+    })
+
+    it('should be overridable', () => {
+      app.userAgentFallback = 'test-agent/123'
+      expect(app.userAgentFallback).to.equal('test-agent/123')
+    })
+
+    it('should be restorable', () => {
+      app.userAgentFallback = 'test-agent/123'
+      app.userAgentFallback = ''
+      expect(app.userAgentFallback).to.equal(initialValue)
+    })
+  })
 })