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: 47 explicit AsyncResolver(URLRequestFileJob* owner) 48 : owner_(owner), owner_loop_(MessageLoop::current()) { 49 } 50 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 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 71 ~AsyncResolver() {} 72 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 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 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 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 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 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 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 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 266 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 272 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 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 302 URLRequestFileJob::~URLRequestFileJob() { 303 #if defined(OS_WIN) 304 DCHECK(!async_resolver_); 305 #endif 306 } 307 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 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