Home | History | Annotate | Download | only in variations
      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