1 // Copyright 2018 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 "content/browser/android/content_url_loader_factory.h"
6
7 #include <limits>
8 #include <string>
9 #include <vector>
10
11 #include "base/android/content_uri_utils.h"
12 #include "base/bind.h"
13 #include "base/files/file.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/macros.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/task/task_traits.h"
19 #include "base/task/thread_pool.h"
20 #include "base/time/time.h"
21 #include "content/browser/web_package/web_bundle_utils.h"
22 #include "content/public/browser/content_browser_client.h"
23 #include "content/public/browser/file_url_loader.h"
24 #include "content/public/common/content_client.h"
25 #include "content/public/common/content_switches.h"
26 #include "mojo/public/cpp/bindings/receiver.h"
27 #include "mojo/public/cpp/bindings/remote.h"
28 #include "mojo/public/cpp/system/data_pipe.h"
29 #include "mojo/public/cpp/system/data_pipe_producer.h"
30 #include "mojo/public/cpp/system/file_data_source.h"
31 #include "net/base/net_errors.h"
32 #include "net/http/http_byte_range.h"
33 #include "net/http/http_util.h"
34 #include "services/network/public/cpp/cors/cors.h"
35 #include "services/network/public/cpp/resource_request.h"
36 #include "services/network/public/mojom/url_loader.mojom.h"
37 #include "services/network/public/mojom/url_response_head.mojom.h"
38 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
39
40 // TODO(eroman): Add unit-tests for "X-Chrome-intent-type"
41 // (see url_request_content_job_unittest.cc).
42 // TODO(eroman): Remove duplication with file_url_loader_factory.cc (notably
43 // Range header parsing).
44
45 namespace content {
46 namespace {
47
48 constexpr size_t kDefaultContentUrlPipeSize = 65536;
49
50 // Assigns the byte range that has been requested based on the Range header.
51 // This assumes the simplest form of the Range header using a single range.
52 // If no byte range was specified, the output range will cover the entire file.
GetRequestedByteRange(const network::ResourceRequest & request,uint64_t content_size,uint64_t * first_byte_to_send,uint64_t * total_bytes_to_send)53 bool GetRequestedByteRange(const network::ResourceRequest& request,
54 uint64_t content_size,
55 uint64_t* first_byte_to_send,
56 uint64_t* total_bytes_to_send) {
57 *first_byte_to_send = 0;
58 *total_bytes_to_send = content_size;
59
60 std::string range_header;
61 std::vector<net::HttpByteRange> ranges;
62
63 if (!request.headers.GetHeader(net::HttpRequestHeaders::kRange,
64 &range_header) ||
65 !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
66 return true;
67 }
68
69 // Only handle a simple Range header for a single range.
70 if (ranges.size() != 1 || !ranges[0].IsValid() ||
71 !ranges[0].ComputeBounds(content_size)) {
72 return false;
73 }
74
75 net::HttpByteRange byte_range = ranges[0];
76 *first_byte_to_send = byte_range.first_byte_position();
77 *total_bytes_to_send =
78 byte_range.last_byte_position() - *first_byte_to_send + 1;
79 return true;
80 }
81
82 // Gets the mimetype for |content_path| either by asking the content provider,
83 // or by using the special Chrome request header X-Chrome-intent-type.
GetMimeType(const network::ResourceRequest & request,const base::FilePath & content_path,std::string * out_mime_type)84 void GetMimeType(const network::ResourceRequest& request,
85 const base::FilePath& content_path,
86 std::string* out_mime_type) {
87 out_mime_type->clear();
88
89 std::string intent_type_header;
90 if ((request.resource_type ==
91 static_cast<int>(blink::mojom::ResourceType::kMainFrame)) &&
92 request.headers.GetHeader("X-Chrome-intent-type", &intent_type_header)) {
93 *out_mime_type = intent_type_header;
94 }
95
96 if (out_mime_type->empty())
97 *out_mime_type = base::GetContentUriMimeType(content_path);
98 }
99
100 class ContentURLLoader : public network::mojom::URLLoader {
101 public:
CreateAndStart(const network::ResourceRequest & request,mojo::PendingReceiver<network::mojom::URLLoader> loader,mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote)102 static void CreateAndStart(
103 const network::ResourceRequest& request,
104 mojo::PendingReceiver<network::mojom::URLLoader> loader,
105 mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote) {
106 // Owns itself. Will live as long as its URLLoader and URLLoaderClient
107 // bindings are alive - essentially until either the client gives up or all
108 // file data has been sent to it.
109 auto* content_url_loader = new ContentURLLoader;
110 content_url_loader->Start(request, std::move(loader),
111 std::move(client_remote));
112 }
113
114 // network::mojom::URLLoader:
FollowRedirect(const std::vector<std::string> & removed_headers,const net::HttpRequestHeaders & modified_headers,const net::HttpRequestHeaders & modified_cors_exempt_headers,const base::Optional<GURL> & new_url)115 void FollowRedirect(
116 const std::vector<std::string>& removed_headers,
117 const net::HttpRequestHeaders& modified_headers,
118 const net::HttpRequestHeaders& modified_cors_exempt_headers,
119 const base::Optional<GURL>& new_url) override {}
SetPriority(net::RequestPriority priority,int32_t intra_priority_value)120 void SetPriority(net::RequestPriority priority,
121 int32_t intra_priority_value) override {}
PauseReadingBodyFromNet()122 void PauseReadingBodyFromNet() override {}
ResumeReadingBodyFromNet()123 void ResumeReadingBodyFromNet() override {}
124
125 private:
126 ContentURLLoader() = default;
127 ~ContentURLLoader() override = default;
128
Start(const network::ResourceRequest & request,mojo::PendingReceiver<network::mojom::URLLoader> loader,mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote)129 void Start(
130 const network::ResourceRequest& request,
131 mojo::PendingReceiver<network::mojom::URLLoader> loader,
132 mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote) {
133 bool disable_web_security =
134 base::CommandLine::ForCurrentProcess()->HasSwitch(
135 switches::kDisableWebSecurity);
136 network::mojom::FetchResponseType response_type =
137 network::cors::CalculateResponseType(request.mode,
138 disable_web_security);
139
140 // Don't allow content:// requests with kSameOrigin or kCors* unless the
141 // web security is turned off.
142 if ((!disable_web_security &&
143 request.mode == network::mojom::RequestMode::kSameOrigin) ||
144 response_type == network::mojom::FetchResponseType::kCors) {
145 mojo::Remote<network::mojom::URLLoaderClient>(std::move(client_remote))
146 ->OnComplete(
147 network::URLLoaderCompletionStatus(network::CorsErrorStatus(
148 network::mojom::CorsError::kCorsDisabledScheme)));
149 return;
150 }
151
152 auto head = network::mojom::URLResponseHead::New();
153 head->request_start = head->response_start = base::TimeTicks::Now();
154 head->response_type = response_type;
155 receiver_.Bind(std::move(loader));
156 receiver_.set_disconnect_handler(base::BindOnce(
157 &ContentURLLoader::OnMojoDisconnect, base::Unretained(this)));
158
159 mojo::Remote<network::mojom::URLLoaderClient> client(
160 std::move(client_remote));
161
162 DCHECK(request.url.SchemeIs("content"));
163 base::FilePath path = base::FilePath(request.url.spec());
164
165 // Get the file length.
166 base::File::Info info;
167 if (!base::GetFileInfo(path, &info))
168 return CompleteWithFailure(std::move(client), net::ERR_FILE_NOT_FOUND);
169
170 uint64_t first_byte_to_send;
171 uint64_t total_bytes_to_send;
172 if (!GetRequestedByteRange(request, (info.size > 0) ? info.size : 0,
173 &first_byte_to_send, &total_bytes_to_send) ||
174 (std::numeric_limits<int64_t>::max() < first_byte_to_send) ||
175 (std::numeric_limits<int64_t>::max() < total_bytes_to_send)) {
176 return CompleteWithFailure(std::move(client),
177 net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
178 }
179
180 mojo::DataPipe pipe(kDefaultContentUrlPipeSize);
181 if (!pipe.consumer_handle.is_valid())
182 return CompleteWithFailure(std::move(client), net::ERR_FAILED);
183
184 base::File file = base::OpenContentUriForRead(path);
185 if (!file.IsValid()) {
186 return CompleteWithFailure(
187 std::move(client), net::FileErrorToNetError(file.error_details()));
188 }
189
190 head->content_length = total_bytes_to_send;
191 total_bytes_written_ = total_bytes_to_send;
192
193 // Set the mimetype of the response.
194 GetMimeType(request, path, &head->mime_type);
195 if (head->mime_type.empty() ||
196 head->mime_type == "application/octet-stream") {
197 // When a Web Bundle file is downloaded with
198 // "content-type: application/webbundle;v=b1" header, Chrome saves it as
199 // "application/webbundle" MIME type. The MIME type is stored to Android's
200 // DownloadManager. If the file is opened from a URI which is under
201 // DownloadManager's control and the ContentProvider can get the MIME
202 // type, |head.mime_type| is set to "application/webbundle". But otherwise
203 // ContentResolver.getType() returns null or the default type
204 // [1]"application/octet-stream" even if the file extension is ".wbn".
205 // (eg: opening the file from "Internal Storage")
206 // This is because the Media type of Web Bundles isn't registered
207 // to the IANA registry (https://www.iana.org/assignments/media-types/),
208 // and it is not listed in the mime.types files [2][3] which was referd by
209 // MimeTypeMap.getMimeTypeFromExtension().
210 // So we set the MIME type if the file extension is ".wbn" by calling
211 // web_bundle_utils::GetWebBundleFileMimeTypeFromFile().
212 // [1]
213 // https://android.googlesource.com/platform/frameworks/base/+/1b817f6/core/java/android/content/ContentResolver.java#481
214 // [2] https://android.googlesource.com/platform/external/mime-support/
215 // [3]
216 // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/net/android.mime.types
217 web_bundle_utils::GetWebBundleFileMimeTypeFromFile(path,
218 &head->mime_type);
219 }
220
221 if (!head->mime_type.empty()) {
222 head->headers = base::MakeRefCounted<net::HttpResponseHeaders>("");
223 head->headers->SetHeader(net::HttpRequestHeaders::kContentType,
224 head->mime_type);
225 }
226
227 client->OnReceiveResponse(std::move(head));
228 client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle));
229 client_ = std::move(client);
230
231 if (total_bytes_to_send == 0) {
232 // There's no more data, so we're already done.
233 OnFileWritten(MOJO_RESULT_OK);
234 return;
235 }
236
237 // In case of a range request, seek to the appropriate position before
238 // sending the remaining bytes asynchronously. Under normal conditions
239 // (i.e., no range request) this Seek is effectively a no-op.
240 auto data_source = std::make_unique<mojo::FileDataSource>(std::move(file));
241 data_source->SetRange(first_byte_to_send,
242 first_byte_to_send + total_bytes_to_send);
243
244 data_producer_ = std::make_unique<mojo::DataPipeProducer>(
245 std::move(pipe.producer_handle));
246 data_producer_->Write(std::move(data_source),
247 base::BindOnce(&ContentURLLoader::OnFileWritten,
248 base::Unretained(this)));
249 }
250
CompleteWithFailure(mojo::Remote<network::mojom::URLLoaderClient> client,net::Error net_error)251 void CompleteWithFailure(mojo::Remote<network::mojom::URLLoaderClient> client,
252 net::Error net_error) {
253 client->OnComplete(network::URLLoaderCompletionStatus(net_error));
254 MaybeDeleteSelf();
255 }
256
OnMojoDisconnect()257 void OnMojoDisconnect() {
258 receiver_.reset();
259 MaybeDeleteSelf();
260 }
261
MaybeDeleteSelf()262 void MaybeDeleteSelf() {
263 if (!receiver_.is_bound() && !client_.is_bound())
264 delete this;
265 }
266
OnFileWritten(MojoResult result)267 void OnFileWritten(MojoResult result) {
268 // All the data has been written now. Close the data pipe. The consumer will
269 // be notified that there will be no more data to read from now.
270 data_producer_.reset();
271
272 if (result == MOJO_RESULT_OK) {
273 network::URLLoaderCompletionStatus status(net::OK);
274 status.encoded_data_length = total_bytes_written_;
275 status.encoded_body_length = total_bytes_written_;
276 status.decoded_body_length = total_bytes_written_;
277 client_->OnComplete(status);
278 } else {
279 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
280 }
281 client_.reset();
282 MaybeDeleteSelf();
283 }
284
285 std::unique_ptr<mojo::DataPipeProducer> data_producer_;
286 mojo::Receiver<network::mojom::URLLoader> receiver_{this};
287 mojo::Remote<network::mojom::URLLoaderClient> client_;
288
289 // In case of successful loads, this holds the total of bytes written.
290 // It is used to set some of the URLLoaderCompletionStatus data passed back
291 // to the URLLoaderClients (eg SimpleURLLoader).
292 size_t total_bytes_written_ = 0;
293
294 DISALLOW_COPY_AND_ASSIGN(ContentURLLoader);
295 };
296
297 } // namespace
298
ContentURLLoaderFactory(scoped_refptr<base::SequencedTaskRunner> task_runner,mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver)299 ContentURLLoaderFactory::ContentURLLoaderFactory(
300 scoped_refptr<base::SequencedTaskRunner> task_runner,
301 mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver)
302 : NonNetworkURLLoaderFactoryBase(std::move(factory_receiver)),
303 task_runner_(std::move(task_runner)) {}
304
305 ContentURLLoaderFactory::~ContentURLLoaderFactory() = default;
306
CreateLoaderAndStart(mojo::PendingReceiver<network::mojom::URLLoader> loader,int32_t routing_id,int32_t request_id,uint32_t options,const network::ResourceRequest & request,mojo::PendingRemote<network::mojom::URLLoaderClient> client,const net::MutableNetworkTrafficAnnotationTag & traffic_annotation)307 void ContentURLLoaderFactory::CreateLoaderAndStart(
308 mojo::PendingReceiver<network::mojom::URLLoader> loader,
309 int32_t routing_id,
310 int32_t request_id,
311 uint32_t options,
312 const network::ResourceRequest& request,
313 mojo::PendingRemote<network::mojom::URLLoaderClient> client,
314 const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
315 task_runner_->PostTask(
316 FROM_HERE, base::BindOnce(&ContentURLLoader::CreateAndStart, request,
317 std::move(loader), std::move(client)));
318 }
319
320 // static
321 mojo::PendingRemote<network::mojom::URLLoaderFactory>
Create()322 ContentURLLoaderFactory::Create() {
323 mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote;
324
325 // The ContentURLLoaderFactory will delete itself when there are no more
326 // receivers - see the NonNetworkURLLoaderFactoryBase::OnDisconnect method.
327 new ContentURLLoaderFactory(
328 base::ThreadPool::CreateSequencedTaskRunner(
329 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
330 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}),
331 pending_remote.InitWithNewPipeAndPassReceiver());
332
333 return pending_remote;
334 }
335
336 } // namespace content
337