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/common/metrics/proto/chrome_experiments.pb.h"
     16 #include "components/google/core/browser/google_util.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 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 Chrome 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   if (total_id_count > 20)
    200     return;
    201 
    202   // Merge the two sets of experiment ids.
    203   std::set<VariationID> all_variation_ids_set = default_variation_ids_set_;
    204   for (std::set<VariationID>::const_iterator it = variation_ids_set_.begin();
    205        it != variation_ids_set_.end(); ++it) {
    206     all_variation_ids_set.insert(*it);
    207   }
    208   metrics::ChromeVariations proto;
    209   for (std::set<VariationID>::const_iterator it = all_variation_ids_set.begin();
    210        it != all_variation_ids_set.end(); ++it) {
    211     proto.add_variation_id(*it);
    212   }
    213 
    214   std::set<VariationID> all_trigger_ids_set = default_trigger_id_set_;
    215   for (std::set<VariationID>::const_iterator it =
    216            variation_trigger_ids_set_.begin();
    217        it != variation_trigger_ids_set_.end(); ++it) {
    218     all_trigger_ids_set.insert(*it);
    219   }
    220   for (std::set<VariationID>::const_iterator it = all_trigger_ids_set.begin();
    221        it != all_trigger_ids_set.end(); ++it) {
    222     proto.add_trigger_variation_id(*it);
    223   }
    224 
    225   std::string serialized;
    226   proto.SerializeToString(&serialized);
    227 
    228   std::string hashed;
    229   base::Base64Encode(serialized, &hashed);
    230   // If successful, swap the header value with the new one.
    231   // Note that the list of IDs and the header could be temporarily out of sync
    232   // if IDs are added as the header is recreated. The receiving servers are OK
    233   // with such discrepancies.
    234   variation_ids_header_ = hashed;
    235 }
    236 
    237 // static
    238 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL& url) {
    239   if (google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
    240                                      google_util::ALLOW_NON_STANDARD_PORTS)) {
    241     return true;
    242   }
    243 
    244   if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS())
    245     return false;
    246 
    247   // Some domains don't have international TLD extensions, so testing for them
    248   // is very straight forward.
    249   const std::string host = url.host();
    250   for (size_t i = 0; i < arraysize(kSuffixesToSetHeadersFor); ++i) {
    251     if (EndsWith(host, kSuffixesToSetHeadersFor[i], false))
    252       return true;
    253   }
    254 
    255   return google_util::IsYoutubeDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
    256                                          google_util::ALLOW_NON_STANDARD_PORTS);
    257 }
    258 
    259 }  // namespace chrome_variations
    260