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/variations_http_header_provider.h"
9 #include "base/base64.h"
10 #include "base/memory/singleton.h"
11 #include "base/metrics/histogram.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "components/google/core/browser/google_util.h"
16 #include "components/variations/proto/client_variations.pb.h"
17 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
18 #include "net/http/http_request_headers.h"
21 namespace variations
{
25 const char* kSuffixesToSetHeadersFor
[] = {
30 ".googleadservices.com",
32 ".googlesyndication.com",
33 ".googleusercontent.com",
41 VariationsHttpHeaderProvider
* VariationsHttpHeaderProvider::GetInstance() {
42 return Singleton
<VariationsHttpHeaderProvider
>::get();
45 void VariationsHttpHeaderProvider::AppendHeaders(
49 net::HttpRequestHeaders
* headers
) {
50 // Note the criteria for attaching client experiment headers:
51 // 1. We only transmit to Google owned domains which can evaluate experiments.
52 // 1a. These include hosts which have a standard postfix such as:
53 // *.doubleclick.net or *.googlesyndication.com or
54 // exactly www.googleadservices.com or
55 // international TLD domains *.google.<TLD> or *.youtube.<TLD>.
56 // 2. Only transmit for non-Incognito profiles.
57 // 3. For the X-Chrome-UMA-Enabled bit, only set it if UMA is in fact enabled
58 // for this install of Chrome.
59 // 4. For the X-Client-Data header, only include non-empty variation IDs.
60 if (incognito
|| !ShouldAppendHeaders(url
))
64 headers
->SetHeaderIfMissing("X-Chrome-UMA-Enabled", "1");
66 // Lazily initialize the header, if not already done, before attempting to
68 InitVariationIDsCacheIfNeeded();
70 std::string variation_ids_header_copy
;
72 base::AutoLock
scoped_lock(lock_
);
73 variation_ids_header_copy
= variation_ids_header_
;
76 if (!variation_ids_header_copy
.empty()) {
77 // Note that prior to M33 this header was named X-Chrome-Variations.
78 headers
->SetHeaderIfMissing("X-Client-Data",
79 variation_ids_header_copy
);
83 bool VariationsHttpHeaderProvider::SetDefaultVariationIds(
84 const std::string
& variation_ids
) {
85 default_variation_ids_set_
.clear();
86 default_trigger_id_set_
.clear();
87 std::vector
<std::string
> entries
;
88 base::SplitString(variation_ids
, ',', &entries
);
89 for (std::vector
<std::string
>::const_iterator it
= entries
.begin();
90 it
!= entries
.end(); ++it
) {
92 default_variation_ids_set_
.clear();
93 default_trigger_id_set_
.clear();
96 bool trigger_id
= StartsWithASCII(*it
, "t", true);
97 // Remove the "t" prefix if it's there.
98 std::string entry
= trigger_id
? it
->substr(1) : *it
;
100 int variation_id
= 0;
101 if (!base::StringToInt(entry
, &variation_id
)) {
102 default_variation_ids_set_
.clear();
103 default_trigger_id_set_
.clear();
107 default_trigger_id_set_
.insert(variation_id
);
109 default_variation_ids_set_
.insert(variation_id
);
114 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
115 : variation_ids_cache_initialized_(false) {
118 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {
121 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
122 const std::string
& trial_name
,
123 const std::string
& group_name
) {
125 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, trial_name
, group_name
);
126 VariationID new_trigger_id
= GetGoogleVariationID(
127 GOOGLE_WEB_PROPERTIES_TRIGGER
, trial_name
, group_name
);
128 if (new_id
== EMPTY_ID
&& new_trigger_id
== EMPTY_ID
)
131 base::AutoLock
scoped_lock(lock_
);
132 if (new_id
!= EMPTY_ID
)
133 variation_ids_set_
.insert(new_id
);
134 if (new_trigger_id
!= EMPTY_ID
)
135 variation_trigger_ids_set_
.insert(new_trigger_id
);
137 UpdateVariationIDsHeaderValue();
140 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
141 base::AutoLock
scoped_lock(lock_
);
142 if (variation_ids_cache_initialized_
)
145 // Register for additional cache updates. This is done first to avoid a race
146 // that could cause registered FieldTrials to be missed.
147 DCHECK(base::MessageLoop::current());
148 base::FieldTrialList::AddObserver(this);
150 base::TimeTicks before_time
= base::TimeTicks::Now();
152 base::FieldTrial::ActiveGroups initial_groups
;
153 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups
);
154 for (base::FieldTrial::ActiveGroups::const_iterator it
=
155 initial_groups
.begin();
156 it
!= initial_groups
.end(); ++it
) {
157 const VariationID id
=
158 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, it
->trial_name
,
161 variation_ids_set_
.insert(id
);
163 const VariationID trigger_id
=
164 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER
, it
->trial_name
,
166 if (trigger_id
!= EMPTY_ID
)
167 variation_trigger_ids_set_
.insert(trigger_id
);
169 UpdateVariationIDsHeaderValue();
171 UMA_HISTOGRAM_CUSTOM_COUNTS(
172 "Variations.HeaderConstructionTime",
173 (base::TimeTicks::Now() - before_time
).InMicroseconds(),
175 base::TimeDelta::FromSeconds(1).InMicroseconds(),
178 variation_ids_cache_initialized_
= true;
181 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
182 lock_
.AssertAcquired();
184 // The header value is a serialized protobuffer of Variation IDs which is
185 // base64 encoded before transmitting as a string.
186 variation_ids_header_
.clear();
188 if (variation_ids_set_
.empty() && default_variation_ids_set_
.empty() &&
189 variation_trigger_ids_set_
.empty() && default_trigger_id_set_
.empty()) {
193 // This is the bottleneck for the creation of the header, so validate the size
194 // here. Force a hard maximum on the ID count in case the Variations server
195 // returns too many IDs and DOSs receiving servers with large requests.
196 const size_t total_id_count
=
197 variation_ids_set_
.size() + variation_trigger_ids_set_
.size();
198 DCHECK_LE(total_id_count
, 10U);
199 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount",
201 if (total_id_count
> 20)
204 // Merge the two sets of experiment ids.
205 std::set
<VariationID
> all_variation_ids_set
= default_variation_ids_set_
;
206 for (std::set
<VariationID
>::const_iterator it
= variation_ids_set_
.begin();
207 it
!= variation_ids_set_
.end(); ++it
) {
208 all_variation_ids_set
.insert(*it
);
210 ClientVariations proto
;
211 for (std::set
<VariationID
>::const_iterator it
= all_variation_ids_set
.begin();
212 it
!= all_variation_ids_set
.end(); ++it
) {
213 proto
.add_variation_id(*it
);
216 std::set
<VariationID
> all_trigger_ids_set
= default_trigger_id_set_
;
217 for (std::set
<VariationID
>::const_iterator it
=
218 variation_trigger_ids_set_
.begin();
219 it
!= variation_trigger_ids_set_
.end(); ++it
) {
220 all_trigger_ids_set
.insert(*it
);
222 for (std::set
<VariationID
>::const_iterator it
= all_trigger_ids_set
.begin();
223 it
!= all_trigger_ids_set
.end(); ++it
) {
224 proto
.add_trigger_variation_id(*it
);
227 std::string serialized
;
228 proto
.SerializeToString(&serialized
);
231 base::Base64Encode(serialized
, &hashed
);
232 // If successful, swap the header value with the new one.
233 // Note that the list of IDs and the header could be temporarily out of sync
234 // if IDs are added as the header is recreated. The receiving servers are OK
235 // with such discrepancies.
236 variation_ids_header_
= hashed
;
240 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL
& url
) {
241 if (google_util::IsGoogleDomainUrl(url
, google_util::ALLOW_SUBDOMAIN
,
242 google_util::ALLOW_NON_STANDARD_PORTS
)) {
246 if (!url
.is_valid() || !url
.SchemeIsHTTPOrHTTPS())
249 // Some domains don't have international TLD extensions, so testing for them
250 // is very straight forward.
251 const std::string host
= url
.host();
252 for (size_t i
= 0; i
< arraysize(kSuffixesToSetHeadersFor
); ++i
) {
253 if (EndsWith(host
, kSuffixesToSetHeadersFor
[i
], false))
257 return google_util::IsYoutubeDomainUrl(url
, google_util::ALLOW_SUBDOMAIN
,
258 google_util::ALLOW_NON_STANDARD_PORTS
);
261 } // namespace variations