1 // Copyright (c) 2012 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 "chrome/browser/metrics/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 "chrome/browser/google/google_util.h"
16 #include "chrome/common/metrics/proto/chrome_experiments.pb.h"
17 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
18 #include "net/http/http_request_headers.h"
21 namespace chrome_variations
{
23 VariationsHttpHeaderProvider
* VariationsHttpHeaderProvider::GetInstance() {
24 return Singleton
<VariationsHttpHeaderProvider
>::get();
27 void VariationsHttpHeaderProvider::AppendHeaders(
31 net::HttpRequestHeaders
* headers
) {
32 // Note the criteria for attaching Chrome experiment headers:
33 // 1. We only transmit to *.google.<TLD> or *.youtube.<TLD> domains.
34 // 2. Only transmit for non-Incognito profiles.
35 // 3. For the X-Chrome-UMA-Enabled bit, only set it if UMA is in fact enabled
36 // for this install of Chrome.
37 // 4. For the X-Client-Data header, only include non-empty variation IDs.
38 if (incognito
|| !ShouldAppendHeaders(url
))
42 headers
->SetHeaderIfMissing("X-Chrome-UMA-Enabled", "1");
44 // Lazily initialize the header, if not already done, before attempting to
46 InitVariationIDsCacheIfNeeded();
48 std::string variation_ids_header_copy
;
50 base::AutoLock
scoped_lock(lock_
);
51 variation_ids_header_copy
= variation_ids_header_
;
54 if (!variation_ids_header_copy
.empty()) {
55 // Note that prior to M33 this header was named X-Chrome-Variations.
56 headers
->SetHeaderIfMissing("X-Client-Data",
57 variation_ids_header_copy
);
61 bool VariationsHttpHeaderProvider::SetDefaultVariationIds(
62 const std::string
& variation_ids
) {
63 default_variation_ids_set_
.clear();
64 std::vector
<std::string
> entries
;
65 base::SplitString(variation_ids
, ',', &entries
);
66 for (std::vector
<std::string
>::const_iterator it
= entries
.begin();
67 it
!= entries
.end(); ++it
) {
69 if (!base::StringToInt(*it
, &variation_id
)) {
70 default_variation_ids_set_
.clear();
73 default_variation_ids_set_
.insert(variation_id
);
78 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
79 : variation_ids_cache_initialized_(false) {
82 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {
85 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
86 const std::string
& trial_name
,
87 const std::string
& group_name
) {
89 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, trial_name
, group_name
);
90 if (new_id
== EMPTY_ID
)
93 base::AutoLock
scoped_lock(lock_
);
94 variation_ids_set_
.insert(new_id
);
95 UpdateVariationIDsHeaderValue();
98 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
99 base::AutoLock
scoped_lock(lock_
);
100 if (variation_ids_cache_initialized_
)
103 // Register for additional cache updates. This is done first to avoid a race
104 // that could cause registered FieldTrials to be missed.
105 DCHECK(base::MessageLoop::current());
106 base::FieldTrialList::AddObserver(this);
108 base::TimeTicks before_time
= base::TimeTicks::Now();
110 base::FieldTrial::ActiveGroups initial_groups
;
111 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups
);
112 for (base::FieldTrial::ActiveGroups::const_iterator it
=
113 initial_groups
.begin();
114 it
!= initial_groups
.end(); ++it
) {
115 const VariationID id
=
116 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES
, it
->trial_name
,
119 variation_ids_set_
.insert(id
);
121 UpdateVariationIDsHeaderValue();
123 UMA_HISTOGRAM_CUSTOM_COUNTS(
124 "Variations.HeaderConstructionTime",
125 (base::TimeTicks::Now() - before_time
).InMicroseconds(),
127 base::TimeDelta::FromSeconds(1).InMicroseconds(),
130 variation_ids_cache_initialized_
= true;
133 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
134 lock_
.AssertAcquired();
136 // The header value is a serialized protobuffer of Variation IDs which is
137 // base64 encoded before transmitting as a string.
138 variation_ids_header_
.clear();
140 if (variation_ids_set_
.empty() && default_variation_ids_set_
.empty())
143 // This is the bottleneck for the creation of the header, so validate the size
144 // here. Force a hard maximum on the ID count in case the Variations server
145 // returns too many IDs and DOSs receiving servers with large requests.
146 DCHECK_LE(variation_ids_set_
.size(), 10U);
147 if (variation_ids_set_
.size() > 20)
150 // Merge the two sets of experiment ids.
151 std::set
<VariationID
> all_variation_ids_set
= default_variation_ids_set_
;
152 for (std::set
<VariationID
>::const_iterator it
= variation_ids_set_
.begin();
153 it
!= variation_ids_set_
.end(); ++it
) {
154 all_variation_ids_set
.insert(*it
);
156 metrics::ChromeVariations proto
;
157 for (std::set
<VariationID
>::const_iterator it
= all_variation_ids_set
.begin();
158 it
!= all_variation_ids_set
.end(); ++it
) {
159 proto
.add_variation_id(*it
);
162 std::string serialized
;
163 proto
.SerializeToString(&serialized
);
166 base::Base64Encode(serialized
, &hashed
);
167 // If successful, swap the header value with the new one.
168 // Note that the list of IDs and the header could be temporarily out of sync
169 // if IDs are added as the header is recreated. The receiving servers are OK
170 // with such discrepancies.
171 variation_ids_header_
= hashed
;
175 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL
& url
) {
176 if (google_util::IsGoogleDomainUrl(url
, google_util::ALLOW_SUBDOMAIN
,
177 google_util::ALLOW_NON_STANDARD_PORTS
)) {
181 // The below mirrors logic in IsGoogleDomainUrl(), but for youtube.<TLD>.
182 if (!url
.is_valid() || !url
.SchemeIsHTTPOrHTTPS())
185 const std::string host
= url
.host();
186 const size_t tld_length
= net::registry_controlled_domains::GetRegistryLength(
188 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES
,
189 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES
);
190 if ((tld_length
== 0) || (tld_length
== std::string::npos
))
193 const std::string
host_minus_tld(host
, 0, host
.length() - tld_length
);
194 return LowerCaseEqualsASCII(host_minus_tld
, "youtube.") ||
195 EndsWith(host_minus_tld
, ".youtube.", false);
198 } // namespace chrome_variations