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 #include "webkit/browser/blob/blob_url_request_job.h"
6
7 #include <limits>
8
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/compiler_specific.h"
12 #include "base/files/file_util_proxy.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/message_loop/message_loop_proxy.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "net/base/io_buffer.h"
18 #include "net/base/net_errors.h"
19 #include "net/http/http_request_headers.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/http/http_response_info.h"
22 #include "net/http/http_util.h"
23 #include "net/url_request/url_request.h"
24 #include "net/url_request/url_request_context.h"
25 #include "net/url_request/url_request_error_job.h"
26 #include "net/url_request/url_request_status.h"
27 #include "webkit/browser/blob/file_stream_reader.h"
28 #include "webkit/browser/fileapi/file_system_context.h"
29 #include "webkit/browser/fileapi/file_system_url.h"
30
31 namespace webkit_blob {
32
33 namespace {
34
IsFileType(BlobData::Item::Type type)35 bool IsFileType(BlobData::Item::Type type) {
36 switch (type) {
37 case BlobData::Item::TYPE_FILE:
38 case BlobData::Item::TYPE_FILE_FILESYSTEM:
39 return true;
40 default:
41 return false;
42 }
43 }
44
45 } // namespace
46
BlobURLRequestJob(net::URLRequest * request,net::NetworkDelegate * network_delegate,BlobData * blob_data,fileapi::FileSystemContext * file_system_context,base::MessageLoopProxy * file_thread_proxy)47 BlobURLRequestJob::BlobURLRequestJob(
48 net::URLRequest* request,
49 net::NetworkDelegate* network_delegate,
50 BlobData* blob_data,
51 fileapi::FileSystemContext* file_system_context,
52 base::MessageLoopProxy* file_thread_proxy)
53 : net::URLRequestJob(request, network_delegate),
54 blob_data_(blob_data),
55 file_system_context_(file_system_context),
56 file_thread_proxy_(file_thread_proxy),
57 total_size_(0),
58 remaining_bytes_(0),
59 pending_get_file_info_count_(0),
60 current_item_index_(0),
61 current_item_offset_(0),
62 error_(false),
63 byte_range_set_(false),
64 weak_factory_(this) {
65 DCHECK(file_thread_proxy_.get());
66 }
67
Start()68 void BlobURLRequestJob::Start() {
69 // Continue asynchronously.
70 base::MessageLoop::current()->PostTask(
71 FROM_HERE,
72 base::Bind(&BlobURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
73 }
74
Kill()75 void BlobURLRequestJob::Kill() {
76 DeleteCurrentFileReader();
77
78 net::URLRequestJob::Kill();
79 weak_factory_.InvalidateWeakPtrs();
80 }
81
ReadRawData(net::IOBuffer * dest,int dest_size,int * bytes_read)82 bool BlobURLRequestJob::ReadRawData(net::IOBuffer* dest,
83 int dest_size,
84 int* bytes_read) {
85 DCHECK_NE(dest_size, 0);
86 DCHECK(bytes_read);
87 DCHECK_GE(remaining_bytes_, 0);
88
89 // Bail out immediately if we encounter an error.
90 if (error_) {
91 *bytes_read = 0;
92 return true;
93 }
94
95 if (remaining_bytes_ < dest_size)
96 dest_size = static_cast<int>(remaining_bytes_);
97
98 // If we should copy zero bytes because |remaining_bytes_| is zero, short
99 // circuit here.
100 if (!dest_size) {
101 *bytes_read = 0;
102 return true;
103 }
104
105 // Keep track of the buffer.
106 DCHECK(!read_buf_.get());
107 read_buf_ = new net::DrainableIOBuffer(dest, dest_size);
108
109 return ReadLoop(bytes_read);
110 }
111
GetMimeType(std::string * mime_type) const112 bool BlobURLRequestJob::GetMimeType(std::string* mime_type) const {
113 if (!response_info_)
114 return false;
115
116 return response_info_->headers->GetMimeType(mime_type);
117 }
118
GetResponseInfo(net::HttpResponseInfo * info)119 void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
120 if (response_info_)
121 *info = *response_info_;
122 }
123
GetResponseCode() const124 int BlobURLRequestJob::GetResponseCode() const {
125 if (!response_info_)
126 return -1;
127
128 return response_info_->headers->response_code();
129 }
130
SetExtraRequestHeaders(const net::HttpRequestHeaders & headers)131 void BlobURLRequestJob::SetExtraRequestHeaders(
132 const net::HttpRequestHeaders& headers) {
133 std::string range_header;
134 if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
135 // We only care about "Range" header here.
136 std::vector<net::HttpByteRange> ranges;
137 if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
138 if (ranges.size() == 1) {
139 byte_range_set_ = true;
140 byte_range_ = ranges[0];
141 } else {
142 // We don't support multiple range requests in one single URL request,
143 // because we need to do multipart encoding here.
144 // TODO(jianli): Support multipart byte range requests.
145 NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
146 }
147 }
148 }
149 }
150
~BlobURLRequestJob()151 BlobURLRequestJob::~BlobURLRequestJob() {
152 STLDeleteValues(&index_to_reader_);
153 }
154
DidStart()155 void BlobURLRequestJob::DidStart() {
156 error_ = false;
157
158 // We only support GET request per the spec.
159 if (request()->method() != "GET") {
160 NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
161 return;
162 }
163
164 // If the blob data is not present, bail out.
165 if (!blob_data_.get()) {
166 NotifyFailure(net::ERR_FILE_NOT_FOUND);
167 return;
168 }
169
170 CountSize();
171 }
172
AddItemLength(size_t index,int64 item_length)173 bool BlobURLRequestJob::AddItemLength(size_t index, int64 item_length) {
174 if (item_length > kint64max - total_size_) {
175 NotifyFailure(net::ERR_FAILED);
176 return false;
177 }
178
179 // Cache the size and add it to the total size.
180 DCHECK_LT(index, item_length_list_.size());
181 item_length_list_[index] = item_length;
182 total_size_ += item_length;
183 return true;
184 }
185
CountSize()186 void BlobURLRequestJob::CountSize() {
187 pending_get_file_info_count_ = 0;
188 total_size_ = 0;
189 item_length_list_.resize(blob_data_->items().size());
190
191 for (size_t i = 0; i < blob_data_->items().size(); ++i) {
192 const BlobData::Item& item = blob_data_->items().at(i);
193 if (IsFileType(item.type())) {
194 ++pending_get_file_info_count_;
195 GetFileStreamReader(i)->GetLength(
196 base::Bind(&BlobURLRequestJob::DidGetFileItemLength,
197 weak_factory_.GetWeakPtr(), i));
198 continue;
199 }
200
201 if (!AddItemLength(i, item.length()))
202 return;
203 }
204
205 if (pending_get_file_info_count_ == 0)
206 DidCountSize(net::OK);
207 }
208
DidCountSize(int error)209 void BlobURLRequestJob::DidCountSize(int error) {
210 DCHECK(!error_);
211
212 // If an error occured, bail out.
213 if (error != net::OK) {
214 NotifyFailure(error);
215 return;
216 }
217
218 // Apply the range requirement.
219 if (!byte_range_.ComputeBounds(total_size_)) {
220 NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
221 return;
222 }
223
224 remaining_bytes_ = byte_range_.last_byte_position() -
225 byte_range_.first_byte_position() + 1;
226 DCHECK_GE(remaining_bytes_, 0);
227
228 // Do the seek at the beginning of the request.
229 if (byte_range_.first_byte_position())
230 Seek(byte_range_.first_byte_position());
231
232 NotifySuccess();
233 }
234
DidGetFileItemLength(size_t index,int64 result)235 void BlobURLRequestJob::DidGetFileItemLength(size_t index, int64 result) {
236 // Do nothing if we have encountered an error.
237 if (error_)
238 return;
239
240 if (result == net::ERR_UPLOAD_FILE_CHANGED) {
241 NotifyFailure(net::ERR_FILE_NOT_FOUND);
242 return;
243 } else if (result < 0) {
244 NotifyFailure(result);
245 return;
246 }
247
248 DCHECK_LT(index, blob_data_->items().size());
249 const BlobData::Item& item = blob_data_->items().at(index);
250 DCHECK(IsFileType(item.type()));
251
252 uint64 file_length = result;
253 uint64 item_offset = item.offset();
254 uint64 item_length = item.length();
255
256 if (item_offset > file_length) {
257 NotifyFailure(net::ERR_FILE_NOT_FOUND);
258 return;
259 }
260
261 uint64 max_length = file_length - item_offset;
262
263 // If item length is -1, we need to use the file size being resolved
264 // in the real time.
265 if (item_length == static_cast<uint64>(-1)) {
266 item_length = max_length;
267 } else if (item_length > max_length) {
268 NotifyFailure(net::ERR_FILE_NOT_FOUND);
269 return;
270 }
271
272 if (!AddItemLength(index, item_length))
273 return;
274
275 if (--pending_get_file_info_count_ == 0)
276 DidCountSize(net::OK);
277 }
278
Seek(int64 offset)279 void BlobURLRequestJob::Seek(int64 offset) {
280 // Skip the initial items that are not in the range.
281 for (current_item_index_ = 0;
282 current_item_index_ < blob_data_->items().size() &&
283 offset >= item_length_list_[current_item_index_];
284 ++current_item_index_) {
285 offset -= item_length_list_[current_item_index_];
286 }
287
288 // Set the offset that need to jump to for the first item in the range.
289 current_item_offset_ = offset;
290
291 if (offset == 0)
292 return;
293
294 // Adjust the offset of the first stream if it is of file type.
295 const BlobData::Item& item = blob_data_->items().at(current_item_index_);
296 if (IsFileType(item.type())) {
297 DeleteCurrentFileReader();
298 CreateFileStreamReader(current_item_index_, offset);
299 }
300 }
301
ReadItem()302 bool BlobURLRequestJob::ReadItem() {
303 // Are we done with reading all the blob data?
304 if (remaining_bytes_ == 0)
305 return true;
306
307 // If we get to the last item but still expect something to read, bail out
308 // since something is wrong.
309 if (current_item_index_ >= blob_data_->items().size()) {
310 NotifyFailure(net::ERR_FAILED);
311 return false;
312 }
313
314 // Compute the bytes to read for current item.
315 int bytes_to_read = ComputeBytesToRead();
316
317 // If nothing to read for current item, advance to next item.
318 if (bytes_to_read == 0) {
319 AdvanceItem();
320 return ReadItem();
321 }
322
323 // Do the reading.
324 const BlobData::Item& item = blob_data_->items().at(current_item_index_);
325 if (item.type() == BlobData::Item::TYPE_BYTES)
326 return ReadBytesItem(item, bytes_to_read);
327 if (IsFileType(item.type())) {
328 return ReadFileItem(GetFileStreamReader(current_item_index_),
329 bytes_to_read);
330 }
331 NOTREACHED();
332 return false;
333 }
334
AdvanceItem()335 void BlobURLRequestJob::AdvanceItem() {
336 // Close the file if the current item is a file.
337 DeleteCurrentFileReader();
338
339 // Advance to the next item.
340 current_item_index_++;
341 current_item_offset_ = 0;
342 }
343
AdvanceBytesRead(int result)344 void BlobURLRequestJob::AdvanceBytesRead(int result) {
345 DCHECK_GT(result, 0);
346
347 // Do we finish reading the current item?
348 current_item_offset_ += result;
349 if (current_item_offset_ == item_length_list_[current_item_index_])
350 AdvanceItem();
351
352 // Subtract the remaining bytes.
353 remaining_bytes_ -= result;
354 DCHECK_GE(remaining_bytes_, 0);
355
356 // Adjust the read buffer.
357 read_buf_->DidConsume(result);
358 DCHECK_GE(read_buf_->BytesRemaining(), 0);
359 }
360
ReadBytesItem(const BlobData::Item & item,int bytes_to_read)361 bool BlobURLRequestJob::ReadBytesItem(const BlobData::Item& item,
362 int bytes_to_read) {
363 DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
364
365 memcpy(read_buf_->data(),
366 item.bytes() + item.offset() + current_item_offset_,
367 bytes_to_read);
368
369 AdvanceBytesRead(bytes_to_read);
370 return true;
371 }
372
ReadFileItem(FileStreamReader * reader,int bytes_to_read)373 bool BlobURLRequestJob::ReadFileItem(FileStreamReader* reader,
374 int bytes_to_read) {
375 DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
376 DCHECK(reader);
377 const int result = reader->Read(
378 read_buf_.get(),
379 bytes_to_read,
380 base::Bind(&BlobURLRequestJob::DidReadFile, base::Unretained(this)));
381 if (result >= 0) {
382 // Data is immediately available.
383 if (GetStatus().is_io_pending())
384 DidReadFile(result);
385 else
386 AdvanceBytesRead(result);
387 return true;
388 }
389 if (result == net::ERR_IO_PENDING)
390 SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
391 else
392 NotifyFailure(result);
393 return false;
394 }
395
DidReadFile(int result)396 void BlobURLRequestJob::DidReadFile(int result) {
397 if (result <= 0) {
398 NotifyFailure(net::ERR_FAILED);
399 return;
400 }
401 SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
402
403 AdvanceBytesRead(result);
404
405 // If the read buffer is completely filled, we're done.
406 if (!read_buf_->BytesRemaining()) {
407 int bytes_read = BytesReadCompleted();
408 NotifyReadComplete(bytes_read);
409 return;
410 }
411
412 // Otherwise, continue the reading.
413 int bytes_read = 0;
414 if (ReadLoop(&bytes_read))
415 NotifyReadComplete(bytes_read);
416 }
417
DeleteCurrentFileReader()418 void BlobURLRequestJob::DeleteCurrentFileReader() {
419 IndexToReaderMap::iterator found = index_to_reader_.find(current_item_index_);
420 if (found != index_to_reader_.end() && found->second) {
421 delete found->second;
422 index_to_reader_.erase(found);
423 }
424 }
425
BytesReadCompleted()426 int BlobURLRequestJob::BytesReadCompleted() {
427 int bytes_read = read_buf_->BytesConsumed();
428 read_buf_ = NULL;
429 return bytes_read;
430 }
431
ComputeBytesToRead() const432 int BlobURLRequestJob::ComputeBytesToRead() const {
433 int64 current_item_length = item_length_list_[current_item_index_];
434
435 int64 item_remaining = current_item_length - current_item_offset_;
436 int64 buf_remaining = read_buf_->BytesRemaining();
437 int64 max_remaining = std::numeric_limits<int>::max();
438
439 int64 min = std::min(std::min(std::min(item_remaining,
440 buf_remaining),
441 remaining_bytes_),
442 max_remaining);
443
444 return static_cast<int>(min);
445 }
446
ReadLoop(int * bytes_read)447 bool BlobURLRequestJob::ReadLoop(int* bytes_read) {
448 // Read until we encounter an error or could not get the data immediately.
449 while (remaining_bytes_ > 0 && read_buf_->BytesRemaining() > 0) {
450 if (!ReadItem())
451 return false;
452 }
453
454 *bytes_read = BytesReadCompleted();
455 return true;
456 }
457
NotifySuccess()458 void BlobURLRequestJob::NotifySuccess() {
459 net::HttpStatusCode status_code = net::HTTP_OK;
460 if (byte_range_set_ && byte_range_.IsValid())
461 status_code = net::HTTP_PARTIAL_CONTENT;
462 HeadersCompleted(status_code);
463 }
464
NotifyFailure(int error_code)465 void BlobURLRequestJob::NotifyFailure(int error_code) {
466 error_ = true;
467
468 // If we already return the headers on success, we can't change the headers
469 // now. Instead, we just error out.
470 if (response_info_) {
471 NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
472 error_code));
473 return;
474 }
475
476 net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
477 switch (error_code) {
478 case net::ERR_ACCESS_DENIED:
479 status_code = net::HTTP_FORBIDDEN;
480 break;
481 case net::ERR_FILE_NOT_FOUND:
482 status_code = net::HTTP_NOT_FOUND;
483 break;
484 case net::ERR_METHOD_NOT_SUPPORTED:
485 status_code = net::HTTP_METHOD_NOT_ALLOWED;
486 break;
487 case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
488 status_code = net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE;
489 break;
490 case net::ERR_FAILED:
491 break;
492 default:
493 DCHECK(false);
494 break;
495 }
496 HeadersCompleted(status_code);
497 }
498
HeadersCompleted(net::HttpStatusCode status_code)499 void BlobURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
500 std::string status("HTTP/1.1 ");
501 status.append(base::IntToString(status_code));
502 status.append(" ");
503 status.append(net::GetHttpReasonPhrase(status_code));
504 status.append("\0\0", 2);
505 net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
506
507 if (status_code == net::HTTP_OK || status_code == net::HTTP_PARTIAL_CONTENT) {
508 std::string content_length_header(net::HttpRequestHeaders::kContentLength);
509 content_length_header.append(": ");
510 content_length_header.append(base::Int64ToString(remaining_bytes_));
511 headers->AddHeader(content_length_header);
512 if (!blob_data_->content_type().empty()) {
513 std::string content_type_header(net::HttpRequestHeaders::kContentType);
514 content_type_header.append(": ");
515 content_type_header.append(blob_data_->content_type());
516 headers->AddHeader(content_type_header);
517 }
518 if (!blob_data_->content_disposition().empty()) {
519 std::string content_disposition_header("Content-Disposition: ");
520 content_disposition_header.append(blob_data_->content_disposition());
521 headers->AddHeader(content_disposition_header);
522 }
523 }
524
525 response_info_.reset(new net::HttpResponseInfo());
526 response_info_->headers = headers;
527
528 set_expected_content_size(remaining_bytes_);
529
530 NotifyHeadersComplete();
531 }
532
GetFileStreamReader(size_t index)533 FileStreamReader* BlobURLRequestJob::GetFileStreamReader(size_t index) {
534 DCHECK_LT(index, blob_data_->items().size());
535 const BlobData::Item& item = blob_data_->items().at(index);
536 if (!IsFileType(item.type()))
537 return NULL;
538 if (index_to_reader_.find(index) == index_to_reader_.end())
539 CreateFileStreamReader(index, 0);
540 DCHECK(index_to_reader_[index]);
541 return index_to_reader_[index];
542 }
543
CreateFileStreamReader(size_t index,int64 additional_offset)544 void BlobURLRequestJob::CreateFileStreamReader(size_t index,
545 int64 additional_offset) {
546 DCHECK_LT(index, blob_data_->items().size());
547 const BlobData::Item& item = blob_data_->items().at(index);
548 DCHECK(IsFileType(item.type()));
549 DCHECK_EQ(0U, index_to_reader_.count(index));
550
551 FileStreamReader* reader = NULL;
552 switch (item.type()) {
553 case BlobData::Item::TYPE_FILE:
554 reader = FileStreamReader::CreateForLocalFile(
555 file_thread_proxy_.get(),
556 item.path(),
557 item.offset() + additional_offset,
558 item.expected_modification_time());
559 break;
560 case BlobData::Item::TYPE_FILE_FILESYSTEM:
561 reader = file_system_context_->CreateFileStreamReader(
562 fileapi::FileSystemURL(
563 file_system_context_->CrackURL(item.filesystem_url())),
564 item.offset() + additional_offset,
565 item.expected_modification_time()).release();
566 break;
567 default:
568 NOTREACHED();
569 }
570 DCHECK(reader);
571 index_to_reader_[index] = reader;
572 }
573
574 } // namespace webkit_blob
575