| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <utility> |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_configurator.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_data.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_io_data.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options.h" |
| #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_util.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h" |
| #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h" |
| #include "components/data_reduction_proxy/core/common/lofi_decider.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/mime_util.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/base/proxy_server.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/nqe/effective_connection_type.h" |
| #include "net/nqe/network_quality_estimator.h" |
| #include "net/proxy_resolution/proxy_info.h" |
| #include "net/proxy_resolution/proxy_resolution_service.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_status.h" |
| #include "url/gurl.h" |
| |
| namespace data_reduction_proxy { |
| |
| namespace { |
| |
| // Values of the UMA DataReductionProxy.Protocol.AcceptTransform histogram |
| // defined in metrics/histograms/histograms.xml. This enum must remain |
| // synchronized with DataReductionProxyProtocolAcceptTransformEvent in |
| // tools/metrics/histograms/enums.xml. |
| enum AcceptTransformEvent { |
| LITE_PAGE_REQUESTED = 0, |
| LITE_PAGE_TRANSFORM_RECEIVED = 1, |
| EMPTY_IMAGE_POLICY_DIRECTIVE_RECEIVED = 2, |
| EMPTY_IMAGE_REQUESTED = 3, |
| EMPTY_IMAGE_TRANSFORM_RECEIVED = 4, |
| COMPRESSED_VIDEO_REQUESTED = 5, |
| IDENTITY_TRANSFORM_REQUESTED = 6, |
| IDENTITY_TRANSFORM_RECEIVED = 7, |
| COMPRESSED_VIDEO_RECEIVED = 8, |
| UNKNOWN_TRANSFORM_RECEIVED = 9, |
| ACCEPT_TRANSFORM_EVENT_BOUNDARY |
| }; |
| |
| // Records the occurrence of |sample| in |name| histogram. UMA macros are not |
| // used because the |name| is not static. |
| void RecordNewContentLengthHistogram(const std::string& name, int64_t sample) { |
| base::UmaHistogramCustomCounts( |
| name, sample, |
| 1, // Minimum sample size in bytes. |
| 128 << 20, // Maximum sample size in bytes. 128MB is chosen because some |
| // video requests can be very large. |
| 50 // Bucket count. |
| ); |
| } |
| |
| void RecordNewContentLengthHistograms( |
| const char* prefix, |
| bool is_https, |
| bool is_video, |
| DataReductionProxyRequestType request_type, |
| int64_t content_length) { |
| const char* connection_type = is_https ? ".Https" : ".Http"; |
| const char* suffix = ".Other"; |
| // TODO(crbug.com/726411): Differentiate between a bypass and a disabled |
| // proxy config. |
| switch (request_type) { |
| case VIA_DATA_REDUCTION_PROXY: |
| suffix = ".ViaDRP"; |
| break; |
| case HTTPS: |
| case DIRECT_HTTP: |
| suffix = ".Direct"; |
| break; |
| case SHORT_BYPASS: |
| case LONG_BYPASS: |
| suffix = ".BypassedDRP"; |
| break; |
| case UPDATE: |
| case UNKNOWN_TYPE: |
| default: |
| // Value already properly initialized to ".Other" |
| break; |
| } |
| // Record a histogram for all traffic, including video. |
| RecordNewContentLengthHistogram( |
| base::StringPrintf("%s%s%s", prefix, connection_type, suffix), |
| content_length); |
| if (is_video) { |
| RecordNewContentLengthHistogram( |
| base::StringPrintf("%s%s%s.Video", prefix, connection_type, suffix), |
| content_length); |
| } |
| } |
| |
| // |received_content_length| is the number of prefilter bytes received. |
| // |original_content_length| is the length of resource if accessed directly |
| // without data saver proxy. |freshness_lifetime| specifies how long the |
| // resource will be fresh for. |
| void RecordContentLengthHistograms(bool is_https, |
| bool is_video, |
| int64_t received_content_length, |
| int64_t original_content_length, |
| const base::TimeDelta& freshness_lifetime, |
| DataReductionProxyRequestType request_type) { |
| // Add the current resource to these histograms only when the content length |
| // is valid. |
| if (original_content_length >= 0) { |
| // This is only used locally in integration testing. |
| LOCAL_HISTOGRAM_COUNTS_1000000("Net.HttpOriginalContentLengthWithValidOCL", |
| original_content_length); |
| UMA_HISTOGRAM_COUNTS_1M("Net.HttpContentLengthDifferenceWithValidOCL", |
| original_content_length - received_content_length); |
| } else { |
| // Presume the original content length is the same as the received content |
| // length. |
| original_content_length = received_content_length; |
| } |
| UMA_HISTOGRAM_COUNTS_1M("Net.HttpContentLength", received_content_length); |
| |
| // Record the new histograms broken down by HTTP/HTTPS and video/non-video |
| RecordNewContentLengthHistograms("Net.HttpContentLengthV2", is_https, |
| is_video, request_type, |
| received_content_length); |
| RecordNewContentLengthHistograms("Net.HttpOriginalContentLengthV2", is_https, |
| is_video, request_type, |
| original_content_length); |
| |
| UMA_HISTOGRAM_COUNTS_1M("Net.HttpOriginalContentLength", |
| original_content_length); |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Net.HttpContentFreshnessLifetime", |
| freshness_lifetime.InSeconds(), |
| base::TimeDelta::FromHours(1).InSeconds(), |
| base::TimeDelta::FromDays(30).InSeconds(), 100); |
| } |
| |
| void RecordAcceptTransformEvent(AcceptTransformEvent event) { |
| UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.Protocol.AcceptTransform", |
| event, ACCEPT_TRANSFORM_EVENT_BOUNDARY); |
| } |
| |
| void RecordAcceptTransformSentUMA( |
| const net::HttpRequestHeaders& request_headers) { |
| switch (ParseRequestTransform(request_headers)) { |
| case TRANSFORM_LITE_PAGE: |
| RecordAcceptTransformEvent(LITE_PAGE_REQUESTED); |
| break; |
| case TRANSFORM_EMPTY_IMAGE: |
| RecordAcceptTransformEvent(EMPTY_IMAGE_REQUESTED); |
| break; |
| case TRANSFORM_COMPRESSED_VIDEO: |
| RecordAcceptTransformEvent(COMPRESSED_VIDEO_REQUESTED); |
| break; |
| case TRANSFORM_IDENTITY: |
| RecordAcceptTransformEvent(IDENTITY_TRANSFORM_REQUESTED); |
| break; |
| case TRANSFORM_NONE: |
| break; |
| case TRANSFORM_PAGE_POLICIES_EMPTY_IMAGE: |
| case TRANSFORM_UNKNOWN: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void RecordAcceptTransformReceivedUMA(const net::URLRequest& request) { |
| net::HttpResponseHeaders* response_headers = request.response_headers(); |
| if (!response_headers) { |
| return; |
| } |
| |
| switch (ParseResponseTransform(*response_headers)) { |
| case TRANSFORM_UNKNOWN: |
| RecordAcceptTransformEvent(UNKNOWN_TRANSFORM_RECEIVED); |
| break; |
| case TRANSFORM_LITE_PAGE: |
| RecordAcceptTransformEvent(LITE_PAGE_TRANSFORM_RECEIVED); |
| break; |
| case TRANSFORM_PAGE_POLICIES_EMPTY_IMAGE: |
| RecordAcceptTransformEvent(EMPTY_IMAGE_POLICY_DIRECTIVE_RECEIVED); |
| break; |
| case TRANSFORM_EMPTY_IMAGE: |
| RecordAcceptTransformEvent(EMPTY_IMAGE_TRANSFORM_RECEIVED); |
| break; |
| case TRANSFORM_IDENTITY: |
| RecordAcceptTransformEvent(IDENTITY_TRANSFORM_RECEIVED); |
| break; |
| case TRANSFORM_COMPRESSED_VIDEO: |
| RecordAcceptTransformEvent(COMPRESSED_VIDEO_RECEIVED); |
| break; |
| case TRANSFORM_NONE: |
| break; |
| } |
| } |
| |
| // Verifies that the chrome proxy related request headers are set correctly. |
| // |via_chrome_proxy| is true if the request is being fetched via Chrome Data |
| // Saver proxy. |
| void VerifyHttpRequestHeaders(bool via_chrome_proxy, |
| const net::HttpRequestHeaders& headers) { |
| // If holdback is enabled, then |via_chrome_proxy| should be false. |
| DCHECK(!params::IsIncludedInHoldbackFieldTrial() || !via_chrome_proxy); |
| |
| if (via_chrome_proxy) { |
| DCHECK(headers.HasHeader(chrome_proxy_ect_header())); |
| std::string chrome_proxy_header_value; |
| DCHECK( |
| headers.GetHeader(chrome_proxy_header(), &chrome_proxy_header_value)); |
| // Check that only 1 "exp" directive is sent. |
| DCHECK_GT(3u, base::SplitStringUsingSubstr(chrome_proxy_header_value, |
| "exp=", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_ALL) |
| .size()); |
| // Silence unused variable warning in release builds. |
| (void)chrome_proxy_header_value; |
| } else { |
| DCHECK(!headers.HasHeader(chrome_proxy_header())); |
| DCHECK(!headers.HasHeader(chrome_proxy_accept_transform_header())); |
| DCHECK(!headers.HasHeader(chrome_proxy_ect_header())); |
| } |
| } |
| |
| // If the response is the entire resource, then the renderer won't show a |
| // placeholder. This should match the behavior in blink::ImageResource. |
| bool IsEntireResource(const net::HttpResponseHeaders* response_headers) { |
| if (!response_headers || response_headers->response_code() != 206) |
| return true; |
| |
| int64_t first, last, length; |
| return response_headers->GetContentRangeFor206(&first, &last, &length) && |
| first == 0 && last + 1 == length; |
| } |
| |
| } // namespace |
| |
| DataReductionProxyNetworkDelegate::DataReductionProxyNetworkDelegate( |
| std::unique_ptr<net::NetworkDelegate> network_delegate, |
| DataReductionProxyConfig* config, |
| DataReductionProxyRequestOptions* request_options, |
| const DataReductionProxyConfigurator* configurator) |
| : LayeredNetworkDelegate(std::move(network_delegate)), |
| data_reduction_proxy_config_(config), |
| data_reduction_proxy_bypass_stats_(nullptr), |
| data_reduction_proxy_request_options_(request_options), |
| data_reduction_proxy_io_data_(nullptr), |
| configurator_(configurator) { |
| DCHECK(data_reduction_proxy_config_); |
| DCHECK(data_reduction_proxy_request_options_); |
| DCHECK(configurator_); |
| } |
| |
| DataReductionProxyNetworkDelegate::~DataReductionProxyNetworkDelegate() { |
| } |
| |
| void DataReductionProxyNetworkDelegate::InitIODataAndUMA( |
| DataReductionProxyIOData* io_data, |
| DataReductionProxyBypassStats* bypass_stats) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(bypass_stats); |
| data_reduction_proxy_io_data_ = io_data; |
| data_reduction_proxy_bypass_stats_ = bypass_stats; |
| } |
| |
| void DataReductionProxyNetworkDelegate::OnBeforeStartTransactionInternal( |
| net::URLRequest* request, |
| net::HttpRequestHeaders* headers) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!data_reduction_proxy_io_data_) |
| return; |
| if (!data_reduction_proxy_io_data_->IsEnabled()) |
| return; |
| |
| if (request->url().SchemeIsCryptographic() || |
| !request->url().SchemeIsHTTPOrHTTPS()) { |
| return; |
| } |
| |
| if (data_reduction_proxy_io_data_->resource_type_provider()) { |
| // Sets content type of |request| in the resource type provider, so it can |
| // be later used for determining the proxy that should be used for fetching |
| // |request|. |
| data_reduction_proxy_io_data_->resource_type_provider()->SetContentType( |
| *request); |
| } |
| |
| if (data_reduction_proxy_io_data_->lofi_decider()) { |
| data_reduction_proxy_io_data_->lofi_decider() |
| ->MaybeSetAcceptTransformHeader(*request, headers); |
| } |
| |
| MaybeAddChromeProxyECTHeader(headers, *request); |
| } |
| |
| void DataReductionProxyNetworkDelegate::OnBeforeSendHeadersInternal( |
| net::URLRequest* request, |
| const net::ProxyInfo& proxy_info, |
| const net::ProxyRetryInfoMap& proxy_retry_info, |
| net::HttpRequestHeaders* headers) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(data_reduction_proxy_config_); |
| DCHECK(request); |
| |
| // If there was a redirect or request bypass, use the same page ID for both |
| // requests. As long as the session ID has not changed. Re-issued requests |
| // and client redirects will be assigned a new page ID as they are different |
| // URLRequests. |
| DataReductionProxyData* data = DataReductionProxyData::GetData(*request); |
| base::Optional<uint64_t> page_id; |
| if (data && data->session_key() == |
| data_reduction_proxy_request_options_->GetSecureSession()) { |
| page_id = data->page_id(); |
| } |
| // Always persist data's |request_info| since it tracks connection pingback |
| // data for redirects on main frame requests. It should include re-issued |
| // requests and client redirects. |
| std::vector<DataReductionProxyData::RequestInfo> request_info; |
| if (data) |
| request_info = data->TakeRequestInfo(); |
| |
| // Reset |request|'s DataReductionProxyData. |
| DataReductionProxyData::ClearData(request); |
| data = nullptr; |
| |
| bool using_data_reduction_proxy = true; |
| // The following checks rule out direct, invalid, and other connection types. |
| if (!proxy_info.is_http() && !proxy_info.is_https() && |
| !proxy_info.is_quic()) { |
| using_data_reduction_proxy = false; |
| } else if (proxy_info.proxy_server().host_port_pair().IsEmpty()) { |
| using_data_reduction_proxy = false; |
| } else if (!data_reduction_proxy_config_->FindConfiguredDataReductionProxy( |
| proxy_info.proxy_server())) { |
| using_data_reduction_proxy = false; |
| } |
| |
| bool is_holdback_eligible = false; |
| |
| if (params::IsIncludedInHoldbackFieldTrial() && |
| WasEligibleWithoutHoldback(*request, proxy_info, proxy_retry_info)) { |
| is_holdback_eligible = true; |
| } |
| // If holdback is enabled, |using_data_reduction_proxy| must be false. |
| DCHECK(!params::IsIncludedInHoldbackFieldTrial() || |
| !using_data_reduction_proxy); |
| |
| // For the holdback field trial, still log UMA and send the pingback as if |
| // the proxy were used. |
| if (is_holdback_eligible || using_data_reduction_proxy) { |
| // Retrieves DataReductionProxyData from a request, creating a new instance |
| // if needed. |
| data = DataReductionProxyData::GetDataAndCreateIfNecessary(request); |
| data->set_used_data_reduction_proxy(true); |
| // Only set GURL, NQE and session key string for main frame requests since |
| // they are not needed for sub-resources. |
| if (request->load_flags() & net::LOAD_MAIN_FRAME_DEPRECATED) { |
| data->set_session_key( |
| data_reduction_proxy_request_options_->GetSecureSession()); |
| data->set_request_url(request->url()); |
| if (request->context()->network_quality_estimator()) { |
| data->set_effective_connection_type(request->context() |
| ->network_quality_estimator() |
| ->GetEffectiveConnectionType()); |
| } |
| data->set_connection_type( |
| net::NetworkChangeNotifier::GetConnectionType()); |
| // Generate a page ID for main frame requests that don't already have one. |
| // TODO(ryansturm): remove LOAD_MAIN_FRAME_DEPRECATED from d_r_p. |
| // crbug.com/709621 |
| if (!page_id) { |
| page_id = data_reduction_proxy_request_options_->GeneratePageId(); |
| } |
| data->set_page_id(page_id.value()); |
| data->set_request_info(std::move(request_info)); |
| } |
| } |
| |
| LoFiDecider* lofi_decider = nullptr; |
| if (data_reduction_proxy_io_data_) |
| lofi_decider = data_reduction_proxy_io_data_->lofi_decider(); |
| |
| if (!using_data_reduction_proxy) { |
| if (lofi_decider) { |
| // If not using the data reduction proxy, strip the |
| // Chrome-Proxy-Accept-Transform header. |
| lofi_decider->RemoveAcceptTransformHeader(headers); |
| } |
| RemoveChromeProxyECTHeader(headers); |
| headers->RemoveHeader(chrome_proxy_header()); |
| VerifyHttpRequestHeaders(false, *headers); |
| return; |
| } |
| |
| DCHECK(data); |
| data->set_lofi_requested( |
| lofi_decider ? lofi_decider->ShouldRecordLoFiUMA(*request) : false); |
| MaybeAddBrotliToAcceptEncodingHeader(proxy_info, headers, *request); |
| |
| data_reduction_proxy_request_options_->AddRequestHeader(headers, page_id); |
| |
| VerifyHttpRequestHeaders(true, *headers); |
| RecordAcceptTransformSentUMA(*headers); |
| } |
| |
| void DataReductionProxyNetworkDelegate::OnBeforeRedirectInternal( |
| net::URLRequest* request, |
| const GURL& new_location) { |
| // Since this is after a redirect response, reset |request|'s |
| // DataReductionProxyData, but keep page ID and session. |
| // TODO(ryansturm): Change ClearData logic to have persistent and |
| // non-persistent (WRT redirects) data. |
| // crbug.com/709564 |
| DataReductionProxyData* data = DataReductionProxyData::GetData(*request); |
| base::Optional<uint64_t> page_id; |
| if (data && data->session_key() == |
| data_reduction_proxy_request_options_->GetSecureSession()) { |
| page_id = data->page_id(); |
| } |
| |
| // Persist data's |request_info| since it tracks connection pingback data for |
| // redirects on main frame requests. |
| std::vector<DataReductionProxyData::RequestInfo> request_info; |
| if (data) |
| request_info = data->TakeRequestInfo(); |
| |
| DataReductionProxyData::ClearData(request); |
| |
| if (page_id) { |
| data = DataReductionProxyData::GetDataAndCreateIfNecessary(request); |
| data->set_page_id(page_id.value()); |
| data->set_session_key( |
| data_reduction_proxy_request_options_->GetSecureSession()); |
| data->set_request_info(std::move(request_info)); |
| } |
| } |
| |
| void DataReductionProxyNetworkDelegate::OnCompletedInternal( |
| net::URLRequest* request, |
| bool started, |
| int net_error) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(request); |
| DCHECK_NE(net::ERR_IO_PENDING, net_error); |
| if (data_reduction_proxy_bypass_stats_) |
| data_reduction_proxy_bypass_stats_->OnUrlRequestCompleted(request, started, |
| net_error); |
| |
| net::HttpRequestHeaders request_headers; |
| bool server_lofi = request->response_headers() && |
| IsEmptyImagePreview(*(request->response_headers())); |
| bool will_show_client_lofi_placeholder = |
| data_reduction_proxy_io_data_ && |
| data_reduction_proxy_io_data_->lofi_decider() && |
| data_reduction_proxy_io_data_->lofi_decider()->IsClientLoFiImageRequest( |
| *request) && |
| // If the response contains the entire resource, then the renderer won't |
| // show a placeholder for this image, so don't bother triggering an |
| // infobar. |
| !IsEntireResource(request->response_headers()); |
| |
| if ((server_lofi || will_show_client_lofi_placeholder) && |
| data_reduction_proxy_io_data_ && |
| data_reduction_proxy_io_data_->lofi_ui_service()) { |
| data_reduction_proxy_io_data_->lofi_ui_service()->OnLoFiReponseReceived( |
| *request); |
| } else if (data_reduction_proxy_io_data_ && request->response_headers() && |
| IsLitePagePreview(*(request->response_headers()))) { |
| RecordLitePageTransformationType(LITE_PAGE); |
| } else if (request->GetFullRequestHeaders(&request_headers)) { |
| // TODO(bengr): transform processing logic should happen elsewhere. |
| std::string header_value; |
| request_headers.GetHeader(chrome_proxy_accept_transform_header(), |
| &header_value); |
| if (header_value == lite_page_directive()) |
| RecordLitePageTransformationType(NO_TRANSFORMATION_LITE_PAGE_REQUESTED); |
| } |
| |
| if (!request->response_info().network_accessed || |
| !request->url().SchemeIsHTTPOrHTTPS() || |
| request->GetTotalReceivedBytes() == 0) { |
| return; |
| } |
| |
| DataReductionProxyRequestType request_type = GetDataReductionProxyRequestType( |
| *request, configurator_->GetProxyConfig(), *data_reduction_proxy_config_); |
| |
| CalculateAndRecordDataUsage(*request, request_type); |
| RecordContentLength(*request, request_type, |
| util::CalculateOCLFromOFCL(*request)); |
| RecordAcceptTransformReceivedUMA(*request); |
| } |
| |
| void DataReductionProxyNetworkDelegate::OnHeadersReceivedInternal( |
| net::URLRequest* request, |
| const net::HttpResponseHeaders* original_response_headers, |
| scoped_refptr<net::HttpResponseHeaders>* override_response_headers, |
| GURL* allowed_unsafe_redirect_url) { |
| if (!original_response_headers || |
| original_response_headers->IsRedirect(nullptr)) |
| return; |
| |
| switch (ParseResponseTransform(*original_response_headers)) { |
| case TRANSFORM_LITE_PAGE: |
| DataReductionProxyData::GetDataAndCreateIfNecessary(request) |
| ->set_lite_page_received(true); |
| break; |
| case TRANSFORM_PAGE_POLICIES_EMPTY_IMAGE: |
| DataReductionProxyData::GetDataAndCreateIfNecessary(request) |
| ->set_lofi_policy_received(true); |
| break; |
| case TRANSFORM_EMPTY_IMAGE: |
| DataReductionProxyData::GetDataAndCreateIfNecessary(request) |
| ->set_lofi_received(true); |
| break; |
| case TRANSFORM_IDENTITY: |
| case TRANSFORM_COMPRESSED_VIDEO: |
| case TRANSFORM_NONE: |
| case TRANSFORM_UNKNOWN: |
| break; |
| } |
| if (data_reduction_proxy_io_data_ && |
| data_reduction_proxy_io_data_->lofi_decider() && |
| data_reduction_proxy_io_data_->lofi_decider()->IsClientLoFiImageRequest( |
| *request)) { |
| DataReductionProxyData* data = |
| DataReductionProxyData::GetDataAndCreateIfNecessary(request); |
| data->set_client_lofi_requested(true); |
| } |
| } |
| |
| void DataReductionProxyNetworkDelegate::CalculateAndRecordDataUsage( |
| const net::URLRequest& request, |
| DataReductionProxyRequestType request_type) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| int64_t data_used = request.GetTotalReceivedBytes(); |
| |
| // Estimate how many bytes would have been used if the DataReductionProxy was |
| // not used, and record the data usage. |
| int64_t original_size = util::EstimateOriginalReceivedBytes( |
| request, data_reduction_proxy_io_data_ |
| ? data_reduction_proxy_io_data_->lofi_decider() |
| : nullptr); |
| |
| std::string mime_type; |
| if (request.response_headers()) |
| request.response_headers()->GetMimeType(&mime_type); |
| |
| AccumulateDataUsage( |
| data_used, original_size, request_type, mime_type, |
| data_use_measurement::DataUseMeasurement::IsUserRequest(request), |
| data_use_measurement::DataUseMeasurement::GetContentTypeForRequest( |
| request), |
| request.traffic_annotation().unique_id_hash_code); |
| |
| if (params::IsDataSaverSiteBreakdownUsingPLMEnabled() && |
| data_reduction_proxy_io_data_ && |
| data_reduction_proxy_io_data_->resource_type_provider() && |
| data_reduction_proxy_io_data_->resource_type_provider() |
| ->IsNonContentInitiatedRequest(request)) { |
| // Record non-content initiated traffic to the Other bucket for data saver |
| // site-breakdown. |
| data_reduction_proxy_io_data_->UpdateDataUseForHost( |
| data_used, original_size, util::GetSiteBreakdownOtherHostName()); |
| } |
| } |
| |
| void DataReductionProxyNetworkDelegate::AccumulateDataUsage( |
| int64_t data_used, |
| int64_t original_size, |
| DataReductionProxyRequestType request_type, |
| const std::string& mime_type, |
| bool is_user_traffic, |
| data_use_measurement::DataUseUserData::DataUseContentType content_type, |
| int32_t service_hash_code) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_GE(data_used, 0); |
| DCHECK_GE(original_size, 0); |
| if (data_reduction_proxy_io_data_) { |
| data_reduction_proxy_io_data_->UpdateContentLengths( |
| data_used, original_size, data_reduction_proxy_io_data_->IsEnabled(), |
| request_type, mime_type, is_user_traffic, content_type, |
| service_hash_code); |
| } |
| } |
| |
| void DataReductionProxyNetworkDelegate::RecordContentLength( |
| const net::URLRequest& request, |
| DataReductionProxyRequestType request_type, |
| int64_t original_content_length) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!request.response_headers() || request.was_cached() || |
| request.received_response_content_length() == 0) { |
| return; |
| } |
| |
| // Record content length histograms for the request. |
| base::TimeDelta freshness_lifetime = |
| request.response_headers() |
| ->GetFreshnessLifetimes(request.response_info().response_time) |
| .freshness; |
| |
| bool is_https = request.url().SchemeIs("https"); |
| bool is_video = false; |
| std::string mime_type; |
| if (request.response_headers()->GetMimeType(&mime_type)) { |
| is_video = net::MatchesMimeType("video/*", mime_type); |
| } |
| |
| RecordContentLengthHistograms( |
| is_https, is_video, request.received_response_content_length(), |
| original_content_length, freshness_lifetime, request_type); |
| |
| if (data_reduction_proxy_io_data_ && data_reduction_proxy_bypass_stats_) { |
| // Record BypassedBytes histograms for the request. |
| data_reduction_proxy_bypass_stats_->RecordBypassedBytesHistograms( |
| request, data_reduction_proxy_io_data_->IsEnabled(), |
| configurator_->GetProxyConfig()); |
| } |
| } |
| |
| void DataReductionProxyNetworkDelegate::RecordLitePageTransformationType( |
| LitePageTransformationType type) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.LoFi.TransformationType", type, |
| LITE_PAGE_TRANSFORMATION_TYPES_INDEX_BOUNDARY); |
| } |
| |
| bool DataReductionProxyNetworkDelegate::WasEligibleWithoutHoldback( |
| const net::URLRequest& request, |
| const net::ProxyInfo& proxy_info, |
| const net::ProxyRetryInfoMap& proxy_retry_info) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(proxy_info.is_empty() || proxy_info.is_direct() || |
| !data_reduction_proxy_config_->FindConfiguredDataReductionProxy( |
| proxy_info.proxy_server())); |
| if (!util::EligibleForDataReductionProxy(proxy_info, request.url(), |
| request.method())) { |
| return false; |
| } |
| net::ProxyConfig proxy_config = |
| data_reduction_proxy_config_->ProxyConfigIgnoringHoldback(); |
| net::ProxyInfo data_reduction_proxy_info; |
| return util::ApplyProxyConfigToProxyInfo(proxy_config, proxy_retry_info, |
| request.url(), |
| &data_reduction_proxy_info); |
| } |
| |
| void DataReductionProxyNetworkDelegate::MaybeAddBrotliToAcceptEncodingHeader( |
| const net::ProxyInfo& proxy_info, |
| net::HttpRequestHeaders* request_headers, |
| const net::URLRequest& request) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // This method should be called only when the resolved proxy was a data |
| // saver proxy. |
| DCHECK(data_reduction_proxy_config_->FindConfiguredDataReductionProxy( |
| proxy_info.proxy_server())); |
| DCHECK(request.url().is_valid()); |
| DCHECK(!request.url().SchemeIsCryptographic()); |
| DCHECK(request.url().SchemeIsHTTPOrHTTPS()); |
| |
| static const char kBrotli[] = "br"; |
| |
| if (!request.context()->enable_brotli()) { |
| // Verify that Brotli is enabled globally. |
| return; |
| } |
| |
| if (!params::IsBrotliAcceptEncodingEnabled()) { |
| // Verify that Brotli is enabled for data reduction proxy. |
| return; |
| } |
| |
| if (!proxy_info.proxy_server().is_https() && |
| !proxy_info.proxy_server().is_quic()) { |
| // Brotli encoding can be used only when the proxy server is a secure proxy |
| // server. |
| return; |
| } |
| |
| if (!request_headers->HasHeader(net::HttpRequestHeaders::kAcceptEncoding)) |
| return; |
| |
| std::string header_value; |
| request_headers->GetHeader(net::HttpRequestHeaders::kAcceptEncoding, |
| &header_value); |
| |
| // Brotli should not be already present in the header since the URL is non- |
| // cryptographic. This is an approximate check, and would trigger even if the |
| // accept-encoding header contains an encoding that has prefix |kBrotli|. |
| DCHECK_EQ(std::string::npos, header_value.find(kBrotli)); |
| |
| request_headers->RemoveHeader(net::HttpRequestHeaders::kAcceptEncoding); |
| if (!header_value.empty()) |
| header_value += ", "; |
| header_value += kBrotli; |
| request_headers->SetHeader(net::HttpRequestHeaders::kAcceptEncoding, |
| header_value); |
| } |
| |
| void DataReductionProxyNetworkDelegate::MaybeAddChromeProxyECTHeader( |
| net::HttpRequestHeaders* request_headers, |
| const net::URLRequest& request) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // This method should be called only when the resolved proxy was a data |
| // saver proxy. |
| DCHECK(request.url().is_valid()); |
| DCHECK(!request.url().SchemeIsCryptographic()); |
| DCHECK(request.url().SchemeIsHTTPOrHTTPS()); |
| |
| if (request_headers->HasHeader(chrome_proxy_ect_header())) |
| request_headers->RemoveHeader(chrome_proxy_ect_header()); |
| |
| if (request.context()->network_quality_estimator()) { |
| net::EffectiveConnectionType type = request.context() |
| ->network_quality_estimator() |
| ->GetEffectiveConnectionType(); |
| if (type > net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) { |
| DCHECK_NE(net::EFFECTIVE_CONNECTION_TYPE_LAST, type); |
| request_headers->SetHeader(chrome_proxy_ect_header(), |
| net::GetNameForEffectiveConnectionType(type)); |
| return; |
| } |
| } |
| request_headers->SetHeader(chrome_proxy_ect_header(), |
| net::GetNameForEffectiveConnectionType( |
| net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN)); |
| |
| static_assert(net::EFFECTIVE_CONNECTION_TYPE_OFFLINE + 1 == |
| net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, |
| "ECT enum value is not handled."); |
| static_assert(net::EFFECTIVE_CONNECTION_TYPE_4G + 1 == |
| net::EFFECTIVE_CONNECTION_TYPE_LAST, |
| "ECT enum value is not handled."); |
| } |
| |
| void DataReductionProxyNetworkDelegate::RemoveChromeProxyECTHeader( |
| net::HttpRequestHeaders* request_headers) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| request_headers->RemoveHeader(chrome_proxy_ect_header()); |
| } |
| |
| } // namespace data_reduction_proxy |