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