blob: 6754cef19ab5019ad9f17a328e991f1a48f32e01 [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
isherman3be67db2014-10-24 05:57:445#include "components/variations/net/variations_http_header_provider.h"
[email protected]bd3b4712012-12-18 17:01:306
horoe09b6c82014-11-01 02:08:287#include <set>
8#include <string>
[email protected]1bd918d2013-10-13 18:23:099#include <vector>
10
[email protected]bd3b4712012-12-18 17:01:3011#include "base/base64.h"
12#include "base/memory/singleton.h"
asvitkine454600f2015-06-16 16:34:5013#include "base/metrics/histogram_macros.h"
[email protected]1bd918d2013-10-13 18:23:0914#include "base/strings/string_number_conversions.h"
15#include "base/strings/string_split.h"
[email protected]7f8a9932013-07-26 20:43:3416#include "base/strings/string_util.h"
[email protected]8e44a5b02014-06-19 19:03:2417#include "components/google/core/browser/google_util.h"
[email protected]ea15bd52014-07-14 22:42:5018#include "components/variations/proto/client_variations.pb.h"
[email protected]7f8a9932013-07-26 20:43:3419#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
[email protected]bd3b4712012-12-18 17:01:3020#include "net/http/http_request_headers.h"
[email protected]7f8a9932013-07-26 20:43:3421#include "url/gurl.h"
[email protected]bd3b4712012-12-18 17:01:3022
[email protected]71011c1682014-07-09 17:19:1623namespace variations {
[email protected]ab7780792013-01-10 01:26:0924
[email protected]64d617e2014-05-31 04:37:5425namespace {
26
27const char* kSuffixesToSetHeadersFor[] = {
28 ".android.com",
29 ".doubleclick.com",
30 ".doubleclick.net",
31 ".ggpht.com",
32 ".googleadservices.com",
33 ".googleapis.com",
34 ".googlesyndication.com",
35 ".googleusercontent.com",
36 ".googlevideo.com",
37 ".gstatic.com",
38 ".ytimg.com",
39};
40
horoe09b6c82014-11-01 02:08:2841const char kChromeUMAEnabled[] = "X-Chrome-UMA-Enabled";
42const char kClientData[] = "X-Client-Data";
43
[email protected]64d617e2014-05-31 04:37:5444} // namespace
45
[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
[email protected]ab7780792013-01-10 01:26:0950void VariationsHttpHeaderProvider::AppendHeaders(
51 const GURL& url,
52 bool incognito,
53 bool uma_enabled,
54 net::HttpRequestHeaders* headers) {
[email protected]ea15bd52014-07-14 22:42:5055 // Note the criteria for attaching client experiment headers:
[email protected]0eea1cf72014-04-01 19:39:1956 // 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>.
[email protected]bd3b4712012-12-18 17:01:3061 // 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.
[email protected]05ef1f22013-12-05 22:24:3464 // 4. For the X-Client-Data header, only include non-empty variation IDs.
[email protected]7f8a9932013-07-26 20:43:3465 if (incognito || !ShouldAppendHeaders(url))
[email protected]bd3b4712012-12-18 17:01:3066 return;
[email protected]bd3b4712012-12-18 17:01:3067
68 if (uma_enabled)
horoe09b6c82014-11-01 02:08:2869 headers->SetHeaderIfMissing(kChromeUMAEnabled, "1");
[email protected]bd3b4712012-12-18 17:01:3070
71 // Lazily initialize the header, if not already done, before attempting to
72 // transmit it.
73 InitVariationIDsCacheIfNeeded();
[email protected]ab7780792013-01-10 01:26:0974
75 std::string variation_ids_header_copy;
76 {
77 base::AutoLock scoped_lock(lock_);
78 variation_ids_header_copy = variation_ids_header_;
79 }
80
81 if (!variation_ids_header_copy.empty()) {
[email protected]05ef1f22013-12-05 22:24:3482 // Note that prior to M33 this header was named X-Chrome-Variations.
horoe09b6c82014-11-01 02:08:2883 headers->SetHeaderIfMissing(kClientData, variation_ids_header_copy);
[email protected]ab7780792013-01-10 01:26:0984 }
[email protected]bd3b4712012-12-18 17:01:3085}
86
[email protected]1bd918d2013-10-13 18:23:0987bool VariationsHttpHeaderProvider::SetDefaultVariationIds(
88 const std::string& variation_ids) {
89 default_variation_ids_set_.clear();
[email protected]8c2c5442014-04-04 18:55:2990 default_trigger_id_set_.clear();
brettw8be197d12015-07-23 23:23:3191 for (const base::StringPiece& entry : base::SplitStringPiece(
92 variation_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
93 if (entry.empty()) {
[email protected]1bd918d2013-10-13 18:23:0994 default_variation_ids_set_.clear();
[email protected]8c2c5442014-04-04 18:55:2995 default_trigger_id_set_.clear();
[email protected]1bd918d2013-10-13 18:23:0996 return false;
97 }
brettw8be197d12015-07-23 23:23:3198 bool trigger_id =
99 base::StartsWith(entry, "t", base::CompareCase::SENSITIVE);
[email protected]8c2c5442014-04-04 18:55:29100 // Remove the "t" prefix if it's there.
brettw8be197d12015-07-23 23:23:31101 base::StringPiece trimmed_entry = trigger_id ? entry.substr(1) : entry;
[email protected]8c2c5442014-04-04 18:55:29102
103 int variation_id = 0;
brettw8be197d12015-07-23 23:23:31104 if (!base::StringToInt(trimmed_entry, &variation_id)) {
[email protected]8c2c5442014-04-04 18:55:29105 default_variation_ids_set_.clear();
106 default_trigger_id_set_.clear();
107 return false;
108 }
109 if (trigger_id)
110 default_trigger_id_set_.insert(variation_id);
111 else
112 default_variation_ids_set_.insert(variation_id);
[email protected]1bd918d2013-10-13 18:23:09113 }
114 return true;
115}
116
asvitkineb4ed78682015-03-12 18:18:54117std::set<std::string> VariationsHttpHeaderProvider::GetVariationHeaderNames()
118 const {
119 std::set<std::string> headers;
120 headers.insert(kChromeUMAEnabled);
121 headers.insert(kClientData);
122 return headers;
123}
124
125void VariationsHttpHeaderProvider::ResetForTesting() {
126 base::AutoLock scoped_lock(lock_);
127
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;
132}
133
[email protected]ab7780792013-01-10 01:26:09134VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
[email protected]bd3b4712012-12-18 17:01:30135 : variation_ids_cache_initialized_(false) {
136}
137
[email protected]ab7780792013-01-10 01:26:09138VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {
[email protected]bd3b4712012-12-18 17:01:30139}
140
[email protected]ab7780792013-01-10 01:26:09141void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
[email protected]bd3b4712012-12-18 17:01:30142 const std::string& trial_name,
143 const std::string& group_name) {
[email protected]ab7780792013-01-10 01:26:09144 VariationID new_id =
145 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_name, group_name);
[email protected]e51dcb0c2014-05-06 16:56:10146 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)
[email protected]bd3b4712012-12-18 17:01:30149 return;
[email protected]ab7780792013-01-10 01:26:09150
[email protected]bd3b4712012-12-18 17:01:30151 base::AutoLock scoped_lock(lock_);
[email protected]e51dcb0c2014-05-06 16:56:10152 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);
156
[email protected]bd3b4712012-12-18 17:01:30157 UpdateVariationIDsHeaderValue();
158}
159
asvitkinee0dbdbe2014-10-31 21:59:57160void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged(
161 const std::vector<metrics::SyntheticTrialGroup>& groups) {
162 base::AutoLock scoped_lock(lock_);
163
164 synthetic_variation_ids_set_.clear();
165 for (const metrics::SyntheticTrialGroup& group : groups) {
166 const VariationID id =
167 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id);
168 if (id != EMPTY_ID)
169 synthetic_variation_ids_set_.insert(id);
170 }
171 UpdateVariationIDsHeaderValue();
172}
173
[email protected]ab7780792013-01-10 01:26:09174void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
[email protected]bd3b4712012-12-18 17:01:30175 base::AutoLock scoped_lock(lock_);
176 if (variation_ids_cache_initialized_)
177 return;
178
179 // Register for additional cache updates. This is done first to avoid a race
180 // that could cause registered FieldTrials to be missed.
[email protected]b3a25092013-05-28 22:08:16181 DCHECK(base::MessageLoop::current());
[email protected]bd3b4712012-12-18 17:01:30182 base::FieldTrialList::AddObserver(this);
183
[email protected]999f7b42013-02-04 16:14:25184 base::TimeTicks before_time = base::TimeTicks::Now();
185
[email protected]bd3b4712012-12-18 17:01:30186 base::FieldTrial::ActiveGroups initial_groups;
187 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups);
188 for (base::FieldTrial::ActiveGroups::const_iterator it =
[email protected]ab7780792013-01-10 01:26:09189 initial_groups.begin();
190 it != initial_groups.end(); ++it) {
191 const VariationID id =
192 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, it->trial_name,
193 it->group_name);
[email protected]90acad02013-01-16 17:17:54194 if (id != EMPTY_ID)
[email protected]bd3b4712012-12-18 17:01:30195 variation_ids_set_.insert(id);
[email protected]e51dcb0c2014-05-06 16:56:10196
197 const VariationID trigger_id =
198 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER, it->trial_name,
199 it->group_name);
200 if (trigger_id != EMPTY_ID)
201 variation_trigger_ids_set_.insert(trigger_id);
[email protected]bd3b4712012-12-18 17:01:30202 }
203 UpdateVariationIDsHeaderValue();
204
[email protected]999f7b42013-02-04 16:14:25205 UMA_HISTOGRAM_CUSTOM_COUNTS(
206 "Variations.HeaderConstructionTime",
207 (base::TimeTicks::Now() - before_time).InMicroseconds(),
208 0,
209 base::TimeDelta::FromSeconds(1).InMicroseconds(),
210 50);
211
[email protected]bd3b4712012-12-18 17:01:30212 variation_ids_cache_initialized_ = true;
213}
214
[email protected]ab7780792013-01-10 01:26:09215void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
216 lock_.AssertAcquired();
217
[email protected]bd3b4712012-12-18 17:01:30218 // The header value is a serialized protobuffer of Variation IDs which is
219 // base64 encoded before transmitting as a string.
[email protected]1bd918d2013-10-13 18:23:09220 variation_ids_header_.clear();
221
[email protected]8c2c5442014-04-04 18:55:29222 if (variation_ids_set_.empty() && default_variation_ids_set_.empty() &&
asvitkinee0dbdbe2014-10-31 21:59:57223 variation_trigger_ids_set_.empty() && default_trigger_id_set_.empty() &&
224 synthetic_variation_ids_set_.empty()) {
[email protected]bd3b4712012-12-18 17:01:30225 return;
[email protected]8c2c5442014-04-04 18:55:29226 }
[email protected]bd3b4712012-12-18 17:01:30227
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.
[email protected]e51dcb0c2014-05-06 16:56:10231 const size_t total_id_count =
232 variation_ids_set_.size() + variation_trigger_ids_set_.size();
233 DCHECK_LE(total_id_count, 10U);
[email protected]a27ae2a2014-08-01 16:17:52234 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount",
235 total_id_count);
[email protected]e51dcb0c2014-05-06 16:56:10236 if (total_id_count > 20)
[email protected]bd3b4712012-12-18 17:01:30237 return;
[email protected]bd3b4712012-12-18 17:01:30238
[email protected]1bd918d2013-10-13 18:23:09239 // Merge the two sets of experiment ids.
240 std::set<VariationID> all_variation_ids_set = default_variation_ids_set_;
asvitkinee0dbdbe2014-10-31 21:59:57241 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);
[email protected]bd3b4712012-12-18 17:01:30245
[email protected]e51dcb0c2014-05-06 16:56:10246 std::set<VariationID> all_trigger_ids_set = default_trigger_id_set_;
asvitkinee0dbdbe2014-10-31 21:59:57247 for (VariationID id : variation_trigger_ids_set_)
248 all_trigger_ids_set.insert(id);
249
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);
[email protected]8c2c5442014-04-04 18:55:29255
[email protected]bd3b4712012-12-18 17:01:30256 std::string serialized;
257 proto.SerializeToString(&serialized);
258
259 std::string hashed;
[email protected]33fca122013-12-11 01:48:50260 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;
[email protected]bd3b4712012-12-18 17:01:30266}
[email protected]ab7780792013-01-10 01:26:09267
[email protected]7f8a9932013-07-26 20:43:34268// static
269bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL& url) {
270 if (google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
271 google_util::ALLOW_NON_STANDARD_PORTS)) {
272 return true;
273 }
274
[email protected]91f5689032013-08-22 01:43:33275 if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS())
[email protected]7f8a9932013-07-26 20:43:34276 return false;
277
[email protected]0eea1cf72014-04-01 19:39:19278 // Some domains don't have international TLD extensions, so testing for them
279 // is very straight forward.
[email protected]7f8a9932013-07-26 20:43:34280 const std::string host = url.host();
[email protected]64d617e2014-05-31 04:37:54281 for (size_t i = 0; i < arraysize(kSuffixesToSetHeadersFor); ++i) {
brettwa7ff1b292015-07-16 17:49:29282 if (base::EndsWith(host, kSuffixesToSetHeadersFor[i],
283 base::CompareCase::INSENSITIVE_ASCII))
[email protected]64d617e2014-05-31 04:37:54284 return true;
[email protected]0eea1cf72014-04-01 19:39:19285 }
286
[email protected]87abe9ea2014-06-17 03:29:54287 return google_util::IsYoutubeDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
288 google_util::ALLOW_NON_STANDARD_PORTS);
[email protected]7f8a9932013-07-26 20:43:34289}
290
[email protected]71011c1682014-07-09 17:19:16291} // namespace variations