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. 4 5 #include "chrome/browser/metrics/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 "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" 19 #include "url/gurl.h" 20 21 namespace chrome_variations { 22 23 VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() { 24 return Singleton<VariationsHttpHeaderProvider>::get(); 25 } 26 27 void VariationsHttpHeaderProvider::AppendHeaders( 28 const GURL& url, 29 bool incognito, 30 bool uma_enabled, 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)) 39 return; 40 41 if (uma_enabled) 42 headers->SetHeaderIfMissing("X-Chrome-UMA-Enabled", "1"); 43 44 // Lazily initialize the header, if not already done, before attempting to 45 // transmit it. 46 InitVariationIDsCacheIfNeeded(); 47 48 std::string variation_ids_header_copy; 49 { 50 base::AutoLock scoped_lock(lock_); 51 variation_ids_header_copy = variation_ids_header_; 52 } 53 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); 58 } 59 } 60 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) { 68 int variation_id = 0; 69 if (!base::StringToInt(*it, &variation_id)) { 70 default_variation_ids_set_.clear(); 71 return false; 72 } 73 default_variation_ids_set_.insert(variation_id); 74 } 75 return true; 76 } 77 78 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider() 79 : variation_ids_cache_initialized_(false) { 80 } 81 82 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() { 83 } 84 85 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized( 86 const std::string& trial_name, 87 const std::string& group_name) { 88 VariationID new_id = 89 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_name, group_name); 90 if (new_id == EMPTY_ID) 91 return; 92 93 base::AutoLock scoped_lock(lock_); 94 variation_ids_set_.insert(new_id); 95 UpdateVariationIDsHeaderValue(); 96 } 97 98 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() { 99 base::AutoLock scoped_lock(lock_); 100 if (variation_ids_cache_initialized_) 101 return; 102 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); 107 108 base::TimeTicks before_time = base::TimeTicks::Now(); 109 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, 117 it->group_name); 118 if (id != EMPTY_ID) 119 variation_ids_set_.insert(id); 120 } 121 UpdateVariationIDsHeaderValue(); 122 123 UMA_HISTOGRAM_CUSTOM_COUNTS( 124 "Variations.HeaderConstructionTime", 125 (base::TimeTicks::Now() - before_time).InMicroseconds(), 126 0, 127 base::TimeDelta::FromSeconds(1).InMicroseconds(), 128 50); 129 130 variation_ids_cache_initialized_ = true; 131 } 132 133 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() { 134 lock_.AssertAcquired(); 135 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(); 139 140 if (variation_ids_set_.empty() && default_variation_ids_set_.empty()) 141 return; 142 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) 148 return; 149 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); 155 } 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); 160 } 161 162 std::string serialized; 163 proto.SerializeToString(&serialized); 164 165 std::string hashed; 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; 172 } 173 174 // static 175 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL& url) { 176 if (google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN, 177 google_util::ALLOW_NON_STANDARD_PORTS)) { 178 return true; 179 } 180 181 // The below mirrors logic in IsGoogleDomainUrl(), but for youtube.<TLD>. 182 if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) 183 return false; 184 185 const std::string host = url.host(); 186 const size_t tld_length = net::registry_controlled_domains::GetRegistryLength( 187 host, 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)) 191 return false; 192 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); 196 } 197 198 } // namespace chrome_variations 199