1 // Copyright 2014 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.
5 #include "components/variations/net/variations_http_header_provider.h"
11 #include "base/base64.h"
12 #include "base/memory/singleton.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "components/google/core/browser/google_util.h"
18 #include "components/variations/proto/client_variations.pb.h"
19 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
20 #include "net/http/http_request_headers.h"
23 namespace variations
{
27 const char* kSuffixesToSetHeadersFor
[] = {
32 ".googleadservices.com",
34 ".googlesyndication.com",
35 ".googleusercontent.com",
41 const char kChromeUMAEnabled
[] = "X-Chrome-UMA-Enabled";
42 const char kClientData
[] = "X-Client-Data";
46 VariationsHttpHeaderProvider
* VariationsHttpHeaderProvider::GetInstance() {
47 return Singleton
<VariationsHttpHeaderProvider
>::get();
50 void VariationsHttpHeaderProvider::AppendHeaders(
54 net::HttpRequestHeaders
* headers
) {
55 // Note the criteria for attaching client experiment headers:
56 // 1. We only transmit to Google owned domains which can evaluate experiments.
57 // 1a. These include hosts which have a standard postfix such as:
58 // *.doubleclick.net or *.googlesyndication.com or
59 // exactly www.googleadservices.com or
60 // international TLD domains *.google.<TLD> or *.youtube.<TLD>.
61 // 2. Only transmit for non-Incognito profiles.
62 // 3. For the X-Chrome-UMA-Enabled bit, only set it if UMA is in fact enabled
63 // for this install of Chrome.
64 // 4. For the X-Client-Data header, only include non-empty variation IDs.
65 if (incognito
|| !ShouldAppendHeaders(url
))
69 headers
->SetHeaderIfMissing(kChromeUMAEnabled
, "1");
71 // Lazily initialize the header, if not already done, before attempting to
73 InitVariationIDsCacheIfNeeded();
75 std::string variation_ids_header_copy
;
77 base::AutoLock
scoped_lock(lock_
);
78 variation_ids_header_copy
= variation_ids_header_
;
81 if (!variation_ids_header_copy
.empty()) {
82 // Note that prior to M33 this header was named X-Chrome-Variations.
83 headers
->SetHeaderIfMissing(kClientData
, variation_ids_header_copy
);
87 bool VariationsHttpHeaderProvider::SetDefaultVariationIds(
88 const std::string
& variation_ids
) {
89 default_variation_ids_set_
.clear();
90 default_trigger_id_set_
.clear();
91 for (const base::StringPiece
& entry
: base::SplitStringPiece(
92 variation_ids
, ",", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
)) {
94 default_variation_ids_set_
.clear();
95 default_trigger_id_set_
.clear();
99 base::StartsWith(entry
, "t", base::CompareCase::SENSITIVE
);
100 // Remove the "t" prefix if it's there.
101 base::StringPiece trimmed_entry
= trigger_id
? entry
.substr(1) : entry
;
103 int variation_id
= 0;
104 if (!base::StringToInt(trimmed_entry
, &variation_id
)) {
105 default_variation_ids_set_
.clear();
106 default_trigger_id_set_
.clear();
110 default_trigger_id_set_
.insert(variation_id
);
112 default_variation_ids_set_
.insert(variation_id
);
117 std::set
<std::string
> VariationsHttpHeaderProvider::GetVariationHeaderNames()
119 std::set
<std::string
> headers
;
120 headers
.insert(kChromeUMAEnabled
);
121 headers
.insert(kClientData
);
125 void VariationsHttpHeaderProvider::ResetForTesting() {
126 base::AutoLock
scoped_lock(lock_
);
128 // Stop observing field trials so that it can be restarted when this is
129 // re-inited. Note: This is a no-op if this is not currently observing.
130 base::FieldTrialList::RemoveObserver(this);
131 variation_ids_cache_initialized_
= false;
134 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
135 : variation_ids_cache_initialized_(false) {
138 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {
141 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
142 const std::string
& trial_name
,
143 const std::string
& group_name
) {
145 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, trial_name
, group_name
);
146 VariationID new_trigger_id
= GetGoogleVariationID(
147 GOOGLE_WEB_PROPERTIES_TRIGGER
, trial_name
, group_name
);
148 if (new_id
== EMPTY_ID
&& new_trigger_id
== EMPTY_ID
)
151 base::AutoLock
scoped_lock(lock_
);
152 if (new_id
!= EMPTY_ID
)
153 variation_ids_set_
.insert(new_id
);
154 if (new_trigger_id
!= EMPTY_ID
)
155 variation_trigger_ids_set_
.insert(new_trigger_id
);
157 UpdateVariationIDsHeaderValue();
160 void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged(
161 const std::vector
<metrics::SyntheticTrialGroup
>& groups
) {
162 base::AutoLock
scoped_lock(lock_
);
164 synthetic_variation_ids_set_
.clear();
165 for (const metrics::SyntheticTrialGroup
& group
: groups
) {
166 const VariationID id
=
167 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES
, group
.id
);
169 synthetic_variation_ids_set_
.insert(id
);
171 UpdateVariationIDsHeaderValue();
174 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
175 base::AutoLock
scoped_lock(lock_
);
176 if (variation_ids_cache_initialized_
)
179 // Register for additional cache updates. This is done first to avoid a race
180 // that could cause registered FieldTrials to be missed.
181 DCHECK(base::MessageLoop::current());
182 base::FieldTrialList::AddObserver(this);
184 base::TimeTicks before_time
= base::TimeTicks::Now();
186 base::FieldTrial::ActiveGroups initial_groups
;
187 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups
);
188 for (base::FieldTrial::ActiveGroups::const_iterator it
=
189 initial_groups
.begin();
190 it
!= initial_groups
.end(); ++it
) {
191 const VariationID id
=
192 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, it
->trial_name
,
195 variation_ids_set_
.insert(id
);
197 const VariationID trigger_id
=
198 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER
, it
->trial_name
,
200 if (trigger_id
!= EMPTY_ID
)
201 variation_trigger_ids_set_
.insert(trigger_id
);
203 UpdateVariationIDsHeaderValue();
205 UMA_HISTOGRAM_CUSTOM_COUNTS(
206 "Variations.HeaderConstructionTime",
207 (base::TimeTicks::Now() - before_time
).InMicroseconds(),
209 base::TimeDelta::FromSeconds(1).InMicroseconds(),
212 variation_ids_cache_initialized_
= true;
215 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
216 lock_
.AssertAcquired();
218 // The header value is a serialized protobuffer of Variation IDs which is
219 // base64 encoded before transmitting as a string.
220 variation_ids_header_
.clear();
222 if (variation_ids_set_
.empty() && default_variation_ids_set_
.empty() &&
223 variation_trigger_ids_set_
.empty() && default_trigger_id_set_
.empty() &&
224 synthetic_variation_ids_set_
.empty()) {
228 // This is the bottleneck for the creation of the header, so validate the size
229 // here. Force a hard maximum on the ID count in case the Variations server
230 // returns too many IDs and DOSs receiving servers with large requests.
231 const size_t total_id_count
=
232 variation_ids_set_
.size() + variation_trigger_ids_set_
.size();
233 DCHECK_LE(total_id_count
, 10U);
234 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount",
236 if (total_id_count
> 20)
239 // Merge the two sets of experiment ids.
240 std::set
<VariationID
> all_variation_ids_set
= default_variation_ids_set_
;
241 for (VariationID id
: variation_ids_set_
)
242 all_variation_ids_set
.insert(id
);
243 for (VariationID id
: synthetic_variation_ids_set_
)
244 all_variation_ids_set
.insert(id
);
246 std::set
<VariationID
> all_trigger_ids_set
= default_trigger_id_set_
;
247 for (VariationID id
: variation_trigger_ids_set_
)
248 all_trigger_ids_set
.insert(id
);
250 ClientVariations proto
;
251 for (VariationID id
: all_variation_ids_set
)
252 proto
.add_variation_id(id
);
253 for (VariationID id
: all_trigger_ids_set
)
254 proto
.add_trigger_variation_id(id
);
256 std::string serialized
;
257 proto
.SerializeToString(&serialized
);
260 base::Base64Encode(serialized
, &hashed
);
261 // If successful, swap the header value with the new one.
262 // Note that the list of IDs and the header could be temporarily out of sync
263 // if IDs are added as the header is recreated. The receiving servers are OK
264 // with such discrepancies.
265 variation_ids_header_
= hashed
;
269 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL
& url
) {
270 if (google_util::IsGoogleDomainUrl(url
, google_util::ALLOW_SUBDOMAIN
,
271 google_util::ALLOW_NON_STANDARD_PORTS
)) {
275 if (!url
.is_valid() || !url
.SchemeIsHTTPOrHTTPS())
278 // Some domains don't have international TLD extensions, so testing for them
279 // is very straight forward.
280 const std::string host
= url
.host();
281 for (size_t i
= 0; i
< arraysize(kSuffixesToSetHeadersFor
); ++i
) {
282 if (base::EndsWith(host
, kSuffixesToSetHeadersFor
[i
],
283 base::CompareCase::INSENSITIVE_ASCII
))
287 return google_util::IsYoutubeDomainUrl(url
, google_util::ALLOW_SUBDOMAIN
,
288 google_util::ALLOW_NON_STANDARD_PORTS
);
291 } // namespace variations