Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 1 | // Copyright (c) 2012 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/bind.h" |
| 23 | #include "base/compiler_specific.h" |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 24 | #include "base/file_util.h" |
Ben Murdoch | 9ab5563 | 2013-07-18 11:57:30 +0100 | [diff] [blame] | 25 | #include "base/message_loop/message_loop.h" |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 26 | #include "base/strings/string_util.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 27 | #include "base/synchronization/lock.h" |
Torne (Richard Coles) | 3551c9c | 2013-08-23 16:39:15 +0100 | [diff] [blame] | 28 | #include "base/task_runner.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 29 | #include "base/threading/thread_restrictions.h" |
| 30 | #include "build/build_config.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 31 | #include "net/base/file_stream.h" |
Ben Murdoch | c5cede9 | 2014-04-10 11:22:14 +0100 | [diff] [blame] | 32 | #include "net/base/filename_util.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 33 | #include "net/base/io_buffer.h" |
| 34 | #include "net/base/load_flags.h" |
| 35 | #include "net/base/mime_util.h" |
| 36 | #include "net/base/net_errors.h" |
Torne (Richard Coles) | 5d1f7b1 | 2014-02-21 12:16:55 +0000 | [diff] [blame] | 37 | #include "net/filter/filter.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 38 | #include "net/http/http_util.h" |
| 39 | #include "net/url_request/url_request_error_job.h" |
| 40 | #include "net/url_request/url_request_file_dir_job.h" |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 41 | #include "url/gurl.h" |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 42 | |
| 43 | #if defined(OS_WIN) |
| 44 | #include "base/win/shortcut.h" |
| 45 | #endif |
| 46 | |
| 47 | namespace net { |
| 48 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 49 | URLRequestFileJob::FileMetaInfo::FileMetaInfo() |
| 50 | : file_size(0), |
| 51 | mime_type_result(false), |
| 52 | file_exists(false), |
| 53 | is_directory(false) { |
| 54 | } |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 55 | |
Torne (Richard Coles) | 3551c9c | 2013-08-23 16:39:15 +0100 | [diff] [blame] | 56 | URLRequestFileJob::URLRequestFileJob( |
| 57 | URLRequest* request, |
| 58 | NetworkDelegate* network_delegate, |
| 59 | const base::FilePath& file_path, |
| 60 | const scoped_refptr<base::TaskRunner>& file_task_runner) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 61 | : URLRequestJob(request, network_delegate), |
| 62 | file_path_(file_path), |
Ben Murdoch | 0529e5d | 2014-04-24 10:50:13 +0100 | [diff] [blame] | 63 | stream_(new FileStream(file_task_runner)), |
Torne (Richard Coles) | 3551c9c | 2013-08-23 16:39:15 +0100 | [diff] [blame] | 64 | file_task_runner_(file_task_runner), |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 65 | remaining_bytes_(0), |
Torne (Richard Coles) | 3551c9c | 2013-08-23 16:39:15 +0100 | [diff] [blame] | 66 | weak_ptr_factory_(this) {} |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 67 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 68 | void URLRequestFileJob::Start() { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 69 | FileMetaInfo* meta_info = new FileMetaInfo(); |
Torne (Richard Coles) | 3551c9c | 2013-08-23 16:39:15 +0100 | [diff] [blame] | 70 | file_task_runner_->PostTaskAndReply( |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 71 | FROM_HERE, |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 72 | base::Bind(&URLRequestFileJob::FetchMetaInfo, file_path_, |
| 73 | base::Unretained(meta_info)), |
| 74 | base::Bind(&URLRequestFileJob::DidFetchMetaInfo, |
| 75 | weak_ptr_factory_.GetWeakPtr(), |
Torne (Richard Coles) | 3551c9c | 2013-08-23 16:39:15 +0100 | [diff] [blame] | 76 | base::Owned(meta_info))); |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 77 | } |
| 78 | |
| 79 | void URLRequestFileJob::Kill() { |
| 80 | stream_.reset(); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 81 | weak_ptr_factory_.InvalidateWeakPtrs(); |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 82 | |
| 83 | URLRequestJob::Kill(); |
| 84 | } |
| 85 | |
Ben Murdoch | a02191e | 2014-04-16 11:17:03 +0100 | [diff] [blame] | 86 | bool URLRequestFileJob::ReadRawData(IOBuffer* dest, |
| 87 | int dest_size, |
| 88 | int* bytes_read) { |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 89 | DCHECK_NE(dest_size, 0); |
| 90 | DCHECK(bytes_read); |
| 91 | DCHECK_GE(remaining_bytes_, 0); |
| 92 | |
| 93 | if (remaining_bytes_ < dest_size) |
| 94 | dest_size = static_cast<int>(remaining_bytes_); |
| 95 | |
| 96 | // If we should copy zero bytes because |remaining_bytes_| is zero, short |
| 97 | // circuit here. |
| 98 | if (!dest_size) { |
| 99 | *bytes_read = 0; |
| 100 | return true; |
| 101 | } |
| 102 | |
Ben Murdoch | a02191e | 2014-04-16 11:17:03 +0100 | [diff] [blame] | 103 | int rv = stream_->Read(dest, |
| 104 | dest_size, |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 105 | base::Bind(&URLRequestFileJob::DidRead, |
Ben Murdoch | a02191e | 2014-04-16 11:17:03 +0100 | [diff] [blame] | 106 | weak_ptr_factory_.GetWeakPtr(), |
| 107 | make_scoped_refptr(dest))); |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 108 | if (rv >= 0) { |
| 109 | // Data is immediately available. |
| 110 | *bytes_read = rv; |
| 111 | remaining_bytes_ -= rv; |
| 112 | DCHECK_GE(remaining_bytes_, 0); |
| 113 | return true; |
| 114 | } |
| 115 | |
| 116 | // Otherwise, a read error occured. We may just need to wait... |
| 117 | if (rv == ERR_IO_PENDING) { |
| 118 | SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); |
| 119 | } else { |
| 120 | NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); |
| 121 | } |
| 122 | return false; |
| 123 | } |
| 124 | |
| 125 | bool URLRequestFileJob::IsRedirectResponse(GURL* location, |
| 126 | int* http_status_code) { |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 127 | if (meta_info_.is_directory) { |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 128 | // This happens when we discovered the file is a directory, so needs a |
| 129 | // slash at the end of the path. |
| 130 | std::string new_path = request_->url().path(); |
| 131 | new_path.push_back('/'); |
| 132 | GURL::Replacements replacements; |
| 133 | replacements.SetPathStr(new_path); |
| 134 | |
| 135 | *location = request_->url().ReplaceComponents(replacements); |
| 136 | *http_status_code = 301; // simulate a permanent redirect |
| 137 | return true; |
| 138 | } |
| 139 | |
| 140 | #if defined(OS_WIN) |
| 141 | // Follow a Windows shortcut. |
| 142 | // We just resolve .lnk file, ignore others. |
| 143 | if (!LowerCaseEqualsASCII(file_path_.Extension(), ".lnk")) |
| 144 | return false; |
| 145 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 146 | base::FilePath new_path = file_path_; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 147 | bool resolved; |
| 148 | resolved = base::win::ResolveShortcut(new_path, &new_path, NULL); |
| 149 | |
| 150 | // If shortcut is not resolved succesfully, do not redirect. |
| 151 | if (!resolved) |
| 152 | return false; |
| 153 | |
| 154 | *location = FilePathToFileURL(new_path); |
| 155 | *http_status_code = 301; |
| 156 | return true; |
| 157 | #else |
| 158 | return false; |
| 159 | #endif |
| 160 | } |
| 161 | |
| 162 | Filter* URLRequestFileJob::SetupFilter() const { |
| 163 | // Bug 9936 - .svgz files needs to be decompressed. |
| 164 | return LowerCaseEqualsASCII(file_path_.Extension(), ".svgz") |
| 165 | ? Filter::GZipFactory() : NULL; |
| 166 | } |
| 167 | |
| 168 | bool URLRequestFileJob::GetMimeType(std::string* mime_type) const { |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 169 | DCHECK(request_); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 170 | if (meta_info_.mime_type_result) { |
| 171 | *mime_type = meta_info_.mime_type; |
| 172 | return true; |
| 173 | } |
| 174 | return false; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | void URLRequestFileJob::SetExtraRequestHeaders( |
| 178 | const HttpRequestHeaders& headers) { |
| 179 | std::string range_header; |
| 180 | if (headers.GetHeader(HttpRequestHeaders::kRange, &range_header)) { |
| 181 | // We only care about "Range" header here. |
| 182 | std::vector<HttpByteRange> ranges; |
| 183 | if (HttpUtil::ParseRangeHeader(range_header, &ranges)) { |
| 184 | if (ranges.size() == 1) { |
| 185 | byte_range_ = ranges[0]; |
| 186 | } else { |
| 187 | // We don't support multiple range requests in one single URL request, |
| 188 | // because we need to do multipart encoding here. |
| 189 | // TODO(hclam): decide whether we want to support multiple range |
| 190 | // requests. |
| 191 | NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, |
| 192 | ERR_REQUEST_RANGE_NOT_SATISFIABLE)); |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | } |
| 197 | |
Ben Murdoch | a02191e | 2014-04-16 11:17:03 +0100 | [diff] [blame] | 198 | void URLRequestFileJob::OnSeekComplete(int64 result) { |
| 199 | } |
| 200 | |
| 201 | void URLRequestFileJob::OnReadComplete(net::IOBuffer* buf, int result) { |
| 202 | } |
| 203 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 204 | URLRequestFileJob::~URLRequestFileJob() { |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 205 | } |
| 206 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 207 | void URLRequestFileJob::FetchMetaInfo(const base::FilePath& file_path, |
| 208 | FileMetaInfo* meta_info) { |
Torne (Richard Coles) | 5d1f7b1 | 2014-02-21 12:16:55 +0000 | [diff] [blame] | 209 | base::File::Info file_info; |
| 210 | meta_info->file_exists = base::GetFileInfo(file_path, &file_info); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 211 | if (meta_info->file_exists) { |
Torne (Richard Coles) | 5d1f7b1 | 2014-02-21 12:16:55 +0000 | [diff] [blame] | 212 | meta_info->file_size = file_info.size; |
| 213 | meta_info->is_directory = file_info.is_directory; |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 214 | } |
| 215 | // On Windows GetMimeTypeFromFile() goes to the registry. Thus it should be |
| 216 | // done in WorkerPool. |
| 217 | meta_info->mime_type_result = GetMimeTypeFromFile(file_path, |
| 218 | &meta_info->mime_type); |
| 219 | } |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 220 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 221 | void URLRequestFileJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) { |
| 222 | meta_info_ = *meta_info; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 223 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 224 | // We use URLRequestFileJob to handle files as well as directories without |
| 225 | // trailing slash. |
| 226 | // If a directory does not exist, we return ERR_FILE_NOT_FOUND. Otherwise, |
| 227 | // we will append trailing slash and redirect to FileDirJob. |
| 228 | // A special case is "\" on Windows. We should resolve as invalid. |
| 229 | // However, Windows resolves "\" to "C:\", thus reports it as existent. |
| 230 | // So what happens is we append it with trailing slash and redirect it to |
| 231 | // FileDirJob where it is resolved as invalid. |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 232 | if (!meta_info_.file_exists) { |
| 233 | DidOpen(ERR_FILE_NOT_FOUND); |
| 234 | return; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 235 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 236 | if (meta_info_.is_directory) { |
| 237 | DidOpen(OK); |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 238 | return; |
| 239 | } |
| 240 | |
Torne (Richard Coles) | 23730a6 | 2014-03-21 14:25:57 +0000 | [diff] [blame] | 241 | int flags = base::File::FLAG_OPEN | |
| 242 | base::File::FLAG_READ | |
| 243 | base::File::FLAG_ASYNC; |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 244 | int rv = stream_->Open(file_path_, flags, |
| 245 | base::Bind(&URLRequestFileJob::DidOpen, |
| 246 | weak_ptr_factory_.GetWeakPtr())); |
| 247 | if (rv != ERR_IO_PENDING) |
| 248 | DidOpen(rv); |
| 249 | } |
| 250 | |
| 251 | void URLRequestFileJob::DidOpen(int result) { |
| 252 | if (result != OK) { |
| 253 | NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); |
| 254 | return; |
| 255 | } |
| 256 | |
| 257 | if (!byte_range_.ComputeBounds(meta_info_.file_size)) { |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 258 | NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, |
| 259 | ERR_REQUEST_RANGE_NOT_SATISFIABLE)); |
| 260 | return; |
| 261 | } |
| 262 | |
| 263 | remaining_bytes_ = byte_range_.last_byte_position() - |
| 264 | byte_range_.first_byte_position() + 1; |
| 265 | DCHECK_GE(remaining_bytes_, 0); |
| 266 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 267 | if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) { |
Ben Murdoch | 116680a | 2014-07-20 18:25:52 -0700 | [diff] [blame] | 268 | int rv = stream_->Seek(base::File::FROM_BEGIN, |
| 269 | byte_range_.first_byte_position(), |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 270 | base::Bind(&URLRequestFileJob::DidSeek, |
| 271 | weak_ptr_factory_.GetWeakPtr())); |
| 272 | if (rv != ERR_IO_PENDING) { |
| 273 | // stream_->Seek() failed, so pass an intentionally erroneous value |
| 274 | // into DidSeek(). |
| 275 | DidSeek(-1); |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 276 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 277 | } else { |
| 278 | // We didn't need to call stream_->Seek() at all, so we pass to DidSeek() |
| 279 | // the value that would mean seek success. This way we skip the code |
| 280 | // handling seek failure. |
| 281 | DidSeek(byte_range_.first_byte_position()); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | void URLRequestFileJob::DidSeek(int64 result) { |
Ben Murdoch | a02191e | 2014-04-16 11:17:03 +0100 | [diff] [blame] | 286 | OnSeekComplete(result); |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 287 | if (result != byte_range_.first_byte_position()) { |
| 288 | NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, |
| 289 | ERR_REQUEST_RANGE_NOT_SATISFIABLE)); |
| 290 | return; |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 291 | } |
| 292 | |
| 293 | set_expected_content_size(remaining_bytes_); |
| 294 | NotifyHeadersComplete(); |
| 295 | } |
| 296 | |
Ben Murdoch | a02191e | 2014-04-16 11:17:03 +0100 | [diff] [blame] | 297 | void URLRequestFileJob::DidRead(scoped_refptr<net::IOBuffer> buf, int result) { |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 298 | if (result > 0) { |
| 299 | SetStatus(URLRequestStatus()); // Clear the IO_PENDING status |
Torne (Richard Coles) | 010d83a | 2014-05-14 12:12:37 +0100 | [diff] [blame] | 300 | remaining_bytes_ -= result; |
| 301 | DCHECK_GE(remaining_bytes_, 0); |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 302 | } |
| 303 | |
Torne (Richard Coles) | 010d83a | 2014-05-14 12:12:37 +0100 | [diff] [blame] | 304 | OnReadComplete(buf.get(), result); |
| 305 | buf = NULL; |
| 306 | |
| 307 | if (result == 0) { |
| 308 | NotifyDone(URLRequestStatus()); |
| 309 | } else if (result < 0) { |
| 310 | NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); |
| 311 | } |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 312 | |
| 313 | NotifyReadComplete(result); |
| 314 | } |
| 315 | |
| 316 | } // namespace net |