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