• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2006-2009 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/worker_pool.h"
27 #include "googleurl/src/gurl.h"
28 #include "net/base/io_buffer.h"
29 #include "net/base/load_flags.h"
30 #include "net/base/mime_util.h"
31 #include "net/base/net_errors.h"
32 #include "net/base/net_util.h"
33 #include "net/http/http_util.h"
34 #include "net/url_request/url_request.h"
35 #include "net/url_request/url_request_file_dir_job.h"
36 
37 #if defined(OS_WIN)
38 class URLRequestFileJob::AsyncResolver :
39     public base::RefCountedThreadSafe<URLRequestFileJob::AsyncResolver> {
40  public:
AsyncResolver(URLRequestFileJob * owner)41   explicit AsyncResolver(URLRequestFileJob* owner)
42       : owner_(owner), owner_loop_(MessageLoop::current()) {
43   }
44 
Resolve(const FilePath & file_path)45   void Resolve(const FilePath& file_path) {
46     file_util::FileInfo file_info;
47     bool exists = file_util::GetFileInfo(file_path, &file_info);
48     AutoLock locked(lock_);
49     if (owner_loop_) {
50       owner_loop_->PostTask(FROM_HERE, NewRunnableMethod(
51           this, &AsyncResolver::ReturnResults, exists, file_info));
52     }
53   }
54 
Cancel()55   void Cancel() {
56     owner_ = NULL;
57 
58     AutoLock locked(lock_);
59     owner_loop_ = NULL;
60   }
61 
62  private:
63   friend class base::RefCountedThreadSafe<URLRequestFileJob::AsyncResolver>;
64 
~AsyncResolver()65   ~AsyncResolver() {}
66 
ReturnResults(bool exists,const file_util::FileInfo & file_info)67   void ReturnResults(bool exists, const file_util::FileInfo& file_info) {
68     if (owner_)
69       owner_->DidResolve(exists, file_info);
70   }
71 
72   URLRequestFileJob* owner_;
73 
74   Lock lock_;
75   MessageLoop* owner_loop_;
76 };
77 #endif
78 
79 // static
Factory(URLRequest * request,const std::string & scheme)80 URLRequestJob* URLRequestFileJob::Factory(
81     URLRequest* request, const std::string& scheme) {
82   FilePath file_path;
83   if (net::FileURLToFilePath(request->url(), &file_path) &&
84       file_util::EnsureEndsWithSeparator(&file_path) &&
85       file_path.IsAbsolute())
86     return new URLRequestFileDirJob(request, file_path);
87 
88   // Use a regular file request job for all non-directories (including invalid
89   // file names).
90   return new URLRequestFileJob(request, file_path);
91 }
92 
URLRequestFileJob(URLRequest * request,const FilePath & file_path)93 URLRequestFileJob::URLRequestFileJob(URLRequest* request,
94                                      const FilePath& file_path)
95     : URLRequestJob(request),
96       file_path_(file_path),
97       ALLOW_THIS_IN_INITIALIZER_LIST(
98           io_callback_(this, &URLRequestFileJob::DidRead)),
99       is_directory_(false),
100       remaining_bytes_(0) {
101 }
102 
~URLRequestFileJob()103 URLRequestFileJob::~URLRequestFileJob() {
104 #if defined(OS_WIN)
105   DCHECK(!async_resolver_);
106 #endif
107 }
108 
Start()109 void URLRequestFileJob::Start() {
110 #if defined(OS_WIN)
111   // Resolve UNC paths on a background thread.
112   if (!file_path_.value().compare(0, 2, L"\\\\")) {
113     DCHECK(!async_resolver_);
114     async_resolver_ = new AsyncResolver(this);
115     WorkerPool::PostTask(FROM_HERE, NewRunnableMethod(
116         async_resolver_.get(), &AsyncResolver::Resolve, file_path_), true);
117     return;
118   }
119 #endif
120   file_util::FileInfo file_info;
121   bool exists = file_util::GetFileInfo(file_path_, &file_info);
122 
123   // Continue asynchronously.
124   MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
125       this, &URLRequestFileJob::DidResolve, exists, file_info));
126 }
127 
Kill()128 void URLRequestFileJob::Kill() {
129   stream_.Close();
130 
131 #if defined(OS_WIN)
132   if (async_resolver_) {
133     async_resolver_->Cancel();
134     async_resolver_ = NULL;
135   }
136 #endif
137 
138   URLRequestJob::Kill();
139 }
140 
ReadRawData(net::IOBuffer * dest,int dest_size,int * bytes_read)141 bool URLRequestFileJob::ReadRawData(net::IOBuffer* dest, int dest_size,
142                                     int *bytes_read) {
143   DCHECK_NE(dest_size, 0);
144   DCHECK(bytes_read);
145   DCHECK_GE(remaining_bytes_, 0);
146 
147   if (remaining_bytes_ < dest_size)
148     dest_size = static_cast<int>(remaining_bytes_);
149 
150   // If we should copy zero bytes because |remaining_bytes_| is zero, short
151   // circuit here.
152   if (!dest_size) {
153     *bytes_read = 0;
154     return true;
155   }
156 
157   int rv = stream_.Read(dest->data(), dest_size, &io_callback_);
158   if (rv >= 0) {
159     // Data is immediately available.
160     *bytes_read = rv;
161     remaining_bytes_ -= rv;
162     DCHECK_GE(remaining_bytes_, 0);
163     return true;
164   }
165 
166   // Otherwise, a read error occured.  We may just need to wait...
167   if (rv == net::ERR_IO_PENDING) {
168     SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
169   } else {
170     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
171   }
172   return false;
173 }
174 
GetContentEncodings(std::vector<Filter::FilterType> * encoding_types)175 bool URLRequestFileJob::GetContentEncodings(
176     std::vector<Filter::FilterType>* encoding_types) {
177   DCHECK(encoding_types->empty());
178 
179   // Bug 9936 - .svgz files needs to be decompressed.
180   if (LowerCaseEqualsASCII(file_path_.Extension(), ".svgz"))
181     encoding_types->push_back(Filter::FILTER_TYPE_GZIP);
182 
183   return !encoding_types->empty();
184 }
185 
GetMimeType(std::string * mime_type) const186 bool URLRequestFileJob::GetMimeType(std::string* mime_type) const {
187   DCHECK(request_);
188   return net::GetMimeTypeFromFile(file_path_, mime_type);
189 }
190 
SetExtraRequestHeaders(const std::string & headers)191 void URLRequestFileJob::SetExtraRequestHeaders(const std::string& headers) {
192   // We only care about "Range" header here.
193   std::vector<net::HttpByteRange> ranges;
194   if (net::HttpUtil::ParseRanges(headers, &ranges)) {
195     if (ranges.size() == 1) {
196       byte_range_ = ranges[0];
197     } else {
198       // We don't support multiple range requests in one single URL request,
199       // because we need to do multipart encoding here.
200       // TODO(hclam): decide whether we want to support multiple range requests.
201       NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
202                  net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
203     }
204   }
205 }
206 
DidResolve(bool exists,const file_util::FileInfo & file_info)207 void URLRequestFileJob::DidResolve(
208     bool exists, const file_util::FileInfo& file_info) {
209 #if defined(OS_WIN)
210   async_resolver_ = NULL;
211 #endif
212 
213   // We may have been orphaned...
214   if (!request_)
215     return;
216 
217   int rv = net::OK;
218   // We use URLRequestFileJob to handle valid and invalid files as well as
219   // invalid directories. For a directory to be invalid, it must either not
220   // exist, or be "\" on Windows. (Windows resolves "\" to "C:\", thus
221   // reporting it as existent.) On POSIX, we don't count any existent
222   // directory as invalid.
223   if (!exists || file_info.is_directory) {
224     rv = net::ERR_FILE_NOT_FOUND;
225   } else {
226     int flags = base::PLATFORM_FILE_OPEN |
227                 base::PLATFORM_FILE_READ |
228                 base::PLATFORM_FILE_ASYNC;
229     rv = stream_.Open(file_path_, flags);
230   }
231 
232   if (rv != net::OK) {
233     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
234     return;
235   }
236 
237   if (!byte_range_.ComputeBounds(file_info.size)) {
238     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
239                net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
240     return;
241   }
242 
243   remaining_bytes_ = byte_range_.last_byte_position() -
244                      byte_range_.first_byte_position() + 1;
245   DCHECK_GE(remaining_bytes_, 0);
246 
247   // Do the seek at the beginning of the request.
248   if (remaining_bytes_ > 0 &&
249       byte_range_.first_byte_position() != 0 &&
250       byte_range_.first_byte_position() !=
251           stream_.Seek(net::FROM_BEGIN, byte_range_.first_byte_position())) {
252     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
253                net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
254     return;
255   }
256 
257   set_expected_content_size(remaining_bytes_);
258   NotifyHeadersComplete();
259 }
260 
DidRead(int result)261 void URLRequestFileJob::DidRead(int result) {
262   if (result > 0) {
263     SetStatus(URLRequestStatus());  // Clear the IO_PENDING status
264   } else if (result == 0) {
265     NotifyDone(URLRequestStatus());
266   } else {
267     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
268   }
269 
270   remaining_bytes_ -= result;
271   DCHECK_GE(remaining_bytes_, 0);
272 
273   NotifyReadComplete(result);
274 }
275 
IsRedirectResponse(GURL * location,int * http_status_code)276 bool URLRequestFileJob::IsRedirectResponse(
277     GURL* location, int* http_status_code) {
278 #if defined(OS_WIN)
279   std::wstring extension =
280       file_util::GetFileExtensionFromPath(file_path_.value());
281 
282   // Follow a Windows shortcut.
283   // We just resolve .lnk file, ignore others.
284   if (!LowerCaseEqualsASCII(extension, "lnk"))
285     return false;
286 
287   FilePath new_path = file_path_;
288   bool resolved;
289   resolved = file_util::ResolveShortcut(&new_path);
290 
291   // If shortcut is not resolved succesfully, do not redirect.
292   if (!resolved)
293     return false;
294 
295   *location = net::FilePathToFileURL(new_path);
296   *http_status_code = 301;
297   return true;
298 #else
299   return false;
300 #endif
301 }
302