• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // For loading files, we make use of overlapped i/o to ensure that reading from
6 // the filesystem (e.g., a network filesystem) does not block the calling
7 // thread.  An alternative approach would be to use a background thread or pool
8 // of threads, but it seems better to leverage the operating system's ability
9 // to do background file reads for us.
10 //
11 // Since overlapped reads require a 'static' buffer for the duration of the
12 // asynchronous read, the URLRequestTestJobBackedByFile keeps a buffer as a
13 // member var.  In URLRequestTestJobBackedByFile::Read, data is simply copied
14 // from the object's buffer into the given buffer.  If there is no data to copy,
15 // the URLRequestTestJobBackedByFile attempts to read more from the file to fill
16 // its buffer.  If reading from the file does not complete synchronously, then
17 // the URLRequestTestJobBackedByFile waits for a signal from the OS that the
18 // overlapped read has completed.  It does so by leveraging the
19 // MessageLoop::WatchObject API.
20 
21 #include "net/test/url_request/url_request_test_job_backed_by_file.h"
22 
23 #include "base/compiler_specific.h"
24 #include "base/files/file_util.h"
25 #include "base/functional/bind.h"
26 #include "base/strings/string_util.h"
27 #include "base/synchronization/lock.h"
28 #include "base/task/task_runner.h"
29 #include "base/threading/thread_restrictions.h"
30 #include "build/build_config.h"
31 #include "net/base/file_stream.h"
32 #include "net/base/filename_util.h"
33 #include "net/base/io_buffer.h"
34 #include "net/base/load_flags.h"
35 #include "net/base/mime_util.h"
36 #include "net/filter/gzip_source_stream.h"
37 #include "net/filter/source_stream.h"
38 #include "net/http/http_util.h"
39 #include "net/url_request/url_request_error_job.h"
40 #include "url/gurl.h"
41 
42 #if BUILDFLAG(IS_WIN)
43 #include "base/win/shortcut.h"
44 #endif
45 
46 namespace net {
47 
48 URLRequestTestJobBackedByFile::FileMetaInfo::FileMetaInfo() = default;
49 
URLRequestTestJobBackedByFile(URLRequest * request,const base::FilePath & file_path,const scoped_refptr<base::TaskRunner> & file_task_runner)50 URLRequestTestJobBackedByFile::URLRequestTestJobBackedByFile(
51     URLRequest* request,
52     const base::FilePath& file_path,
53     const scoped_refptr<base::TaskRunner>& file_task_runner)
54     : URLRequestJob(request),
55       file_path_(file_path),
56       stream_(std::make_unique<FileStream>(file_task_runner)),
57       file_task_runner_(file_task_runner) {}
58 
Start()59 void URLRequestTestJobBackedByFile::Start() {
60   file_task_runner_->PostTaskAndReplyWithResult(
61       FROM_HERE,
62       base::BindOnce(&URLRequestTestJobBackedByFile::FetchMetaInfo, file_path_),
63       base::BindOnce(&URLRequestTestJobBackedByFile::DidFetchMetaInfo,
64                      weak_ptr_factory_.GetWeakPtr()));
65 }
66 
Kill()67 void URLRequestTestJobBackedByFile::Kill() {
68   stream_.reset();
69   weak_ptr_factory_.InvalidateWeakPtrs();
70 
71   URLRequestJob::Kill();
72 }
73 
ReadRawData(IOBuffer * dest,int dest_size)74 int URLRequestTestJobBackedByFile::ReadRawData(IOBuffer* dest, int dest_size) {
75   DCHECK_NE(dest_size, 0);
76   DCHECK_GE(remaining_bytes_, 0);
77 
78   if (remaining_bytes_ < dest_size)
79     dest_size = remaining_bytes_;
80 
81   // If we should copy zero bytes because |remaining_bytes_| is zero, short
82   // circuit here.
83   if (!dest_size)
84     return 0;
85 
86   int rv = stream_->Read(dest, dest_size,
87                          base::BindOnce(&URLRequestTestJobBackedByFile::DidRead,
88                                         weak_ptr_factory_.GetWeakPtr(),
89                                         base::WrapRefCounted(dest)));
90   if (rv >= 0) {
91     remaining_bytes_ -= rv;
92     DCHECK_GE(remaining_bytes_, 0);
93   }
94 
95   return rv;
96 }
97 
GetMimeType(std::string * mime_type) const98 bool URLRequestTestJobBackedByFile::GetMimeType(std::string* mime_type) const {
99   DCHECK(request_);
100   if (meta_info_.mime_type_result) {
101     *mime_type = meta_info_.mime_type;
102     return true;
103   }
104   return false;
105 }
106 
SetExtraRequestHeaders(const HttpRequestHeaders & headers)107 void URLRequestTestJobBackedByFile::SetExtraRequestHeaders(
108     const HttpRequestHeaders& headers) {
109   std::string range_header;
110   if (headers.GetHeader(HttpRequestHeaders::kRange, &range_header)) {
111     // This job only cares about the Range header. This method stashes the value
112     // for later use in DidOpen(), which is responsible for some of the range
113     // validation as well. NotifyStartError is not legal to call here since
114     // the job has not started.
115     std::vector<HttpByteRange> ranges;
116     if (HttpUtil::ParseRangeHeader(range_header, &ranges)) {
117       if (ranges.size() == 1) {
118         byte_range_ = ranges[0];
119       } else {
120         // We don't support multiple range requests in one single URL request,
121         // because we need to do multipart encoding here.
122         // TODO(hclam): decide whether we want to support multiple range
123         // requests.
124         range_parse_result_ = ERR_REQUEST_RANGE_NOT_SATISFIABLE;
125       }
126     }
127   }
128 }
129 
GetResponseInfo(HttpResponseInfo * info)130 void URLRequestTestJobBackedByFile::GetResponseInfo(HttpResponseInfo* info) {
131   if (!serve_mime_type_as_content_type_ || !meta_info_.mime_type_result)
132     return;
133   auto headers =
134       base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
135   headers->AddHeader(net::HttpRequestHeaders::kContentType,
136                      meta_info_.mime_type);
137   info->headers = headers;
138 }
139 
OnOpenComplete(int result)140 void URLRequestTestJobBackedByFile::OnOpenComplete(int result) {}
141 
OnSeekComplete(int64_t result)142 void URLRequestTestJobBackedByFile::OnSeekComplete(int64_t result) {}
143 
OnReadComplete(IOBuffer * buf,int result)144 void URLRequestTestJobBackedByFile::OnReadComplete(IOBuffer* buf, int result) {}
145 
146 URLRequestTestJobBackedByFile::~URLRequestTestJobBackedByFile() = default;
147 
148 std::unique_ptr<SourceStream>
SetUpSourceStream()149 URLRequestTestJobBackedByFile::SetUpSourceStream() {
150   std::unique_ptr<SourceStream> source = URLRequestJob::SetUpSourceStream();
151   if (!base::EqualsCaseInsensitiveASCII(file_path_.Extension(), ".svgz"))
152     return source;
153 
154   return GzipSourceStream::Create(std::move(source), SourceStream::TYPE_GZIP);
155 }
156 
157 std::unique_ptr<URLRequestTestJobBackedByFile::FileMetaInfo>
FetchMetaInfo(const base::FilePath & file_path)158 URLRequestTestJobBackedByFile::FetchMetaInfo(const base::FilePath& file_path) {
159   auto meta_info = std::make_unique<FileMetaInfo>();
160   base::File::Info file_info;
161   meta_info->file_exists = base::GetFileInfo(file_path, &file_info);
162   if (meta_info->file_exists) {
163     meta_info->file_size = file_info.size;
164     meta_info->is_directory = file_info.is_directory;
165   }
166   // On Windows GetMimeTypeFromFile() goes to the registry. Thus it should be
167   // done in WorkerPool.
168   meta_info->mime_type_result =
169       GetMimeTypeFromFile(file_path, &meta_info->mime_type);
170   meta_info->absolute_path = base::MakeAbsoluteFilePath(file_path);
171   return meta_info;
172 }
173 
DidFetchMetaInfo(std::unique_ptr<FileMetaInfo> meta_info)174 void URLRequestTestJobBackedByFile::DidFetchMetaInfo(
175     std::unique_ptr<FileMetaInfo> meta_info) {
176   meta_info_ = *meta_info;
177 
178   if (!meta_info_.file_exists) {
179     DidOpen(ERR_FILE_NOT_FOUND);
180     return;
181   }
182 
183   // This class is only used for mocking out network requests in test by using a
184   // file as a response body. It doesn't need to support directory listings.
185   if (meta_info_.is_directory) {
186     DidOpen(ERR_INVALID_ARGUMENT);
187     return;
188   }
189 
190   int flags =
191       base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_ASYNC;
192   int rv = stream_->Open(file_path_, flags,
193                          base::BindOnce(&URLRequestTestJobBackedByFile::DidOpen,
194                                         weak_ptr_factory_.GetWeakPtr()));
195   if (rv != ERR_IO_PENDING)
196     DidOpen(rv);
197 }
198 
DidOpen(int result)199 void URLRequestTestJobBackedByFile::DidOpen(int result) {
200   OnOpenComplete(result);
201   if (result != OK) {
202     NotifyStartError(result);
203     return;
204   }
205 
206   if (range_parse_result_ != OK ||
207       !byte_range_.ComputeBounds(meta_info_.file_size)) {
208     DidSeek(ERR_REQUEST_RANGE_NOT_SATISFIABLE);
209     return;
210   }
211 
212   remaining_bytes_ =
213       byte_range_.last_byte_position() - byte_range_.first_byte_position() + 1;
214   DCHECK_GE(remaining_bytes_, 0);
215 
216   if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) {
217     int rv =
218         stream_->Seek(byte_range_.first_byte_position(),
219                       base::BindOnce(&URLRequestTestJobBackedByFile::DidSeek,
220                                      weak_ptr_factory_.GetWeakPtr()));
221     if (rv != ERR_IO_PENDING)
222       DidSeek(ERR_REQUEST_RANGE_NOT_SATISFIABLE);
223   } else {
224     // We didn't need to call stream_->Seek() at all, so we pass to DidSeek()
225     // the value that would mean seek success. This way we skip the code
226     // handling seek failure.
227     DidSeek(byte_range_.first_byte_position());
228   }
229 }
230 
DidSeek(int64_t result)231 void URLRequestTestJobBackedByFile::DidSeek(int64_t result) {
232   DCHECK(result < 0 || result == byte_range_.first_byte_position());
233 
234   OnSeekComplete(result);
235   if (result < 0) {
236     NotifyStartError(ERR_REQUEST_RANGE_NOT_SATISFIABLE);
237     return;
238   }
239 
240   set_expected_content_size(remaining_bytes_);
241   NotifyHeadersComplete();
242 }
243 
DidRead(scoped_refptr<IOBuffer> buf,int result)244 void URLRequestTestJobBackedByFile::DidRead(scoped_refptr<IOBuffer> buf,
245                                             int result) {
246   if (result >= 0) {
247     remaining_bytes_ -= result;
248     DCHECK_GE(remaining_bytes_, 0);
249   }
250 
251   OnReadComplete(buf.get(), result);
252   buf = nullptr;
253 
254   ReadRawDataComplete(result);
255 }
256 
257 }  // namespace net
258