1 // Copyright 2019 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/safe_browsing/cloud_content_scanning/multipart_uploader.h"
6
7 #include <memory>
8
9 #include "base/bind.h"
10 #include "base/memory/scoped_refptr.h"
11 #include "base/metrics/histogram_functions.h"
12 #include "base/time/time.h"
13 #include "components/safe_browsing/core/features.h"
14 #include "content/public/browser/browser_task_traits.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "net/base/mime_util.h"
17 #include "net/http/http_status_code.h"
18 #include "net/traffic_annotation/network_traffic_annotation.h"
19 #include "services/network/public/cpp/resource_request.h"
20 #include "services/network/public/cpp/shared_url_loader_factory.h"
21 #include "services/network/public/cpp/simple_url_loader.h"
22
23 namespace safe_browsing {
24
25 namespace {
26
27 // Constants associated with exponential backoff. On each failure, we will
28 // increase the backoff by |kBackoffFactor|, starting from
29 // |kInitialBackoffSeconds|. If we fail after |kMaxRetryAttempts| retries, the
30 // upload fails.
31 const int kInitialBackoffSeconds = 1;
32 const int kBackoffFactor = 2;
33 const int kMaxRetryAttempts = 2;
34
35 // Content type of a full multipart request
36 const char kUploadContentType[] = "multipart/related; boundary=";
37
38 // Content type of the metadata and file contents.
39 const char kDataContentType[] = "Content-Type: application/octet-stream";
40
RecordUploadSuccessHistogram(bool success)41 void RecordUploadSuccessHistogram(bool success) {
42 base::UmaHistogramBoolean("SBMultipartUploader.UploadSuccess", success);
43 }
44
45 } // namespace
46
MultipartUploadRequest(scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,const GURL & base_url,const std::string & metadata,const std::string & data,const net::NetworkTrafficAnnotationTag & traffic_annotation,Callback callback)47 MultipartUploadRequest::MultipartUploadRequest(
48 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
49 const GURL& base_url,
50 const std::string& metadata,
51 const std::string& data,
52 const net::NetworkTrafficAnnotationTag& traffic_annotation,
53 Callback callback)
54 : base_url_(base_url),
55 metadata_(metadata),
56 data_(data),
57 boundary_(net::GenerateMimeMultipartBoundary()),
58 callback_(std::move(callback)),
59 current_backoff_(base::TimeDelta::FromSeconds(kInitialBackoffSeconds)),
60 retry_count_(0),
61 url_loader_factory_(url_loader_factory),
62 traffic_annotation_(traffic_annotation) {
63 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
64 }
65
~MultipartUploadRequest()66 MultipartUploadRequest::~MultipartUploadRequest() {}
67
Start()68 void MultipartUploadRequest::Start() {
69 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
70
71 start_time_ = base::Time::Now();
72 SendRequest();
73 }
74
GenerateRequestBody(const std::string & metadata,const std::string & data)75 std::string MultipartUploadRequest::GenerateRequestBody(
76 const std::string& metadata,
77 const std::string& data) {
78 return "--" + boundary_ + "\r\n" + kDataContentType + "\r\n\r\n" + metadata +
79 "\r\n--" + boundary_ + "\r\n" + kDataContentType + "\r\n\r\n" + data +
80 "\r\n--" + boundary_ + "--\r\n";
81 }
82
SendRequest()83 void MultipartUploadRequest::SendRequest() {
84 auto resource_request = std::make_unique<network::ResourceRequest>();
85 resource_request->url = base_url_;
86 resource_request->method = "POST";
87 resource_request->headers.SetHeader("X-Goog-Upload-Protocol", "multipart");
88
89 if (base::FeatureList::IsEnabled(kSafeBrowsingRemoveCookies)) {
90 resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
91 }
92
93 url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
94 traffic_annotation_);
95 url_loader_->SetAllowHttpErrorResults(true);
96
97 std::string request_body = GenerateRequestBody(metadata_, data_);
98 base::UmaHistogramMemoryKB("SBMultipartUploader.UploadSize",
99 request_body.size());
100 url_loader_->AttachStringForUpload(request_body,
101 kUploadContentType + boundary_);
102
103 url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
104 url_loader_factory_.get(),
105 base::BindOnce(&MultipartUploadRequest::OnURLLoaderComplete,
106 weak_factory_.GetWeakPtr()));
107 }
108
OnURLLoaderComplete(std::unique_ptr<std::string> response_body)109 void MultipartUploadRequest::OnURLLoaderComplete(
110 std::unique_ptr<std::string> response_body) {
111 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
112 int response_code = 0;
113 if (url_loader_->ResponseInfo() && url_loader_->ResponseInfo()->headers)
114 response_code = url_loader_->ResponseInfo()->headers->response_code();
115
116 RetryOrFinish(url_loader_->NetError(), response_code,
117 std::move(response_body));
118 }
119
RetryOrFinish(int net_error,int response_code,std::unique_ptr<std::string> response_body)120 void MultipartUploadRequest::RetryOrFinish(
121 int net_error,
122 int response_code,
123 std::unique_ptr<std::string> response_body) {
124 base::UmaHistogramSparse(
125 "SBMultipartUploader.NetworkRequestResponseCodeOrError",
126 net_error == net::OK ? response_code : net_error);
127
128 if (net_error == net::OK && response_code == net::HTTP_OK) {
129 RecordUploadSuccessHistogram(/*success=*/true);
130 base::UmaHistogramExactLinear("SBMultipartUploader.RetriesNeeded",
131 retry_count_, kMaxRetryAttempts);
132 base::UmaHistogramMediumTimes(
133 "SBMultipartUploader.SuccessfulUploadDuration",
134 base::Time::Now() - start_time_);
135 std::move(callback_).Run(/*success=*/true, *response_body.get());
136 } else {
137 if (response_code < 500 || retry_count_ >= kMaxRetryAttempts) {
138 RecordUploadSuccessHistogram(/*success=*/false);
139 base::UmaHistogramMediumTimes("SBMultipartUploader.FailedUploadDuration",
140 base::Time::Now() - start_time_);
141 std::move(callback_).Run(/*success=*/false, *response_body.get());
142 } else {
143 content::GetUIThreadTaskRunner({})->PostDelayedTask(
144 FROM_HERE,
145 base::BindOnce(&MultipartUploadRequest::SendRequest,
146 weak_factory_.GetWeakPtr()),
147 current_backoff_);
148 current_backoff_ *= kBackoffFactor;
149 retry_count_++;
150 }
151 }
152 }
153
154 // static
155 MultipartUploadRequestFactory* MultipartUploadRequest::factory_ = nullptr;
156
157 // static
Create(scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,const GURL & base_url,const std::string & metadata,const std::string & data,const net::NetworkTrafficAnnotationTag & traffic_annotation,MultipartUploadRequest::Callback callback)158 std::unique_ptr<MultipartUploadRequest> MultipartUploadRequest::Create(
159 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
160 const GURL& base_url,
161 const std::string& metadata,
162 const std::string& data,
163 const net::NetworkTrafficAnnotationTag& traffic_annotation,
164 MultipartUploadRequest::Callback callback) {
165 if (!factory_) {
166 return std::make_unique<MultipartUploadRequest>(
167 url_loader_factory, base_url, metadata, data, traffic_annotation,
168 std::move(callback));
169 }
170
171 return factory_->Create(url_loader_factory, base_url, metadata, data,
172 traffic_annotation, std::move(callback));
173 }
174
175 } // namespace safe_browsing
176