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