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 41 VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() { 42 return Singleton<VariationsHttpHeaderProvider>::get(); 43 } 44 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 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 114 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider() 115 : variation_ids_cache_initialized_(false) { 116 } 117 118 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() { 119 } 120 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 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 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 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