OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 20010 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 #include "webkit/blob/blob_url_request_job.h" |
| 6 |
| 7 #include "base/file_path.h" |
| 8 #include "base/file_util.h" |
| 9 #include "base/file_util_proxy.h" |
| 10 #include "base/message_loop.h" |
| 11 #include "base/message_loop_proxy.h" |
| 12 #include "base/string_number_conversions.h" |
| 13 #include "net/base/io_buffer.h" |
| 14 #include "net/base/net_errors.h" |
| 15 #include "net/http/http_request_headers.h" |
| 16 #include "net/http/http_response_headers.h" |
| 17 #include "net/http/http_response_info.h" |
| 18 #include "net/http/http_util.h" |
| 19 #include "net/url_request/url_request.h" |
| 20 #include "net/url_request/url_request_error_job.h" |
| 21 #include "net/url_request/url_request_status.h" |
| 22 |
| 23 namespace webkit_blob { |
| 24 |
| 25 static const int kHTTPOk = 200; |
| 26 static const int kHTTPPartialContent = 206; |
| 27 static const int kHTTPNotAllowed = 403; |
| 28 static const int kHTTPNotFound = 404; |
| 29 static const int kHTTPMethodNotAllow = 405; |
| 30 static const int kHTTPRequestedRangeNotSatisfiable = 416; |
| 31 static const int kHTTPInternalError = 500; |
| 32 |
| 33 static const char* kHTTPOKText = "OK"; |
| 34 static const char* kHTTPPartialContentText = "Partial Content"; |
| 35 static const char* kHTTPNotAllowedText = "Not Allowed"; |
| 36 static const char* kHTTPNotFoundText = "Not Found"; |
| 37 static const char* kHTTPMethodNotAllowText = "Method Not Allowed"; |
| 38 static const char* kHTTPRequestedRangeNotSatisfiableText = |
| 39 "Requested Range Not Satisfiable"; |
| 40 static const char* kHTTPInternalErrorText = "Internal Server Error"; |
| 41 |
| 42 BlobURLRequestJob::BlobURLRequestJob( |
| 43 URLRequest* request, |
| 44 BlobData* blob_data, |
| 45 base::MessageLoopProxy* file_thread_proxy) |
| 46 : URLRequestJob(request), |
| 47 callback_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| 48 blob_data_(blob_data), |
| 49 file_thread_proxy_(file_thread_proxy), |
| 50 ALLOW_THIS_IN_INITIALIZER_LIST( |
| 51 io_callback_(this, &BlobURLRequestJob::DidRead)), |
| 52 item_index_(0), |
| 53 total_size_(0), |
| 54 current_item_offset_(0), |
| 55 remaining_bytes_(0), |
| 56 read_buf_offset_(0), |
| 57 read_buf_size_(0), |
| 58 read_buf_remaining_bytes_(0), |
| 59 error_(false), |
| 60 headers_set_(false), |
| 61 byte_range_set_(false) { |
| 62 } |
| 63 |
| 64 BlobURLRequestJob::~BlobURLRequestJob() { |
| 65 } |
| 66 |
| 67 void BlobURLRequestJob::Start() { |
| 68 // We only support GET request per the spec. |
| 69 if (request()->method() != "GET") { |
| 70 NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED); |
| 71 return; |
| 72 } |
| 73 |
| 74 // If the blob data is not present, bail out. |
| 75 if (!blob_data_) { |
| 76 NotifyFailure(net::ERR_FILE_NOT_FOUND); |
| 77 return; |
| 78 } |
| 79 |
| 80 // Start counting the size. |
| 81 CountSize(); |
| 82 } |
| 83 |
| 84 void BlobURLRequestJob::Kill() { |
| 85 stream_.Close(); |
| 86 |
| 87 URLRequestJob::Kill(); |
| 88 } |
| 89 |
| 90 void BlobURLRequestJob::ResolveFile(const FilePath& file_path) { |
| 91 // If the file thread proxy is provided, we can use it get the file info. |
| 92 if (file_thread_proxy_) { |
| 93 base::FileUtilProxy::GetFileInfo( |
| 94 file_thread_proxy_, |
| 95 file_path, |
| 96 callback_factory_.NewCallback(&BlobURLRequestJob::DidResolve)); |
| 97 return; |
| 98 } |
| 99 |
| 100 // Otherwise, we use current thread, i.e. IO thread, as this is the case when |
| 101 // we run the unittest or test shell. |
| 102 // TODO(jianli): Consider using the proxy of current thread. |
| 103 file_util::FileInfo file_info; |
| 104 bool exists = file_util::GetFileInfo(file_path, &file_info); |
| 105 |
| 106 // Continue asynchronously. |
| 107 MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( |
| 108 this, &BlobURLRequestJob::DidResolve, exists, file_info)); |
| 109 } |
| 110 |
| 111 void BlobURLRequestJob::DidResolve( |
| 112 bool exists, const file_util::FileInfo& file_info) { |
| 113 // We may have been orphaned... |
| 114 if (!request_) |
| 115 return; |
| 116 |
| 117 // If the file does not exist, bail out. |
| 118 if (!exists) { |
| 119 NotifyFailure(net::ERR_FILE_NOT_FOUND); |
| 120 return; |
| 121 } |
| 122 |
| 123 // Validate the expected modification time. |
| 124 // Note that the expected modification time from WebKit is based on |
| 125 // time_t precision. So we have to convert both to time_t to compare. |
| 126 const BlobData::Item& item = blob_data_->items().at(item_index_); |
| 127 DCHECK(item.type() == BlobData::TYPE_FILE); |
| 128 |
| 129 if (!item.expected_modification_time().is_null() && |
| 130 item.expected_modification_time().ToTimeT() != |
| 131 file_info.last_modified.ToTimeT()) { |
| 132 NotifyFailure(net::ERR_FILE_NOT_FOUND); |
| 133 return; |
| 134 } |
| 135 |
| 136 // If item length is -1, we need to use the file size being resolved |
| 137 // in the real time. |
| 138 int64 item_length = static_cast<int64>(item.length()); |
| 139 if (item_length == -1) |
| 140 item_length = file_info.size; |
| 141 |
| 142 // Cache the size and add it to the total size. |
| 143 item_length_list_.push_back(item_length); |
| 144 total_size_ += item_length; |
| 145 |
| 146 // Continue counting the size for the remaining items. |
| 147 item_index_++; |
| 148 CountSize(); |
| 149 } |
| 150 |
| 151 void BlobURLRequestJob::CountSize() { |
| 152 for (; item_index_ < blob_data_->items().size(); ++item_index_) { |
| 153 const BlobData::Item& item = blob_data_->items().at(item_index_); |
| 154 int64 item_length = static_cast<int64>(item.length()); |
| 155 |
| 156 // If there is a file item, do the resolving. |
| 157 if (item.type() == BlobData::TYPE_FILE) { |
| 158 ResolveFile(item.file_path()); |
| 159 return; |
| 160 } |
| 161 |
| 162 // Cache the size and add it to the total size. |
| 163 item_length_list_.push_back(item_length); |
| 164 total_size_ += item_length; |
| 165 } |
| 166 |
| 167 // Reset item_index_ since it will be reused to read the items. |
| 168 item_index_ = 0; |
| 169 |
| 170 // Apply the range requirement. |
| 171 if (!byte_range_.ComputeBounds(total_size_)) { |
| 172 NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); |
| 173 return; |
| 174 } |
| 175 |
| 176 remaining_bytes_ = byte_range_.last_byte_position() - |
| 177 byte_range_.first_byte_position() + 1; |
| 178 DCHECK_GE(remaining_bytes_, 0); |
| 179 |
| 180 // Do the seek at the beginning of the request. |
| 181 if (byte_range_.first_byte_position()) |
| 182 Seek(byte_range_.first_byte_position()); |
| 183 |
| 184 NotifySuccess(); |
| 185 } |
| 186 |
| 187 void BlobURLRequestJob::Seek(int64 offset) { |
| 188 // Skip the initial items that are not in the range. |
| 189 for (item_index_ = 0; |
| 190 item_index_ < blob_data_->items().size() && |
| 191 offset >= item_length_list_[item_index_]; |
| 192 ++item_index_) { |
| 193 offset -= item_length_list_[item_index_]; |
| 194 } |
| 195 |
| 196 // Set the offset that need to jump to for the first item in the range. |
| 197 current_item_offset_ = offset; |
| 198 } |
| 199 |
| 200 bool BlobURLRequestJob::ReadRawData(net::IOBuffer* dest, |
| 201 int dest_size, |
| 202 int* bytes_read) { |
| 203 DCHECK_NE(dest_size, 0); |
| 204 DCHECK(bytes_read); |
| 205 DCHECK_GE(remaining_bytes_, 0); |
| 206 |
| 207 // Bail out immediately if we encounter an error. |
| 208 if (error_) { |
| 209 *bytes_read = 0; |
| 210 return true; |
| 211 } |
| 212 |
| 213 if (remaining_bytes_ < dest_size) |
| 214 dest_size = static_cast<int>(remaining_bytes_); |
| 215 |
| 216 // If we should copy zero bytes because |remaining_bytes_| is zero, short |
| 217 // circuit here. |
| 218 if (!dest_size) { |
| 219 *bytes_read = 0; |
| 220 return true; |
| 221 } |
| 222 |
| 223 // Keep track of the buffer. |
| 224 DCHECK(!read_buf_); |
| 225 read_buf_ = dest; |
| 226 read_buf_offset_ = 0; |
| 227 read_buf_size_ = dest_size; |
| 228 read_buf_remaining_bytes_ = dest_size; |
| 229 |
| 230 return ReadLoop(bytes_read); |
| 231 } |
| 232 |
| 233 bool BlobURLRequestJob::ReadLoop(int* bytes_read) { |
| 234 // Read until we encounter an error or could not get the data immediately. |
| 235 while (remaining_bytes_ > 0 && read_buf_remaining_bytes_ > 0) { |
| 236 if (!ReadItem()) |
| 237 return false; |
| 238 } |
| 239 |
| 240 *bytes_read = ReadCompleted(); |
| 241 return true; |
| 242 } |
| 243 |
| 244 bool BlobURLRequestJob::ReadItem() { |
| 245 // Are we done with reading all the blob data? |
| 246 if (remaining_bytes_ == 0) |
| 247 return true; |
| 248 |
| 249 // If we get to the last item but still expect something to read, bail out |
| 250 // since something is wrong. |
| 251 if (item_index_ >= blob_data_->items().size()) { |
| 252 NotifyFailure(net::ERR_FAILED); |
| 253 return false; |
| 254 } |
| 255 |
| 256 const BlobData::Item& item = blob_data_->items().at(item_index_); |
| 257 |
| 258 // Compute the bytes to read for current item. |
| 259 int64 current_item_remaining_bytes = |
| 260 item_length_list_[item_index_] - current_item_offset_; |
| 261 int bytes_to_read = (read_buf_remaining_bytes_ > current_item_remaining_bytes) |
| 262 ? static_cast<int>(current_item_remaining_bytes) |
| 263 : read_buf_remaining_bytes_; |
| 264 if (bytes_to_read > remaining_bytes_) |
| 265 bytes_to_read = static_cast<int>(remaining_bytes_); |
| 266 |
| 267 // If nothing to read for current item, advance to next item. |
| 268 if (bytes_to_read == 0) { |
| 269 AdvanceItem(); |
| 270 return ReadItem(); |
| 271 } |
| 272 |
| 273 // Do the reading. |
| 274 switch (item.type()) { |
| 275 case BlobData::TYPE_DATA: |
| 276 return ReadBytes(item, bytes_to_read); |
| 277 case BlobData::TYPE_FILE: |
| 278 return ReadFile(item, bytes_to_read); |
| 279 default: |
| 280 DCHECK(false); |
| 281 return false; |
| 282 } |
| 283 } |
| 284 |
| 285 bool BlobURLRequestJob::ReadBytes(const BlobData::Item& item, |
| 286 int bytes_to_read) { |
| 287 DCHECK(read_buf_remaining_bytes_ >= bytes_to_read); |
| 288 |
| 289 memcpy(read_buf_->data() + read_buf_offset_, |
| 290 &item.data().at(0) + item.offset() + current_item_offset_, |
| 291 bytes_to_read); |
| 292 |
| 293 AdvanceBytesRead(bytes_to_read); |
| 294 return true; |
| 295 } |
| 296 |
| 297 bool BlobURLRequestJob::ReadFile(const BlobData::Item& item, |
| 298 int bytes_to_read) { |
| 299 DCHECK(read_buf_remaining_bytes_ >= bytes_to_read); |
| 300 |
| 301 // Open the file if not yet. |
| 302 if (!stream_.IsOpen()) { |
| 303 int rv = stream_.Open(item.file_path(), base::PLATFORM_FILE_OPEN | |
| 304 base::PLATFORM_FILE_READ | base::PLATFORM_FILE_ASYNC); |
| 305 if (rv != net::OK) { |
| 306 NotifyFailure(net::ERR_FAILED); |
| 307 return false; |
| 308 } |
| 309 |
| 310 // Seek the file if needed. |
| 311 int64 offset = current_item_offset_ + static_cast<int64>(item.offset()); |
| 312 if (offset > 0) { |
| 313 if (offset != stream_.Seek(net::FROM_BEGIN, offset)) { |
| 314 NotifyFailure(net::ERR_FAILED); |
| 315 return false; |
| 316 } |
| 317 } |
| 318 } |
| 319 |
| 320 // Start the asynchronous reading. |
| 321 int rv = stream_.Read(read_buf_->data() + read_buf_offset_, |
| 322 bytes_to_read, |
| 323 &io_callback_); |
| 324 |
| 325 // If I/O pending error is returned, we just need to wait. |
| 326 if (rv == net::ERR_IO_PENDING) { |
| 327 SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); |
| 328 return false; |
| 329 } |
| 330 |
| 331 // For all other errors, bail out. |
| 332 if (rv < 0) { |
| 333 NotifyFailure(net::ERR_FAILED); |
| 334 return false; |
| 335 } |
| 336 |
| 337 // Otherwise, data is immediately available. |
| 338 AdvanceBytesRead(rv); |
| 339 return true; |
| 340 } |
| 341 |
| 342 void BlobURLRequestJob::DidRead(int result) { |
| 343 if (result >= 0) { |
| 344 SetStatus(URLRequestStatus()); // Clear the IO_PENDING status |
| 345 } else if (result < 0) { |
| 346 NotifyFailure(net::ERR_FAILED); |
| 347 return; |
| 348 } |
| 349 |
| 350 AdvanceBytesRead(result); |
| 351 |
| 352 // If the read buffer is completely filled, we're done. |
| 353 if (!read_buf_remaining_bytes_) { |
| 354 int bytes_read = ReadCompleted(); |
| 355 NotifyReadComplete(bytes_read); |
| 356 return; |
| 357 } |
| 358 |
| 359 // Otherwise, continue the reading. |
| 360 int bytes_read = 0; |
| 361 if (ReadLoop(&bytes_read)) |
| 362 NotifyReadComplete(bytes_read); |
| 363 } |
| 364 |
| 365 void BlobURLRequestJob::AdvanceItem() { |
| 366 // Close the stream if the current item is a file. |
| 367 if (stream_.IsOpen()) |
| 368 stream_.Close(); |
| 369 |
| 370 // Advance to the next item. |
| 371 item_index_++; |
| 372 current_item_offset_ = 0; |
| 373 } |
| 374 |
| 375 void BlobURLRequestJob::AdvanceBytesRead(int result) { |
| 376 DCHECK_GT(result, 0); |
| 377 |
| 378 // Do we finish reading the current item? |
| 379 current_item_offset_ += result; |
| 380 if (current_item_offset_ == item_length_list_[item_index_]) |
| 381 AdvanceItem(); |
| 382 |
| 383 // Subtract the remaining bytes. |
| 384 remaining_bytes_ -= result; |
| 385 DCHECK_GE(remaining_bytes_, 0); |
| 386 |
| 387 // Adjust the read buffer. |
| 388 read_buf_offset_ += result; |
| 389 read_buf_remaining_bytes_ -= result; |
| 390 DCHECK_GE(read_buf_remaining_bytes_, 0); |
| 391 } |
| 392 |
| 393 int BlobURLRequestJob::ReadCompleted() { |
| 394 int bytes_read = read_buf_size_ - read_buf_remaining_bytes_; |
| 395 read_buf_ = NULL; |
| 396 read_buf_offset_ = 0; |
| 397 read_buf_size_ = 0; |
| 398 read_buf_remaining_bytes_ = 0; |
| 399 return bytes_read; |
| 400 } |
| 401 |
| 402 void BlobURLRequestJob::HeadersCompleted(int status_code, |
| 403 const std::string& status_text) { |
| 404 std::string status("HTTP/1.1 "); |
| 405 status.append(base::IntToString(status_code)); |
| 406 status.append(" "); |
| 407 status.append(status_text); |
| 408 status.append("\0\0", 2); |
| 409 net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); |
| 410 |
| 411 if (status_code == kHTTPOk || status_code == kHTTPPartialContent) { |
| 412 std::string content_length_header(net::HttpRequestHeaders::kContentLength); |
| 413 content_length_header.append(": "); |
| 414 content_length_header.append(base::Int64ToString(remaining_bytes_)); |
| 415 headers->AddHeader(content_length_header); |
| 416 if (!blob_data_->content_type().empty()) { |
| 417 std::string content_type_header(net::HttpRequestHeaders::kContentType); |
| 418 content_type_header.append(": "); |
| 419 content_type_header.append(blob_data_->content_type()); |
| 420 headers->AddHeader(content_type_header); |
| 421 } |
| 422 if (!blob_data_->content_disposition().empty()) { |
| 423 std::string content_disposition_header("Content-Disposition: "); |
| 424 content_disposition_header.append(blob_data_->content_disposition()); |
| 425 headers->AddHeader(content_disposition_header); |
| 426 } |
| 427 } |
| 428 |
| 429 response_info_.reset(new net::HttpResponseInfo()); |
| 430 response_info_->headers = headers; |
| 431 |
| 432 set_expected_content_size(remaining_bytes_); |
| 433 NotifyHeadersComplete(); |
| 434 |
| 435 headers_set_ = true; |
| 436 } |
| 437 |
| 438 void BlobURLRequestJob::NotifySuccess() { |
| 439 int status_code = 0; |
| 440 std::string status_text; |
| 441 if (byte_range_set_ && byte_range_.IsValid()) { |
| 442 status_code = kHTTPPartialContent; |
| 443 status_text += kHTTPPartialContentText; |
| 444 } else { |
| 445 status_code = kHTTPOk; |
| 446 status_text = kHTTPOKText; |
| 447 } |
| 448 HeadersCompleted(status_code, status_text); |
| 449 } |
| 450 |
| 451 void BlobURLRequestJob::NotifyFailure(int error_code) { |
| 452 error_ = true; |
| 453 |
| 454 // If we already return the headers on success, we can't change the headers |
| 455 // now. Instead, we just error out. |
| 456 if (headers_set_) { |
| 457 NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, error_code)); |
| 458 return; |
| 459 } |
| 460 |
| 461 int status_code = 0; |
| 462 std::string status_txt; |
| 463 switch (error_code) { |
| 464 case net::ERR_ACCESS_DENIED: |
| 465 status_code = kHTTPNotAllowed; |
| 466 status_txt = kHTTPNotAllowedText; |
| 467 break; |
| 468 case net::ERR_FILE_NOT_FOUND: |
| 469 status_code = kHTTPNotFound; |
| 470 status_txt = kHTTPNotFoundText; |
| 471 break; |
| 472 case net::ERR_METHOD_NOT_SUPPORTED: |
| 473 status_code = kHTTPMethodNotAllow; |
| 474 status_txt = kHTTPMethodNotAllowText; |
| 475 break; |
| 476 case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE: |
| 477 status_code = kHTTPRequestedRangeNotSatisfiable; |
| 478 status_txt = kHTTPRequestedRangeNotSatisfiableText; |
| 479 break; |
| 480 case net::ERR_FAILED: |
| 481 status_code = kHTTPInternalError; |
| 482 status_txt = kHTTPInternalErrorText; |
| 483 break; |
| 484 default: |
| 485 DCHECK(false); |
| 486 status_code = kHTTPInternalError; |
| 487 status_txt = kHTTPInternalErrorText; |
| 488 break; |
| 489 } |
| 490 HeadersCompleted(status_code, status_txt); |
| 491 } |
| 492 |
| 493 bool BlobURLRequestJob::GetMimeType(std::string* mime_type) const { |
| 494 if (!response_info_.get()) |
| 495 return false; |
| 496 |
| 497 return response_info_->headers->GetMimeType(mime_type); |
| 498 } |
| 499 |
| 500 void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { |
| 501 if (response_info_.get()) |
| 502 *info = *response_info_; |
| 503 } |
| 504 |
| 505 int BlobURLRequestJob::GetResponseCode() const { |
| 506 if (!response_info_.get()) |
| 507 return -1; |
| 508 |
| 509 return response_info_->headers->response_code(); |
| 510 } |
| 511 |
| 512 void BlobURLRequestJob::SetExtraRequestHeaders( |
| 513 const net::HttpRequestHeaders& headers) { |
| 514 std::string range_header; |
| 515 if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { |
| 516 // We only care about "Range" header here. |
| 517 std::vector<net::HttpByteRange> ranges; |
| 518 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) { |
| 519 if (ranges.size() == 1) { |
| 520 byte_range_set_ = true; |
| 521 byte_range_ = ranges[0]; |
| 522 } else { |
| 523 // We don't support multiple range requests in one single URL request, |
| 524 // because we need to do multipart encoding here. |
| 525 // TODO(jianli): Support multipart byte range requests. |
| 526 NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE); |
| 527 } |
| 528 } |
| 529 } |
| 530 } |
| 531 |
| 532 } // namespace webkit_blob |
OLD | NEW |