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 <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