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