• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "components/variations/variations_http_header_provider.h"
6 
7 #include <vector>
8 
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"
19 #include "url/gurl.h"
20 
21 namespace variations {
22 
23 namespace {
24 
25 const char* kSuffixesToSetHeadersFor[] = {
26   ".android.com",
27   ".doubleclick.com",
28   ".doubleclick.net",
29   ".ggpht.com",
30   ".googleadservices.com",
31   ".googleapis.com",
32   ".googlesyndication.com",
33   ".googleusercontent.com",
34   ".googlevideo.com",
35   ".gstatic.com",
36   ".ytimg.com",
37 };
38 
39 }  // namespace
40 
GetInstance()41 VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() {
42   return Singleton<VariationsHttpHeaderProvider>::get();
43 }
44 
AppendHeaders(const GURL & url,bool incognito,bool uma_enabled,net::HttpRequestHeaders * headers)45 void VariationsHttpHeaderProvider::AppendHeaders(
46     const GURL& url,
47     bool incognito,
48     bool uma_enabled,
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))
61     return;
62 
63   if (uma_enabled)
64     headers->SetHeaderIfMissing("X-Chrome-UMA-Enabled", "1");
65 
66   // Lazily initialize the header, if not already done, before attempting to
67   // transmit it.
68   InitVariationIDsCacheIfNeeded();
69 
70   std::string variation_ids_header_copy;
71   {
72     base::AutoLock scoped_lock(lock_);
73     variation_ids_header_copy = variation_ids_header_;
74   }
75 
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);
80   }
81 }
82 
SetDefaultVariationIds(const std::string & variation_ids)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) {
91     if (it->empty()) {
92       default_variation_ids_set_.clear();
93       default_trigger_id_set_.clear();
94       return false;
95     }
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;
99 
100     int variation_id = 0;
101     if (!base::StringToInt(entry, &variation_id)) {
102       default_variation_ids_set_.clear();
103       default_trigger_id_set_.clear();
104       return false;
105     }
106     if (trigger_id)
107       default_trigger_id_set_.insert(variation_id);
108     else
109       default_variation_ids_set_.insert(variation_id);
110   }
111   return true;
112 }
113 
VariationsHttpHeaderProvider()114 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
115     : variation_ids_cache_initialized_(false) {
116 }
117 
~VariationsHttpHeaderProvider()118 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {
119 }
120 
OnFieldTrialGroupFinalized(const std::string & trial_name,const std::string & group_name)121 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
122     const std::string& trial_name,
123     const std::string& group_name) {
124   VariationID new_id =
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)
129     return;
130 
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);
136 
137   UpdateVariationIDsHeaderValue();
138 }
139 
InitVariationIDsCacheIfNeeded()140 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
141   base::AutoLock scoped_lock(lock_);
142   if (variation_ids_cache_initialized_)
143     return;
144 
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);
149 
150   base::TimeTicks before_time = base::TimeTicks::Now();
151 
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,
159                              it->group_name);
160     if (id != EMPTY_ID)
161       variation_ids_set_.insert(id);
162 
163     const VariationID trigger_id =
164         GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER, it->trial_name,
165                              it->group_name);
166     if (trigger_id != EMPTY_ID)
167       variation_trigger_ids_set_.insert(trigger_id);
168   }
169   UpdateVariationIDsHeaderValue();
170 
171   UMA_HISTOGRAM_CUSTOM_COUNTS(
172       "Variations.HeaderConstructionTime",
173       (base::TimeTicks::Now() - before_time).InMicroseconds(),
174       0,
175       base::TimeDelta::FromSeconds(1).InMicroseconds(),
176       50);
177 
178   variation_ids_cache_initialized_ = true;
179 }
180 
UpdateVariationIDsHeaderValue()181 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
182   lock_.AssertAcquired();
183 
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();
187 
188   if (variation_ids_set_.empty() && default_variation_ids_set_.empty() &&
189       variation_trigger_ids_set_.empty() && default_trigger_id_set_.empty()) {
190     return;
191   }
192 
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",
200                            total_id_count);
201   if (total_id_count > 20)
202     return;
203 
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);
209   }
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);
214   }
215 
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);
221   }
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);
225   }
226 
227   std::string serialized;
228   proto.SerializeToString(&serialized);
229 
230   std::string hashed;
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;
237 }
238 
239 // static
ShouldAppendHeaders(const GURL & url)240 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL& url) {
241   if (google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
242                                      google_util::ALLOW_NON_STANDARD_PORTS)) {
243     return true;
244   }
245 
246   if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS())
247     return false;
248 
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))
254       return true;
255   }
256 
257   return google_util::IsYoutubeDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
258                                          google_util::ALLOW_NON_STANDARD_PORTS);
259 }
260 
261 }  // namespace variations
262