• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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