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