1 // Copyright 2015 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 "components/variations/net/variations_http_headers.h"
6 
7 #include <stddef.h>
8 
9 #include <utility>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/feature_list.h"
14 #include "base/macros.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/stl_util.h"
17 #include "base/strings/string_util.h"
18 #include "components/google/core/common/google_util.h"
19 #include "components/variations/net/omnibox_http_headers.h"
20 #include "components/variations/variations_http_header_provider.h"
21 #include "net/traffic_annotation/network_traffic_annotation.h"
22 #include "net/url_request/redirect_info.h"
23 #include "services/network/public/cpp/resource_request.h"
24 #include "services/network/public/cpp/simple_url_loader.h"
25 #include "services/network/public/mojom/url_response_head.mojom.h"
26 #include "url/gurl.h"
27 
28 namespace variations {
29 
30 namespace {
31 
32 // The name string for the header for variations information.
33 // Note that prior to M33 this header was named X-Chrome-Variations.
34 const char kClientDataHeader[] = "X-Client-Data";
35 
36 // The result of checking if a URL should have variations headers appended.
37 // This enum is used to record UMA histogram values, and should not be
38 // reordered.
39 enum URLValidationResult {
40   INVALID_URL,
41   NOT_HTTPS,
42   NOT_GOOGLE_DOMAIN,
43   SHOULD_APPEND,
44   NEITHER_HTTP_HTTPS,
45   IS_GOOGLE_NOT_HTTPS,
46   URL_VALIDATION_RESULT_SIZE,
47 };
48 
LogUrlValidationHistogram(URLValidationResult result)49 void LogUrlValidationHistogram(URLValidationResult result) {
50   UMA_HISTOGRAM_ENUMERATION("Variations.Headers.URLValidationResult", result,
51                             URL_VALIDATION_RESULT_SIZE);
52 }
53 
ShouldAppendVariationsHeader(const GURL & url)54 bool ShouldAppendVariationsHeader(const GURL& url) {
55   if (!url.is_valid()) {
56     LogUrlValidationHistogram(INVALID_URL);
57     return false;
58   }
59   if (!url.SchemeIsHTTPOrHTTPS()) {
60     LogUrlValidationHistogram(NEITHER_HTTP_HTTPS);
61     return false;
62   }
63   if (!google_util::IsGoogleAssociatedDomainUrl(url)) {
64     LogUrlValidationHistogram(NOT_GOOGLE_DOMAIN);
65     return false;
66   }
67   // We check https here, rather than before the IsGoogleDomain() check, to know
68   // how many Google domains are being rejected by the change to https only.
69   if (!url.SchemeIs(url::kHttpsScheme)) {
70     LogUrlValidationHistogram(IS_GOOGLE_NOT_HTTPS);
71     return false;
72   }
73   LogUrlValidationHistogram(SHOULD_APPEND);
74   return true;
75 }
76 
77 class VariationsHeaderHelper {
78  public:
79   // Note: It's OK to pass SignedIn::kNo if it's unknown, as it does not affect
80   // transmission of experiments coming from the variations server.
VariationsHeaderHelper(network::ResourceRequest * request,SignedIn signed_in=SignedIn::kNo)81   VariationsHeaderHelper(network::ResourceRequest* request,
82                          SignedIn signed_in = SignedIn::kNo)
83       : VariationsHeaderHelper(request, CreateVariationsHeader(signed_in)) {}
VariationsHeaderHelper(network::ResourceRequest * resource_request,std::string variations_header)84   VariationsHeaderHelper(network::ResourceRequest* resource_request,
85                          std::string variations_header)
86       : resource_request_(resource_request) {
87     DCHECK(resource_request_);
88     variations_header_ = std::move(variations_header);
89   }
90 
AppendHeaderIfNeeded(const GURL & url,InIncognito incognito)91   bool AppendHeaderIfNeeded(const GURL& url, InIncognito incognito) {
92     AppendOmniboxOnDeviceSuggestionsHeaderIfNeeded(url, resource_request_);
93 
94     // Note the criteria for attaching client experiment headers:
95     // 1. We only transmit to Google owned domains which can evaluate
96     // experiments.
97     //    1a. These include hosts which have a standard postfix such as:
98     //         *.doubleclick.net or *.googlesyndication.com or
99     //         exactly www.googleadservices.com or
100     //         international TLD domains *.google.<TLD> or *.youtube.<TLD>.
101     // 2. Only transmit for non-Incognito profiles.
102     // 3. For the X-Client-Data header, only include non-empty variation IDs.
103     if ((incognito == InIncognito::kYes) || !ShouldAppendVariationsHeader(url))
104       return false;
105 
106     if (variations_header_.empty())
107       return false;
108 
109     // Set the variations header to cors_exempt_headers rather than headers
110     // to be exempted from CORS checks.
111     resource_request_->cors_exempt_headers.SetHeaderIfMissing(
112         kClientDataHeader, variations_header_);
113     return true;
114   }
115 
116  private:
CreateVariationsHeader(SignedIn signed_in)117   static std::string CreateVariationsHeader(SignedIn signed_in) {
118     return VariationsHttpHeaderProvider::GetInstance()->GetClientDataHeader(
119         signed_in == SignedIn::kYes);
120   }
121 
122   network::ResourceRequest* resource_request_;
123   std::string variations_header_;
124 
125   DISALLOW_COPY_AND_ASSIGN(VariationsHeaderHelper);
126 };
127 
128 }  // namespace
129 
AppendVariationsHeader(const GURL & url,InIncognito incognito,SignedIn signed_in,network::ResourceRequest * request)130 bool AppendVariationsHeader(const GURL& url,
131                             InIncognito incognito,
132                             SignedIn signed_in,
133                             network::ResourceRequest* request) {
134   return VariationsHeaderHelper(request, signed_in)
135       .AppendHeaderIfNeeded(url, incognito);
136 }
137 
AppendVariationsHeaderWithCustomValue(const GURL & url,InIncognito incognito,const std::string & variations_header,network::ResourceRequest * request)138 bool AppendVariationsHeaderWithCustomValue(const GURL& url,
139                                            InIncognito incognito,
140                                            const std::string& variations_header,
141                                            network::ResourceRequest* request) {
142   return VariationsHeaderHelper(request, variations_header)
143       .AppendHeaderIfNeeded(url, incognito);
144 }
145 
AppendVariationsHeaderUnknownSignedIn(const GURL & url,InIncognito incognito,network::ResourceRequest * request)146 bool AppendVariationsHeaderUnknownSignedIn(const GURL& url,
147                                            InIncognito incognito,
148                                            network::ResourceRequest* request) {
149   return VariationsHeaderHelper(request).AppendHeaderIfNeeded(url, incognito);
150 }
151 
RemoveVariationsHeaderIfNeeded(const net::RedirectInfo & redirect_info,const network::mojom::URLResponseHead & response_head,std::vector<std::string> * to_be_removed_headers)152 void RemoveVariationsHeaderIfNeeded(
153     const net::RedirectInfo& redirect_info,
154     const network::mojom::URLResponseHead& response_head,
155     std::vector<std::string>* to_be_removed_headers) {
156   if (!ShouldAppendVariationsHeader(redirect_info.new_url))
157     to_be_removed_headers->push_back(kClientDataHeader);
158 }
159 
160 std::unique_ptr<network::SimpleURLLoader>
CreateSimpleURLLoaderWithVariationsHeader(std::unique_ptr<network::ResourceRequest> request,InIncognito incognito,SignedIn signed_in,const net::NetworkTrafficAnnotationTag & annotation_tag)161 CreateSimpleURLLoaderWithVariationsHeader(
162     std::unique_ptr<network::ResourceRequest> request,
163     InIncognito incognito,
164     SignedIn signed_in,
165     const net::NetworkTrafficAnnotationTag& annotation_tag) {
166   bool variation_headers_added =
167       AppendVariationsHeader(request->url, incognito, signed_in, request.get());
168   std::unique_ptr<network::SimpleURLLoader> simple_url_loader =
169       network::SimpleURLLoader::Create(std::move(request), annotation_tag);
170   if (variation_headers_added) {
171     simple_url_loader->SetOnRedirectCallback(
172         base::BindRepeating(&RemoveVariationsHeaderIfNeeded));
173   }
174   return simple_url_loader;
175 }
176 
177 std::unique_ptr<network::SimpleURLLoader>
CreateSimpleURLLoaderWithVariationsHeaderUnknownSignedIn(std::unique_ptr<network::ResourceRequest> request,InIncognito incognito,const net::NetworkTrafficAnnotationTag & annotation_tag)178 CreateSimpleURLLoaderWithVariationsHeaderUnknownSignedIn(
179     std::unique_ptr<network::ResourceRequest> request,
180     InIncognito incognito,
181     const net::NetworkTrafficAnnotationTag& annotation_tag) {
182   return CreateSimpleURLLoaderWithVariationsHeader(
183       std::move(request), incognito, SignedIn::kNo, annotation_tag);
184 }
185 
IsVariationsHeader(const std::string & header_name)186 bool IsVariationsHeader(const std::string& header_name) {
187   return header_name == kClientDataHeader ||
188          header_name == kOmniboxOnDeviceSuggestionsHeader;
189 }
190 
HasVariationsHeader(const network::ResourceRequest & request)191 bool HasVariationsHeader(const network::ResourceRequest& request) {
192   // Note: kOmniboxOnDeviceSuggestionsHeader is not listed because this function
193   // is only used for testing.
194   return request.cors_exempt_headers.HasHeader(kClientDataHeader);
195 }
196 
ShouldAppendVariationsHeaderForTesting(const GURL & url)197 bool ShouldAppendVariationsHeaderForTesting(const GURL& url) {
198   return ShouldAppendVariationsHeader(url);
199 }
200 
UpdateCorsExemptHeaderForVariations(network::mojom::NetworkContextParams * params)201 void UpdateCorsExemptHeaderForVariations(
202     network::mojom::NetworkContextParams* params) {
203   params->cors_exempt_header_list.push_back(kClientDataHeader);
204 
205   if (base::FeatureList::IsEnabled(kReportOmniboxOnDeviceSuggestionsHeader)) {
206     params->cors_exempt_header_list.push_back(
207         kOmniboxOnDeviceSuggestionsHeader);
208   }
209 }
210 
211 }  // namespace variations
212