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/strings/string_util.h" 27 #include "base/synchronization/lock.h" 28 #include "base/task_runner.h" 29 #include "base/threading/thread_restrictions.h" 30 #include "build/build_config.h" 31 #include "net/base/file_stream.h" 32 #include "net/base/filename_util.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/filter/filter.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 47 namespace net { 48 49 URLRequestFileJob::FileMetaInfo::FileMetaInfo() 50 : file_size(0), 51 mime_type_result(false), 52 file_exists(false), 53 is_directory(false) { 54 } 55 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) 61 : URLRequestJob(request, network_delegate), 62 file_path_(file_path), 63 stream_(new FileStream(file_task_runner)), 64 file_task_runner_(file_task_runner), 65 remaining_bytes_(0), 66 weak_ptr_factory_(this) {} 67 68 void URLRequestFileJob::Start() { 69 FileMetaInfo* meta_info = new FileMetaInfo(); 70 file_task_runner_->PostTaskAndReply( 71 FROM_HERE, 72 base::Bind(&URLRequestFileJob::FetchMetaInfo, file_path_, 73 base::Unretained(meta_info)), 74 base::Bind(&URLRequestFileJob::DidFetchMetaInfo, 75 weak_ptr_factory_.GetWeakPtr(), 76 base::Owned(meta_info))); 77 } 78 79 void URLRequestFileJob::Kill() { 80 stream_.reset(); 81 weak_ptr_factory_.InvalidateWeakPtrs(); 82 83 URLRequestJob::Kill(); 84 } 85 86 bool URLRequestFileJob::ReadRawData(IOBuffer* dest, 87 int dest_size, 88 int* bytes_read) { 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 103 int rv = stream_->Read(dest, 104 dest_size, 105 base::Bind(&URLRequestFileJob::DidRead, 106 weak_ptr_factory_.GetWeakPtr(), 107 make_scoped_refptr(dest))); 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) { 127 if (meta_info_.is_directory) { 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 146 base::FilePath new_path = file_path_; 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 { 169 DCHECK(request_); 170 if (meta_info_.mime_type_result) { 171 *mime_type = meta_info_.mime_type; 172 return true; 173 } 174 return false; 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 198 void URLRequestFileJob::OnSeekComplete(int64 result) { 199 } 200 201 void URLRequestFileJob::OnReadComplete(net::IOBuffer* buf, int result) { 202 } 203 204 URLRequestFileJob::~URLRequestFileJob() { 205 } 206 207 void URLRequestFileJob::FetchMetaInfo(const base::FilePath& file_path, 208 FileMetaInfo* meta_info) { 209 base::File::Info file_info; 210 meta_info->file_exists = base::GetFileInfo(file_path, &file_info); 211 if (meta_info->file_exists) { 212 meta_info->file_size = file_info.size; 213 meta_info->is_directory = file_info.is_directory; 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 } 220 221 void URLRequestFileJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) { 222 meta_info_ = *meta_info; 223 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. 232 if (!meta_info_.file_exists) { 233 DidOpen(ERR_FILE_NOT_FOUND); 234 return; 235 } 236 if (meta_info_.is_directory) { 237 DidOpen(OK); 238 return; 239 } 240 241 int flags = base::File::FLAG_OPEN | 242 base::File::FLAG_READ | 243 base::File::FLAG_ASYNC; 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)) { 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 267 if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) { 268 int rv = stream_->Seek(base::File::FROM_BEGIN, 269 byte_range_.first_byte_position(), 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); 276 } 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) { 286 OnSeekComplete(result); 287 if (result != byte_range_.first_byte_position()) { 288 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, 289 ERR_REQUEST_RANGE_NOT_SATISFIABLE)); 290 return; 291 } 292 293 set_expected_content_size(remaining_bytes_); 294 NotifyHeadersComplete(); 295 } 296 297 void URLRequestFileJob::DidRead(scoped_refptr<net::IOBuffer> buf, int result) { 298 if (result > 0) { 299 SetStatus(URLRequestStatus()); // Clear the IO_PENDING status 300 remaining_bytes_ -= result; 301 DCHECK_GE(remaining_bytes_, 0); 302 } 303 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 } 312 313 NotifyReadComplete(result); 314 } 315 316 } // namespace net 317