John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 1 | // Copyright 2021 The Chromium Authors. All rights reserved. |
| 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 "components/embedder_support/user_agent_utils.h" |
| 6 | |
| 7 | #include "base/command_line.h" |
Lei Zhang | fcf7167 | 2021-05-14 16:28:20 | [diff] [blame] | 8 | #include "base/no_destructor.h" |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 9 | #include "base/strings/strcat.h" |
Aaron Tagliaboschi | c42d0b61 | 2021-06-17 15:08:23 | [diff] [blame] | 10 | #include "base/strings/stringprintf.h" |
| 11 | #include "base/system/sys_info.h" |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 12 | #include "build/branding_buildflags.h" |
| 13 | #include "components/embedder_support/switches.h" |
| 14 | #include "components/version_info/version_info.h" |
| 15 | #include "content/public/browser/web_contents.h" |
| 16 | #include "content/public/common/content_features.h" |
| 17 | #include "content/public/common/content_switches.h" |
| 18 | #include "content/public/common/user_agent.h" |
| 19 | #include "net/http/http_util.h" |
| 20 | #include "third_party/blink/public/common/features.h" |
| 21 | #include "third_party/blink/public/common/user_agent/user_agent_metadata.h" |
| 22 | |
| 23 | namespace embedder_support { |
| 24 | |
| 25 | std::string GetProduct() { |
| 26 | return version_info::GetProductNameAndVersionForUserAgent(); |
| 27 | } |
| 28 | |
| 29 | std::string GetUserAgent() { |
| 30 | base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| 31 | if (command_line->HasSwitch(kUserAgent)) { |
| 32 | std::string ua = command_line->GetSwitchValueASCII(kUserAgent); |
| 33 | if (net::HttpUtil::IsValidHeaderValue(ua)) |
| 34 | return ua; |
| 35 | LOG(WARNING) << "Ignored invalid value for flag --" << kUserAgent; |
| 36 | } |
| 37 | |
Aaron Tagliaboschi | 2fa19065 | 2021-07-30 19:02:06 | [diff] [blame] | 38 | if (base::FeatureList::IsEnabled(blink::features::kReduceUserAgent)) |
| 39 | return GetReducedUserAgent(); |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 40 | |
| 41 | std::string product = GetProduct(); |
| 42 | #if defined(OS_ANDROID) |
| 43 | if (command_line->HasSwitch(switches::kUseMobileUserAgent)) |
| 44 | product += " Mobile"; |
| 45 | #endif |
| 46 | return content::BuildUserAgentFromProduct(product); |
| 47 | } |
| 48 | |
Aaron Tagliaboschi | 2fa19065 | 2021-07-30 19:02:06 | [diff] [blame] | 49 | std::string GetReducedUserAgent() { |
| 50 | return content::GetReducedUserAgent( |
| 51 | base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 52 | switches::kUseMobileUserAgent), |
| 53 | version_info::GetMajorVersionNumber()); |
| 54 | } |
| 55 | |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 56 | // Generate a pseudo-random permutation of the following brand/version pairs: |
| 57 | // 1. The base project (i.e. Chromium) |
| 58 | // 2. The browser brand, if available |
| 59 | // 3. A randomized string containing escaped characters to ensure proper |
| 60 | // header parsing, along with an arbitrarily low version to ensure proper |
| 61 | // version checking. |
| 62 | blink::UserAgentBrandList GenerateBrandVersionList( |
| 63 | int seed, |
Anton Bikineev | 1156b5f | 2021-05-15 22:35:36 | [diff] [blame] | 64 | absl::optional<std::string> brand, |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 65 | std::string major_version, |
Anton Bikineev | 1156b5f | 2021-05-15 22:35:36 | [diff] [blame] | 66 | absl::optional<std::string> maybe_greasey_brand) { |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 67 | DCHECK_GE(seed, 0); |
| 68 | const int npermutations = 6; // 3! |
| 69 | int permutation = seed % npermutations; |
| 70 | |
| 71 | // Pick a stable permutation seeded by major version number. any values here |
| 72 | // and in order should be under three. |
| 73 | const std::vector<std::vector<int>> orders{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, |
| 74 | {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}; |
| 75 | const std::vector<int> order = orders[permutation]; |
| 76 | DCHECK_EQ(6u, orders.size()); |
| 77 | DCHECK_EQ(3u, order.size()); |
| 78 | |
| 79 | // Previous values for indexes 0 and 1 were '\' and '"', temporarily removed |
| 80 | // because of compat issues |
| 81 | const std::vector<std::string> escaped_chars = {" ", " ", ";"}; |
| 82 | std::string greasey_brand = |
| 83 | base::StrCat({escaped_chars[order[0]], "Not", escaped_chars[order[1]], |
| 84 | "A", escaped_chars[order[2]], "Brand"}); |
| 85 | |
| 86 | blink::UserAgentBrandVersion greasey_bv = { |
| 87 | maybe_greasey_brand.value_or(greasey_brand), "99"}; |
| 88 | blink::UserAgentBrandVersion chromium_bv = {"Chromium", major_version}; |
| 89 | |
| 90 | blink::UserAgentBrandList greased_brand_version_list(3); |
| 91 | |
| 92 | if (brand) { |
| 93 | blink::UserAgentBrandVersion brand_bv = {brand.value(), major_version}; |
| 94 | |
| 95 | greased_brand_version_list[order[0]] = greasey_bv; |
| 96 | greased_brand_version_list[order[1]] = chromium_bv; |
| 97 | greased_brand_version_list[order[2]] = brand_bv; |
| 98 | } else { |
| 99 | greased_brand_version_list[seed % 2] = greasey_bv; |
| 100 | greased_brand_version_list[(seed + 1) % 2] = chromium_bv; |
| 101 | |
| 102 | // If left, the last element would make a blank "" at the end of the header. |
| 103 | greased_brand_version_list.pop_back(); |
| 104 | } |
| 105 | |
| 106 | return greased_brand_version_list; |
| 107 | } |
| 108 | |
| 109 | const blink::UserAgentBrandList& GetBrandVersionList() { |
| 110 | static const base::NoDestructor<blink::UserAgentBrandList> |
| 111 | greased_brand_version_list([] { |
| 112 | int major_version_number; |
| 113 | std::string major_version = version_info::GetMajorVersionNumber(); |
| 114 | base::StringToInt(major_version, &major_version_number); |
Anton Bikineev | 1156b5f | 2021-05-15 22:35:36 | [diff] [blame] | 115 | absl::optional<std::string> brand; |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 116 | #if !BUILDFLAG(CHROMIUM_BRANDING) |
| 117 | brand = version_info::GetProductName(); |
| 118 | #endif |
Anton Bikineev | 1156b5f | 2021-05-15 22:35:36 | [diff] [blame] | 119 | absl::optional<std::string> maybe_param_override = |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 120 | base::GetFieldTrialParamValueByFeature(features::kGreaseUACH, |
| 121 | "brand_override"); |
| 122 | if (maybe_param_override->empty()) |
Anton Bikineev | 1156b5f | 2021-05-15 22:35:36 | [diff] [blame] | 123 | maybe_param_override = absl::nullopt; |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 124 | |
| 125 | return GenerateBrandVersionList(major_version_number, brand, |
| 126 | major_version, maybe_param_override); |
| 127 | }()); |
| 128 | return *greased_brand_version_list; |
| 129 | } |
| 130 | |
Aaron Tagliaboschi | e2e23a7 | 2021-01-29 15:42:02 | [diff] [blame] | 131 | // TODO(crbug.com/1103047): This can be removed/re-refactored once we use |
| 132 | // "macOS" by default |
| 133 | std::string GetPlatformForUAMetadata() { |
| 134 | #if defined(OS_MAC) |
| 135 | return "macOS"; |
| 136 | #else |
| 137 | return version_info::GetOSType(); |
| 138 | #endif |
| 139 | } |
| 140 | |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 141 | blink::UserAgentMetadata GetUserAgentMetadata() { |
| 142 | blink::UserAgentMetadata metadata; |
| 143 | |
| 144 | metadata.brand_version_list = GetBrandVersionList(); |
| 145 | metadata.full_version = version_info::GetVersionNumber(); |
Aaron Tagliaboschi | e2e23a7 | 2021-01-29 15:42:02 | [diff] [blame] | 146 | metadata.platform = GetPlatformForUAMetadata(); |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 147 | metadata.architecture = content::GetLowEntropyCpuArchitecture(); |
| 148 | metadata.model = content::BuildModelInfo(); |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 149 | metadata.mobile = false; |
| 150 | #if defined(OS_ANDROID) |
| 151 | metadata.mobile = base::CommandLine::ForCurrentProcess()->HasSwitch( |
| 152 | switches::kUseMobileUserAgent); |
| 153 | #endif |
| 154 | |
Aaron Tagliaboschi | c42d0b61 | 2021-06-17 15:08:23 | [diff] [blame] | 155 | int32_t major, minor, bugfix = 0; |
| 156 | base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); |
| 157 | metadata.platform_version = |
| 158 | base::StringPrintf("%d.%d.%d", major, minor, bugfix); |
Aaron Tagliaboschi | 1649e10 | 2021-06-18 23:15:07 | [diff] [blame] | 159 | // These methods use the same information as the User-Agent string, but are |
| 160 | // "low entropy" in that they reduce the number of options for output to a |
| 161 | // set number. For more information, see the respective headers. |
| 162 | metadata.architecture = content::GetLowEntropyCpuArchitecture(); |
| 163 | metadata.bitness = content::GetLowEntropyCpuBitness(); |
Aaron Tagliaboschi | c42d0b61 | 2021-06-17 15:08:23 | [diff] [blame] | 164 | |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 165 | return metadata; |
| 166 | } |
| 167 | |
| 168 | #if defined(OS_ANDROID) |
| 169 | void SetDesktopUserAgentOverride(content::WebContents* web_contents, |
Gang Wu | b14b302 | 2021-03-25 22:53:48 | [diff] [blame] | 170 | const blink::UserAgentMetadata& metadata, |
| 171 | bool override_in_new_tabs) { |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 172 | const char kLinuxInfoStr[] = "X11; Linux x86_64"; |
| 173 | std::string product = version_info::GetProductNameAndVersionForUserAgent(); |
| 174 | |
| 175 | blink::UserAgentOverride spoofed_ua; |
| 176 | spoofed_ua.ua_string_override = |
| 177 | content::BuildUserAgentFromOSAndProduct(kLinuxInfoStr, product); |
| 178 | spoofed_ua.ua_metadata_override = metadata; |
| 179 | spoofed_ua.ua_metadata_override->platform = "Linux"; |
| 180 | spoofed_ua.ua_metadata_override->platform_version = |
| 181 | std::string(); // match content::GetOSVersion(false) on Linux |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 182 | spoofed_ua.ua_metadata_override->model = std::string(); |
| 183 | spoofed_ua.ua_metadata_override->mobile = false; |
Aaron Tagliaboschi | 1649e10 | 2021-06-18 23:15:07 | [diff] [blame] | 184 | // Match the above "CpuInfo" string, which is also the most common Linux |
| 185 | // CPU architecture and bitness.` |
| 186 | spoofed_ua.ua_metadata_override->architecture = "x86"; |
| 187 | spoofed_ua.ua_metadata_override->bitness = "64"; |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 188 | |
Gang Wu | b14b302 | 2021-03-25 22:53:48 | [diff] [blame] | 189 | web_contents->SetUserAgentOverride(spoofed_ua, override_in_new_tabs); |
John Abd-El-Malek | ec1fc69e | 2021-01-28 19:14:41 | [diff] [blame] | 190 | } |
| 191 | #endif // OS_ANDROID |
| 192 | |
| 193 | } // namespace embedder_support |