blob: e8a1a389cbf31d60e7da8f535a7e56fc3ca00475 [file] [log] [blame]
[email protected]71011c1682014-07-09 17:19:161// Copyright 2014 The Chromium Authors. All rights reserved.
[email protected]bd3b4712012-12-18 17:01:302// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
asvitkine9a279832015-12-18 02:35:505#include "components/variations/variations_http_header_provider.h"
[email protected]bd3b4712012-12-18 17:01:306
avi5dd91f82015-12-25 22:30:467#include <stddef.h>
8
horoe09b6c82014-11-01 02:08:289#include <set>
10#include <string>
[email protected]1bd918d2013-10-13 18:23:0911#include <vector>
12
[email protected]bd3b4712012-12-18 17:01:3013#include "base/base64.h"
14#include "base/memory/singleton.h"
asvitkine454600f2015-06-16 16:34:5015#include "base/metrics/histogram_macros.h"
[email protected]1bd918d2013-10-13 18:23:0916#include "base/strings/string_number_conversions.h"
17#include "base/strings/string_split.h"
[email protected]7f8a9932013-07-26 20:43:3418#include "base/strings/string_util.h"
fdoraycddcf0e2016-06-23 21:20:3019#include "base/threading/thread_task_runner_handle.h"
[email protected]ea15bd52014-07-14 22:42:5020#include "components/variations/proto/client_variations.pb.h"
[email protected]bd3b4712012-12-18 17:01:3021
[email protected]71011c1682014-07-09 17:19:1622namespace variations {
[email protected]ab7780792013-01-10 01:26:0923
Jun Caif3aba7f92018-07-12 16:52:4024// The following documents how adding/removing http headers for web content
25// requests are implemented when Network Service is enabled or not enabled.
26//
27// When Network Service is not enabled, adding headers is implemented in
28// ChromeResourceDispatcherHostDelegate::RequestBeginning() by calling
29// variations::AppendVariationHeaders(), and removing headers is implemented in
30// ChromeNetworkDelegate::OnBeforeRedirect() by calling
31// variations::StripVariationHeaderIfNeeded().
32//
33// When Network Service is enabled, adding/removing headers is implemented by
34// request consumers, and how it is implemented depends on the request type.
35// There are three cases:
36// 1. Subresources request in renderer, it is implemented
37// in URLLoaderThrottleProviderImpl::CreateThrottles() by adding a
John Abd-El-Malek9fb60492018-08-02 04:28:5038// GoogleURLLoaderThrottle to a content::URLLoaderThrottle vector.
Jun Caif3aba7f92018-07-12 16:52:4039// 2. Navigations/Downloads request in browser, it is implemented in
40// ChromeContentBrowserClient::CreateURLLoaderThrottles() by also adding a
John Abd-El-Malek9fb60492018-08-02 04:28:5041// GoogleURLLoaderThrottle to a content::URLLoaderThrottle vector.
Jun Caif3aba7f92018-07-12 16:52:4042// 3. SimpleURLLoader in browser, it is implemented in a SimpleURLLoader wrapper
Takashi Toyoshimaf3ceca92019-02-04 07:49:0543// function variations::CreateSimpleURLLoaderWithVariationsHeader().
Jun Caif3aba7f92018-07-12 16:52:4044
asvitkine9a279832015-12-18 02:35:5045// static
[email protected]ab7780792013-01-10 01:26:0946VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() {
olli.raula36aa8be2015-09-10 11:14:2247 return base::Singleton<VariationsHttpHeaderProvider>::get();
[email protected]bd3b4712012-12-18 17:01:3048}
49
yutak3305f49d2016-12-13 10:32:3150std::string VariationsHttpHeaderProvider::GetClientDataHeader(
51 bool is_signed_in) {
[email protected]bd3b4712012-12-18 17:01:3052 // Lazily initialize the header, if not already done, before attempting to
53 // transmit it.
54 InitVariationIDsCacheIfNeeded();
[email protected]ab7780792013-01-10 01:26:0955
56 std::string variation_ids_header_copy;
57 {
58 base::AutoLock scoped_lock(lock_);
yutak3305f49d2016-12-13 10:32:3159 variation_ids_header_copy = is_signed_in
60 ? cached_variation_ids_header_signed_in_
61 : cached_variation_ids_header_;
[email protected]ab7780792013-01-10 01:26:0962 }
asvitkine9a279832015-12-18 02:35:5063 return variation_ids_header_copy;
[email protected]bd3b4712012-12-18 17:01:3064}
65
asvitkine35ba6472015-12-18 23:52:3366std::string VariationsHttpHeaderProvider::GetVariationsString() {
67 InitVariationIDsCacheIfNeeded();
68
69 // Construct a space-separated string with leading and trailing spaces from
70 // the variations set. Note: The ids in it will be in sorted order per the
71 // std::set contract.
72 std::string ids_string = " ";
73 {
74 base::AutoLock scoped_lock(lock_);
yutak3305f49d2016-12-13 10:32:3175 for (const VariationIDEntry& entry : GetAllVariationIds()) {
76 if (entry.second == GOOGLE_WEB_PROPERTIES) {
Raul Tambref88e5102019-02-06 10:54:0377 ids_string.append(base::NumberToString(entry.first));
yutak3305f49d2016-12-13 10:32:3178 ids_string.push_back(' ');
79 }
asvitkine35ba6472015-12-18 23:52:3380 }
81 }
82 return ids_string;
83}
84
Roger McFarlaned4aaef312019-02-19 17:26:4485std::vector<VariationID> VariationsHttpHeaderProvider::GetVariationsVector(
86 IDCollectionKey key) {
87 InitVariationIDsCacheIfNeeded();
88
89 // Get all the active variation ids while holding the lock.
90 std::set<VariationIDEntry> all_variation_ids;
91 {
92 base::AutoLock scoped_lock(lock_);
93 all_variation_ids = GetAllVariationIds();
94 }
95
96 // Copy the requested variations to the output vector. Note that the ids will
97 // be in sorted order because they're coming from a std::set.
98 std::vector<VariationID> result;
99 result.reserve(all_variation_ids.size());
100 for (const VariationIDEntry& entry : all_variation_ids) {
101 if (entry.second == key)
102 result.push_back(entry.first);
103 }
104 return result;
105}
106
Alexei Svitkine105f942e2018-02-17 02:53:48107VariationsHttpHeaderProvider::ForceIdsResult
108VariationsHttpHeaderProvider::ForceVariationIds(
109 const std::vector<std::string>& variation_ids,
110 const std::string& command_line_variation_ids) {
111 default_variation_ids_set_.clear();
112
113 if (!AddDefaultVariationIds(variation_ids))
114 return ForceIdsResult::INVALID_VECTOR_ENTRY;
115
jkrcalbf073372016-07-29 07:21:31116 if (!command_line_variation_ids.empty()) {
Alexei Svitkine105f942e2018-02-17 02:53:48117 std::vector<std::string> variation_ids_from_command_line =
jkrcalbf073372016-07-29 07:21:31118 base::SplitString(command_line_variation_ids, ",",
119 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
Alexei Svitkine105f942e2018-02-17 02:53:48120 if (!AddDefaultVariationIds(variation_ids_from_command_line))
121 return ForceIdsResult::INVALID_SWITCH_ENTRY;
jkrcalbf073372016-07-29 07:21:31122 }
Alexei Svitkine105f942e2018-02-17 02:53:48123 return ForceIdsResult::SUCCESS;
[email protected]1bd918d2013-10-13 18:23:09124}
125
Jun Cai0e568632018-08-09 02:05:33126void VariationsHttpHeaderProvider::AddObserver(Observer* observer) {
127 observer_list_.AddObserver(observer);
128}
129
130void VariationsHttpHeaderProvider::RemoveObserver(Observer* observer) {
131 observer_list_.RemoveObserver(observer);
132}
133
asvitkineb4ed78682015-03-12 18:18:54134void VariationsHttpHeaderProvider::ResetForTesting() {
135 base::AutoLock scoped_lock(lock_);
136
137 // Stop observing field trials so that it can be restarted when this is
138 // re-inited. Note: This is a no-op if this is not currently observing.
139 base::FieldTrialList::RemoveObserver(this);
140 variation_ids_cache_initialized_ = false;
Olivier Robin9edc20f2018-10-03 09:51:42141 variation_ids_set_.clear();
142 default_variation_ids_set_.clear();
143 synthetic_variation_ids_set_.clear();
144 cached_variation_ids_header_.clear();
145 cached_variation_ids_header_signed_in_.clear();
asvitkineb4ed78682015-03-12 18:18:54146}
147
[email protected]ab7780792013-01-10 01:26:09148VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
asvitkine9a279832015-12-18 02:35:50149 : variation_ids_cache_initialized_(false) {}
[email protected]bd3b4712012-12-18 17:01:30150
asvitkine9a279832015-12-18 02:35:50151VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {}
[email protected]bd3b4712012-12-18 17:01:30152
[email protected]ab7780792013-01-10 01:26:09153void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
[email protected]bd3b4712012-12-18 17:01:30154 const std::string& trial_name,
155 const std::string& group_name) {
[email protected]bd3b4712012-12-18 17:01:30156 base::AutoLock scoped_lock(lock_);
yutak3305f49d2016-12-13 10:32:31157 const size_t old_size = variation_ids_set_.size();
158 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES);
159 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_SIGNED_IN);
160 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_TRIGGER);
161 if (variation_ids_set_.size() != old_size)
162 UpdateVariationIDsHeaderValue();
[email protected]bd3b4712012-12-18 17:01:30163}
164
asvitkinee0dbdbe2014-10-31 21:59:57165void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged(
asvitkine9a279832015-12-18 02:35:50166 const std::vector<SyntheticTrialGroup>& groups) {
asvitkinee0dbdbe2014-10-31 21:59:57167 base::AutoLock scoped_lock(lock_);
168
169 synthetic_variation_ids_set_.clear();
asvitkine9a279832015-12-18 02:35:50170 for (const SyntheticTrialGroup& group : groups) {
yutak3305f49d2016-12-13 10:32:31171 VariationID id =
asvitkinee0dbdbe2014-10-31 21:59:57172 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id);
yutak3305f49d2016-12-13 10:32:31173 if (id != EMPTY_ID) {
174 synthetic_variation_ids_set_.insert(
175 VariationIDEntry(id, GOOGLE_WEB_PROPERTIES));
176 }
177 id = GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES_SIGNED_IN,
178 group.id);
179 if (id != EMPTY_ID) {
180 synthetic_variation_ids_set_.insert(
181 VariationIDEntry(id, GOOGLE_WEB_PROPERTIES_SIGNED_IN));
182 }
asvitkinee0dbdbe2014-10-31 21:59:57183 }
184 UpdateVariationIDsHeaderValue();
185}
186
[email protected]ab7780792013-01-10 01:26:09187void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
[email protected]bd3b4712012-12-18 17:01:30188 base::AutoLock scoped_lock(lock_);
189 if (variation_ids_cache_initialized_)
190 return;
191
192 // Register for additional cache updates. This is done first to avoid a race
193 // that could cause registered FieldTrials to be missed.
fdoraycddcf0e2016-06-23 21:20:30194 DCHECK(base::ThreadTaskRunnerHandle::IsSet());
[email protected]bd3b4712012-12-18 17:01:30195 base::FieldTrialList::AddObserver(this);
196
[email protected]999f7b42013-02-04 16:14:25197 base::TimeTicks before_time = base::TimeTicks::Now();
198
[email protected]bd3b4712012-12-18 17:01:30199 base::FieldTrial::ActiveGroups initial_groups;
200 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups);
asvitkine35ba6472015-12-18 23:52:33201
202 for (const auto& entry : initial_groups) {
yutak3305f49d2016-12-13 10:32:31203 CacheVariationsId(entry.trial_name, entry.group_name,
204 GOOGLE_WEB_PROPERTIES);
205 CacheVariationsId(entry.trial_name, entry.group_name,
206 GOOGLE_WEB_PROPERTIES_SIGNED_IN);
207 CacheVariationsId(entry.trial_name, entry.group_name,
208 GOOGLE_WEB_PROPERTIES_TRIGGER);
[email protected]bd3b4712012-12-18 17:01:30209 }
210 UpdateVariationIDsHeaderValue();
211
[email protected]999f7b42013-02-04 16:14:25212 UMA_HISTOGRAM_CUSTOM_COUNTS(
213 "Variations.HeaderConstructionTime",
drbasicf0d1b262016-08-23 06:10:42214 (base::TimeTicks::Now() - before_time).InMicroseconds(), 1,
asvitkine9a279832015-12-18 02:35:50215 base::TimeDelta::FromSeconds(1).InMicroseconds(), 50);
[email protected]999f7b42013-02-04 16:14:25216
[email protected]bd3b4712012-12-18 17:01:30217 variation_ids_cache_initialized_ = true;
218}
219
yutak3305f49d2016-12-13 10:32:31220void VariationsHttpHeaderProvider::CacheVariationsId(
221 const std::string& trial_name,
222 const std::string& group_name,
223 IDCollectionKey key) {
224 const VariationID id = GetGoogleVariationID(key, trial_name, group_name);
225 if (id != EMPTY_ID)
226 variation_ids_set_.insert(VariationIDEntry(id, key));
227}
228
[email protected]ab7780792013-01-10 01:26:09229void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
230 lock_.AssertAcquired();
231
[email protected]bd3b4712012-12-18 17:01:30232 // The header value is a serialized protobuffer of Variation IDs which is
233 // base64 encoded before transmitting as a string.
yutak3305f49d2016-12-13 10:32:31234 cached_variation_ids_header_.clear();
235 cached_variation_ids_header_signed_in_.clear();
[email protected]1bd918d2013-10-13 18:23:09236
yutak3305f49d2016-12-13 10:32:31237 // If successful, swap the header value with the new one.
238 // Note that the list of IDs and the header could be temporarily out of sync
239 // if IDs are added as the header is recreated. The receiving servers are OK
240 // with such discrepancies.
241 cached_variation_ids_header_ = GenerateBase64EncodedProto(false);
242 cached_variation_ids_header_signed_in_ = GenerateBase64EncodedProto(true);
Jun Cai0e568632018-08-09 02:05:33243
244 for (auto& observer : observer_list_) {
245 observer.VariationIdsHeaderUpdated(cached_variation_ids_header_,
246 cached_variation_ids_header_signed_in_);
247 }
yutak3305f49d2016-12-13 10:32:31248}
249
250std::string VariationsHttpHeaderProvider::GenerateBase64EncodedProto(
251 bool is_signed_in) {
252 std::set<VariationIDEntry> all_variation_ids_set = GetAllVariationIds();
253
254 ClientVariations proto;
255 for (const VariationIDEntry& entry : all_variation_ids_set) {
256 switch (entry.second) {
257 case GOOGLE_WEB_PROPERTIES_SIGNED_IN:
258 if (is_signed_in)
259 proto.add_variation_id(entry.first);
260 break;
261 case GOOGLE_WEB_PROPERTIES:
262 proto.add_variation_id(entry.first);
263 break;
264 case GOOGLE_WEB_PROPERTIES_TRIGGER:
265 proto.add_trigger_variation_id(entry.first);
266 break;
Sky Malicea846ad7d2017-12-05 00:44:42267 case CHROME_SYNC_EVENT_LOGGER:
yutak3305f49d2016-12-13 10:32:31268 case ID_COLLECTION_COUNT:
269 // These cases included to get full enum coverage for switch, so that
270 // new enums introduce compiler warnings. Nothing to do for these.
271 break;
272 }
[email protected]8c2c5442014-04-04 18:55:29273 }
[email protected]bd3b4712012-12-18 17:01:30274
yutak3305f49d2016-12-13 10:32:31275 const size_t total_id_count =
276 proto.variation_id_size() + proto.trigger_variation_id_size();
277
278 if (total_id_count == 0)
279 return std::string();
280
[email protected]bd3b4712012-12-18 17:01:30281 // This is the bottleneck for the creation of the header, so validate the size
282 // here. Force a hard maximum on the ID count in case the Variations server
283 // returns too many IDs and DOSs receiving servers with large requests.
Alexei Svitkine93c85fc2018-07-20 20:21:05284 DCHECK_LE(total_id_count, 20U);
[email protected]a27ae2a2014-08-01 16:17:52285 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount",
286 total_id_count);
Alexei Svitkine93c85fc2018-07-20 20:21:05287 if (total_id_count > 30)
yutak3305f49d2016-12-13 10:32:31288 return std::string();
[email protected]8c2c5442014-04-04 18:55:29289
[email protected]bd3b4712012-12-18 17:01:30290 std::string serialized;
291 proto.SerializeToString(&serialized);
292
293 std::string hashed;
[email protected]33fca122013-12-11 01:48:50294 base::Base64Encode(serialized, &hashed);
yutak3305f49d2016-12-13 10:32:31295 return hashed;
[email protected]bd3b4712012-12-18 17:01:30296}
[email protected]ab7780792013-01-10 01:26:09297
Alexei Svitkine105f942e2018-02-17 02:53:48298bool VariationsHttpHeaderProvider::AddDefaultVariationIds(
299 const std::vector<std::string>& variation_ids) {
300 for (const std::string& entry : variation_ids) {
301 if (entry.empty()) {
302 default_variation_ids_set_.clear();
303 return false;
304 }
305 bool trigger_id =
306 base::StartsWith(entry, "t", base::CompareCase::SENSITIVE);
307 // Remove the "t" prefix if it's there.
308 std::string trimmed_entry = trigger_id ? entry.substr(1) : entry;
309
310 int variation_id = 0;
311 if (!base::StringToInt(trimmed_entry, &variation_id)) {
312 default_variation_ids_set_.clear();
313 return false;
314 }
315 default_variation_ids_set_.insert(VariationIDEntry(
316 variation_id,
317 trigger_id ? GOOGLE_WEB_PROPERTIES_TRIGGER : GOOGLE_WEB_PROPERTIES));
318 }
319 return true;
320}
321
yutak3305f49d2016-12-13 10:32:31322std::set<VariationsHttpHeaderProvider::VariationIDEntry>
323VariationsHttpHeaderProvider::GetAllVariationIds() {
asvitkine35ba6472015-12-18 23:52:33324 lock_.AssertAcquired();
325
yutak3305f49d2016-12-13 10:32:31326 std::set<VariationIDEntry> all_variation_ids_set = default_variation_ids_set_;
327 for (const VariationIDEntry& entry : variation_ids_set_) {
328 all_variation_ids_set.insert(entry);
329 }
330 for (const VariationIDEntry& entry : synthetic_variation_ids_set_) {
331 all_variation_ids_set.insert(entry);
332 }
asvitkine35ba6472015-12-18 23:52:33333 return all_variation_ids_set;
334}
335
[email protected]71011c1682014-07-09 17:19:16336} // namespace variations