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