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 "base/base64.h" 8 #include "base/memory/singleton.h" 9 #include "base/metrics/histogram.h" 10 #include "base/strings/string_util.h" 11 #include "chrome/browser/google/google_util.h" 12 #include "chrome/common/metrics/proto/chrome_experiments.pb.h" 13 #include "chrome/common/metrics/variations/variations_associated_data.h" 14 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" 15 #include "net/http/http_request_headers.h" 16 #include "url/gurl.h" 17 18 namespace chrome_variations { 19 20 VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() { 21 return Singleton<VariationsHttpHeaderProvider>::get(); 22 } 23 24 void VariationsHttpHeaderProvider::AppendHeaders( 25 const GURL& url, 26 bool incognito, 27 bool uma_enabled, 28 net::HttpRequestHeaders* headers) { 29 // Note the criteria for attaching Chrome experiment headers: 30 // 1. We only transmit to *.google.<TLD> or *.youtube.<TLD> domains. 31 // 2. Only transmit for non-Incognito profiles. 32 // 3. For the X-Chrome-UMA-Enabled bit, only set it if UMA is in fact enabled 33 // for this install of Chrome. 34 // 4. For the X-Chrome-Variations, only include non-empty variation IDs. 35 if (incognito || !ShouldAppendHeaders(url)) 36 return; 37 38 if (uma_enabled) 39 headers->SetHeaderIfMissing("X-Chrome-UMA-Enabled", "1"); 40 41 // Lazily initialize the header, if not already done, before attempting to 42 // transmit it. 43 InitVariationIDsCacheIfNeeded(); 44 45 std::string variation_ids_header_copy; 46 { 47 base::AutoLock scoped_lock(lock_); 48 variation_ids_header_copy = variation_ids_header_; 49 } 50 51 if (!variation_ids_header_copy.empty()) { 52 headers->SetHeaderIfMissing("X-Chrome-Variations", 53 variation_ids_header_copy); 54 } 55 } 56 57 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider() 58 : variation_ids_cache_initialized_(false) { 59 } 60 61 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() { 62 } 63 64 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized( 65 const std::string& trial_name, 66 const std::string& group_name) { 67 VariationID new_id = 68 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_name, group_name); 69 if (new_id == EMPTY_ID) 70 return; 71 72 base::AutoLock scoped_lock(lock_); 73 variation_ids_set_.insert(new_id); 74 UpdateVariationIDsHeaderValue(); 75 } 76 77 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() { 78 base::AutoLock scoped_lock(lock_); 79 if (variation_ids_cache_initialized_) 80 return; 81 82 // Register for additional cache updates. This is done first to avoid a race 83 // that could cause registered FieldTrials to be missed. 84 DCHECK(base::MessageLoop::current()); 85 base::FieldTrialList::AddObserver(this); 86 87 base::TimeTicks before_time = base::TimeTicks::Now(); 88 89 base::FieldTrial::ActiveGroups initial_groups; 90 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups); 91 for (base::FieldTrial::ActiveGroups::const_iterator it = 92 initial_groups.begin(); 93 it != initial_groups.end(); ++it) { 94 const VariationID id = 95 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, it->trial_name, 96 it->group_name); 97 if (id != EMPTY_ID) 98 variation_ids_set_.insert(id); 99 } 100 UpdateVariationIDsHeaderValue(); 101 102 UMA_HISTOGRAM_CUSTOM_COUNTS( 103 "Variations.HeaderConstructionTime", 104 (base::TimeTicks::Now() - before_time).InMicroseconds(), 105 0, 106 base::TimeDelta::FromSeconds(1).InMicroseconds(), 107 50); 108 109 variation_ids_cache_initialized_ = true; 110 } 111 112 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() { 113 lock_.AssertAcquired(); 114 115 // The header value is a serialized protobuffer of Variation IDs which is 116 // base64 encoded before transmitting as a string. 117 if (variation_ids_set_.empty()) 118 return; 119 120 // This is the bottleneck for the creation of the header, so validate the size 121 // here. Force a hard maximum on the ID count in case the Variations server 122 // returns too many IDs and DOSs receiving servers with large requests. 123 DCHECK_LE(variation_ids_set_.size(), 10U); 124 if (variation_ids_set_.size() > 20) { 125 variation_ids_header_.clear(); 126 return; 127 } 128 129 metrics::ChromeVariations proto; 130 for (std::set<VariationID>::const_iterator it = variation_ids_set_.begin(); 131 it != variation_ids_set_.end(); ++it) { 132 proto.add_variation_id(*it); 133 } 134 135 std::string serialized; 136 proto.SerializeToString(&serialized); 137 138 std::string hashed; 139 if (base::Base64Encode(serialized, &hashed)) { 140 // If successful, swap the header value with the new one. 141 // Note that the list of IDs and the header could be temporarily out of sync 142 // if IDs are added as the header is recreated. The receiving servers are OK 143 // with such discrepancies. 144 variation_ids_header_ = hashed; 145 } else { 146 NOTREACHED() << "Failed to base64 encode Variation IDs value: " 147 << serialized; 148 } 149 } 150 151 // static 152 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL& url) { 153 if (google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN, 154 google_util::ALLOW_NON_STANDARD_PORTS)) { 155 return true; 156 } 157 158 // The below mirrors logic in IsGoogleDomainUrl(), but for youtube.<TLD>. 159 if (!url.is_valid() || !(url.SchemeIs("http") || url.SchemeIs("https"))) 160 return false; 161 162 const std::string host = url.host(); 163 const size_t tld_length = net::registry_controlled_domains::GetRegistryLength( 164 host, 165 net::registry_controlled_domains::EXCLUDE_UNKNOWN_REGISTRIES, 166 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); 167 if ((tld_length == 0) || (tld_length == std::string::npos)) 168 return false; 169 170 const std::string host_minus_tld(host, 0, host.length() - tld_length); 171 return LowerCaseEqualsASCII(host_minus_tld, "youtube.") || 172 EndsWith(host_minus_tld, ".youtube.", false); 173 } 174 175 } // namespace chrome_variations 176