1 // Copyright (c) 2011 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 // 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 URLRequestFileJob keeps a buffer as a member var. In
13 // URLRequestFileJob::Read, data is simply copied from the object's buffer into
14 // the given buffer. If there is no data to copy, the URLRequestFileJob
15 // attempts to read more from the file to fill its buffer. If reading from the
16 // file does not complete synchronously, then the URLRequestFileJob waits for a
17 // signal from the OS that the overlapped read has completed. It does so by
18 // leveraging the MessageLoop::WatchObject API.
19
20 #include "net/url_request/url_request_file_job.h"
21
22 #include "base/compiler_specific.h"
23 #include "base/message_loop.h"
24 #include "base/platform_file.h"
25 #include "base/string_util.h"
26 #include "base/synchronization/lock.h"
27 #include "base/threading/worker_pool.h"
28 #include "base/threading/thread_restrictions.h"
29 #include "build/build_config.h"
30 #include "googleurl/src/gurl.h"
31 #include "net/base/io_buffer.h"
32 #include "net/base/load_flags.h"
33 #include "net/base/mime_util.h"
34 #include "net/base/net_errors.h"
35 #include "net/base/net_util.h"
36 #include "net/http/http_util.h"
37 #include "net/url_request/url_request.h"
38 #include "net/url_request/url_request_error_job.h"
39 #include "net/url_request/url_request_file_dir_job.h"
40
41 namespace net {
42
43 #if defined(OS_WIN)
44 class URLRequestFileJob::AsyncResolver
45 : public base::RefCountedThreadSafe<URLRequestFileJob::AsyncResolver> {
46 public:
AsyncResolver(URLRequestFileJob * owner)47 explicit AsyncResolver(URLRequestFileJob* owner)
48 : owner_(owner), owner_loop_(MessageLoop::current()) {
49 }
50
Resolve(const FilePath & file_path)51 void Resolve(const FilePath& file_path) {
52 base::PlatformFileInfo file_info;
53 bool exists = file_util::GetFileInfo(file_path, &file_info);
54 base::AutoLock locked(lock_);
55 if (owner_loop_) {
56 owner_loop_->PostTask(FROM_HERE, NewRunnableMethod(
57 this, &AsyncResolver::ReturnResults, exists, file_info));
58 }
59 }
60
Cancel()61 void Cancel() {
62 owner_ = NULL;
63
64 base::AutoLock locked(lock_);
65 owner_loop_ = NULL;
66 }
67
68 private:
69 friend class base::RefCountedThreadSafe<URLRequestFileJob::AsyncResolver>;
70
~AsyncResolver()71 ~AsyncResolver() {}
72
ReturnResults(bool exists,const base::PlatformFileInfo & file_info)73 void ReturnResults(bool exists, const base::PlatformFileInfo& file_info) {
74 if (owner_)
75 owner_->DidResolve(exists, file_info);
76 }
77
78 URLRequestFileJob* owner_;
79
80 base::Lock lock_;
81 MessageLoop* owner_loop_;
82 };
83 #endif
84
URLRequestFileJob(URLRequest * request,const FilePath & file_path)85 URLRequestFileJob::URLRequestFileJob(URLRequest* request,
86 const FilePath& file_path)
87 : URLRequestJob(request),
88 file_path_(file_path),
89 ALLOW_THIS_IN_INITIALIZER_LIST(
90 io_callback_(this, &URLRequestFileJob::DidRead)),
91 is_directory_(false),
92 remaining_bytes_(0),
93 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
94 }
95
96 // static
Factory(URLRequest * request,const std::string & scheme)97 URLRequestJob* URLRequestFileJob::Factory(URLRequest* request,
98 const std::string& scheme) {
99
100 FilePath file_path;
101 const bool is_file = FileURLToFilePath(request->url(), &file_path);
102
103 #if defined(OS_CHROMEOS)
104 // Check file access.
105 if (AccessDisabled(file_path))
106 return new URLRequestErrorJob(request, ERR_ACCESS_DENIED);
107 #endif
108
109 // We need to decide whether to create URLRequestFileJob for file access or
110 // URLRequestFileDirJob for directory access. To avoid accessing the
111 // filesystem, we only look at the path string here.
112 // The code in the URLRequestFileJob::Start() method discovers that a path,
113 // which doesn't end with a slash, should really be treated as a directory,
114 // and it then redirects to the URLRequestFileDirJob.
115 if (is_file &&
116 file_util::EndsWithSeparator(file_path) &&
117 file_path.IsAbsolute())
118 return new URLRequestFileDirJob(request, file_path);
119
120 // Use a regular file request job for all non-directories (including invalid
121 // file names).
122 return new URLRequestFileJob(request, file_path);
123 }
124
125 #if defined(OS_CHROMEOS)
126 static const char* const kLocalAccessWhiteList[] = {
127 "/home/chronos/user/Downloads",
128 "/media",
129 "/opt/oem",
130 "/usr/share/chromeos-assets",
131 "/tmp",
132 "/var/log",
133 };
134
135 // static
AccessDisabled(const FilePath & file_path)136 bool URLRequestFileJob::AccessDisabled(const FilePath& file_path) {
137 if (URLRequest::IsFileAccessAllowed()) { // for tests.
138 return false;
139 }
140
141 for (size_t i = 0; i < arraysize(kLocalAccessWhiteList); ++i) {
142 const FilePath white_listed_path(kLocalAccessWhiteList[i]);
143 // FilePath::operator== should probably handle trailing seperators.
144 if (white_listed_path == file_path.StripTrailingSeparators() ||
145 white_listed_path.IsParent(file_path)) {
146 return false;
147 }
148 }
149 return true;
150 }
151 #endif
152
Start()153 void URLRequestFileJob::Start() {
154 #if defined(OS_WIN)
155 // Resolve UNC paths on a background thread.
156 if (!file_path_.value().compare(0, 2, L"\\\\")) {
157 DCHECK(!async_resolver_);
158 async_resolver_ = new AsyncResolver(this);
159 base::WorkerPool::PostTask(FROM_HERE, NewRunnableMethod(
160 async_resolver_.get(), &AsyncResolver::Resolve, file_path_), true);
161 return;
162 }
163 #endif
164
165 // URL requests should not block on the disk!
166 // http://code.google.com/p/chromium/issues/detail?id=59849
167 bool exists;
168 base::PlatformFileInfo file_info;
169 {
170 base::ThreadRestrictions::ScopedAllowIO allow_io;
171 exists = file_util::GetFileInfo(file_path_, &file_info);
172 }
173
174 // Continue asynchronously.
175 MessageLoop::current()->PostTask(
176 FROM_HERE,
177 method_factory_.NewRunnableMethod(
178 &URLRequestFileJob::DidResolve, exists, file_info));
179 }
180
Kill()181 void URLRequestFileJob::Kill() {
182 stream_.Close();
183
184 #if defined(OS_WIN)
185 if (async_resolver_) {
186 async_resolver_->Cancel();
187 async_resolver_ = NULL;
188 }
189 #endif
190
191 URLRequestJob::Kill();
192 method_factory_.RevokeAll();
193 }
194
ReadRawData(IOBuffer * dest,int dest_size,int * bytes_read)195 bool URLRequestFileJob::ReadRawData(IOBuffer* dest, int dest_size,
196 int *bytes_read) {
197 DCHECK_NE(dest_size, 0);
198 DCHECK(bytes_read);
199 DCHECK_GE(remaining_bytes_, 0);
200
201 if (remaining_bytes_ < dest_size)
202 dest_size = static_cast<int>(remaining_bytes_);
203
204 // If we should copy zero bytes because |remaining_bytes_| is zero, short
205 // circuit here.
206 if (!dest_size) {
207 *bytes_read = 0;
208 return true;
209 }
210
211 int rv = stream_.Read(dest->data(), dest_size, &io_callback_);
212 if (rv >= 0) {
213 // Data is immediately available.
214 *bytes_read = rv;
215 remaining_bytes_ -= rv;
216 DCHECK_GE(remaining_bytes_, 0);
217 return true;
218 }
219
220 // Otherwise, a read error occured. We may just need to wait...
221 if (rv == ERR_IO_PENDING) {
222 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
223 } else {
224 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
225 }
226 return false;
227 }
228
IsRedirectResponse(GURL * location,int * http_status_code)229 bool URLRequestFileJob::IsRedirectResponse(GURL* location,
230 int* http_status_code) {
231 if (is_directory_) {
232 // This happens when we discovered the file is a directory, so needs a
233 // slash at the end of the path.
234 std::string new_path = request_->url().path();
235 new_path.push_back('/');
236 GURL::Replacements replacements;
237 replacements.SetPathStr(new_path);
238
239 *location = request_->url().ReplaceComponents(replacements);
240 *http_status_code = 301; // simulate a permanent redirect
241 return true;
242 }
243
244 #if defined(OS_WIN)
245 // Follow a Windows shortcut.
246 // We just resolve .lnk file, ignore others.
247 if (!LowerCaseEqualsASCII(file_path_.Extension(), ".lnk"))
248 return false;
249
250 FilePath new_path = file_path_;
251 bool resolved;
252 resolved = file_util::ResolveShortcut(&new_path);
253
254 // If shortcut is not resolved succesfully, do not redirect.
255 if (!resolved)
256 return false;
257
258 *location = FilePathToFileURL(new_path);
259 *http_status_code = 301;
260 return true;
261 #else
262 return false;
263 #endif
264 }
265
SetupFilter() const266 Filter* URLRequestFileJob::SetupFilter() const {
267 // Bug 9936 - .svgz files needs to be decompressed.
268 return LowerCaseEqualsASCII(file_path_.Extension(), ".svgz")
269 ? Filter::GZipFactory() : NULL;
270 }
271
GetMimeType(std::string * mime_type) const272 bool URLRequestFileJob::GetMimeType(std::string* mime_type) const {
273 // URL requests should not block on the disk! On Windows this goes to the
274 // registry.
275 // http://code.google.com/p/chromium/issues/detail?id=59849
276 base::ThreadRestrictions::ScopedAllowIO allow_io;
277 DCHECK(request_);
278 return GetMimeTypeFromFile(file_path_, mime_type);
279 }
280
SetExtraRequestHeaders(const HttpRequestHeaders & headers)281 void URLRequestFileJob::SetExtraRequestHeaders(
282 const HttpRequestHeaders& headers) {
283 std::string range_header;
284 if (headers.GetHeader(HttpRequestHeaders::kRange, &range_header)) {
285 // We only care about "Range" header here.
286 std::vector<HttpByteRange> ranges;
287 if (HttpUtil::ParseRangeHeader(range_header, &ranges)) {
288 if (ranges.size() == 1) {
289 byte_range_ = ranges[0];
290 } else {
291 // We don't support multiple range requests in one single URL request,
292 // because we need to do multipart encoding here.
293 // TODO(hclam): decide whether we want to support multiple range
294 // requests.
295 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
296 ERR_REQUEST_RANGE_NOT_SATISFIABLE));
297 }
298 }
299 }
300 }
301
~URLRequestFileJob()302 URLRequestFileJob::~URLRequestFileJob() {
303 #if defined(OS_WIN)
304 DCHECK(!async_resolver_);
305 #endif
306 }
307
DidResolve(bool exists,const base::PlatformFileInfo & file_info)308 void URLRequestFileJob::DidResolve(
309 bool exists, const base::PlatformFileInfo& file_info) {
310 #if defined(OS_WIN)
311 async_resolver_ = NULL;
312 #endif
313
314 // We may have been orphaned...
315 if (!request_)
316 return;
317
318 is_directory_ = file_info.is_directory;
319
320 int rv = OK;
321 // We use URLRequestFileJob to handle files as well as directories without
322 // trailing slash.
323 // If a directory does not exist, we return ERR_FILE_NOT_FOUND. Otherwise,
324 // we will append trailing slash and redirect to FileDirJob.
325 // A special case is "\" on Windows. We should resolve as invalid.
326 // However, Windows resolves "\" to "C:\", thus reports it as existent.
327 // So what happens is we append it with trailing slash and redirect it to
328 // FileDirJob where it is resolved as invalid.
329 if (!exists) {
330 rv = ERR_FILE_NOT_FOUND;
331 } else if (!is_directory_) {
332 // URL requests should not block on the disk!
333 // http://code.google.com/p/chromium/issues/detail?id=59849
334 base::ThreadRestrictions::ScopedAllowIO allow_io;
335
336 int flags = base::PLATFORM_FILE_OPEN |
337 base::PLATFORM_FILE_READ |
338 base::PLATFORM_FILE_ASYNC;
339 rv = stream_.Open(file_path_, flags);
340 }
341
342 if (rv != OK) {
343 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
344 return;
345 }
346
347 if (!byte_range_.ComputeBounds(file_info.size)) {
348 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
349 ERR_REQUEST_RANGE_NOT_SATISFIABLE));
350 return;
351 }
352
353 remaining_bytes_ = byte_range_.last_byte_position() -
354 byte_range_.first_byte_position() + 1;
355 DCHECK_GE(remaining_bytes_, 0);
356
357 // URL requests should not block on the disk!
358 // http://code.google.com/p/chromium/issues/detail?id=59849
359 {
360 base::ThreadRestrictions::ScopedAllowIO allow_io;
361 // Do the seek at the beginning of the request.
362 if (remaining_bytes_ > 0 &&
363 byte_range_.first_byte_position() != 0 &&
364 byte_range_.first_byte_position() !=
365 stream_.Seek(FROM_BEGIN, byte_range_.first_byte_position())) {
366 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
367 ERR_REQUEST_RANGE_NOT_SATISFIABLE));
368 return;
369 }
370 }
371
372 set_expected_content_size(remaining_bytes_);
373 NotifyHeadersComplete();
374 }
375
DidRead(int result)376 void URLRequestFileJob::DidRead(int result) {
377 if (result > 0) {
378 SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
379 } else if (result == 0) {
380 NotifyDone(URLRequestStatus());
381 } else {
382 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
383 }
384
385 remaining_bytes_ -= result;
386 DCHECK_GE(remaining_bytes_, 0);
387
388 NotifyReadComplete(result);
389 }
390
391 } // namespace net
392