// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/appcache/appcache_response.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/pickle.h" #include "base/strings/string_util.h" #include "content/browser/appcache/appcache_storage.h" #include "net/base/completion_callback.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" namespace content { namespace { // Disk cache entry data indices. enum { kResponseInfoIndex, kResponseContentIndex }; // An IOBuffer that wraps a pickle's data. Ownership of the // pickle is transfered to the WrappedPickleIOBuffer object. class WrappedPickleIOBuffer : public net::WrappedIOBuffer { public: explicit WrappedPickleIOBuffer(const Pickle* pickle) : net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data())), pickle_(pickle) { DCHECK(pickle->data()); } private: virtual ~WrappedPickleIOBuffer() {} scoped_ptr<const Pickle> pickle_; }; } // anon namespace // AppCacheResponseInfo ---------------------------------------------- AppCacheResponseInfo::AppCacheResponseInfo( AppCacheStorage* storage, const GURL& manifest_url, int64 response_id, net::HttpResponseInfo* http_info, int64 response_data_size) : manifest_url_(manifest_url), response_id_(response_id), http_response_info_(http_info), response_data_size_(response_data_size), storage_(storage) { DCHECK(http_info); DCHECK(response_id != kAppCacheNoResponseId); storage_->working_set()->AddResponseInfo(this); } AppCacheResponseInfo::~AppCacheResponseInfo() { storage_->working_set()->RemoveResponseInfo(this); } // HttpResponseInfoIOBuffer ------------------------------------------ HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer() : response_data_size(kUnkownResponseDataSize) {} HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer(net::HttpResponseInfo* info) : http_info(info), response_data_size(kUnkownResponseDataSize) {} HttpResponseInfoIOBuffer::~HttpResponseInfoIOBuffer() {} // AppCacheResponseIO ---------------------------------------------- AppCacheResponseIO::AppCacheResponseIO( int64 response_id, int64 group_id, AppCacheDiskCacheInterface* disk_cache) : response_id_(response_id), group_id_(group_id), disk_cache_(disk_cache), entry_(NULL), buffer_len_(0), weak_factory_(this) { } AppCacheResponseIO::~AppCacheResponseIO() { if (entry_) entry_->Close(); } void AppCacheResponseIO::ScheduleIOCompletionCallback(int result) { base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&AppCacheResponseIO::OnIOComplete, weak_factory_.GetWeakPtr(), result)); } void AppCacheResponseIO::InvokeUserCompletionCallback(int result) { // Clear the user callback and buffers prior to invoking the callback // so the caller can schedule additional operations in the callback. buffer_ = NULL; info_buffer_ = NULL; net::CompletionCallback cb = callback_; callback_.Reset(); cb.Run(result); } void AppCacheResponseIO::ReadRaw(int index, int offset, net::IOBuffer* buf, int buf_len) { DCHECK(entry_); int rv = entry_->Read( index, offset, buf, buf_len, base::Bind(&AppCacheResponseIO::OnRawIOComplete, weak_factory_.GetWeakPtr())); if (rv != net::ERR_IO_PENDING) ScheduleIOCompletionCallback(rv); } void AppCacheResponseIO::WriteRaw(int index, int offset, net::IOBuffer* buf, int buf_len) { DCHECK(entry_); int rv = entry_->Write( index, offset, buf, buf_len, base::Bind(&AppCacheResponseIO::OnRawIOComplete, weak_factory_.GetWeakPtr())); if (rv != net::ERR_IO_PENDING) ScheduleIOCompletionCallback(rv); } void AppCacheResponseIO::OnRawIOComplete(int result) { DCHECK_NE(net::ERR_IO_PENDING, result); OnIOComplete(result); } // AppCacheResponseReader ---------------------------------------------- AppCacheResponseReader::AppCacheResponseReader( int64 response_id, int64 group_id, AppCacheDiskCacheInterface* disk_cache) : AppCacheResponseIO(response_id, group_id, disk_cache), range_offset_(0), range_length_(kint32max), read_position_(0), weak_factory_(this) { } AppCacheResponseReader::~AppCacheResponseReader() { } void AppCacheResponseReader::ReadInfo(HttpResponseInfoIOBuffer* info_buf, const net::CompletionCallback& callback) { DCHECK(!callback.is_null()); DCHECK(!IsReadPending()); DCHECK(info_buf); DCHECK(!info_buf->http_info.get()); DCHECK(!buffer_.get()); DCHECK(!info_buffer_.get()); info_buffer_ = info_buf; callback_ = callback; // cleared on completion OpenEntryIfNeededAndContinue(); } void AppCacheResponseReader::ContinueReadInfo() { if (!entry_) { ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); return; } int size = entry_->GetSize(kResponseInfoIndex); if (size <= 0) { ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); return; } buffer_ = new net::IOBuffer(size); ReadRaw(kResponseInfoIndex, 0, buffer_.get(), size); } void AppCacheResponseReader::ReadData(net::IOBuffer* buf, int buf_len, const net::CompletionCallback& callback) { DCHECK(!callback.is_null()); DCHECK(!IsReadPending()); DCHECK(buf); DCHECK(buf_len >= 0); DCHECK(!buffer_.get()); DCHECK(!info_buffer_.get()); buffer_ = buf; buffer_len_ = buf_len; callback_ = callback; // cleared on completion OpenEntryIfNeededAndContinue(); } void AppCacheResponseReader::ContinueReadData() { if (!entry_) { ScheduleIOCompletionCallback(net::ERR_CACHE_MISS); return; } if (read_position_ + buffer_len_ > range_length_) { // TODO(michaeln): What about integer overflows? DCHECK(range_length_ >= read_position_); buffer_len_ = range_length_ - read_position_; } ReadRaw(kResponseContentIndex, range_offset_ + read_position_, buffer_.get(), buffer_len_); } void AppCacheResponseReader::SetReadRange(int offset, int length) { DCHECK(!IsReadPending() && !read_position_); range_offset_ = offset; range_length_ = length; } void AppCacheResponseReader::OnIOComplete(int result) { if (result >= 0) { if (info_buffer_.get()) { // Deserialize the http info structure, ensuring we got headers. Pickle pickle(buffer_->data(), result); scoped_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo); bool response_truncated = false; if (!info->InitFromPickle(pickle, &response_truncated) || !info->headers.get()) { InvokeUserCompletionCallback(net::ERR_FAILED); return; } DCHECK(!response_truncated); info_buffer_->http_info.reset(info.release()); // Also return the size of the response body DCHECK(entry_); info_buffer_->response_data_size = entry_->GetSize(kResponseContentIndex); } else { read_position_ += result; } } InvokeUserCompletionCallback(result); } void AppCacheResponseReader::OpenEntryIfNeededAndContinue() { int rv; AppCacheDiskCacheInterface::Entry** entry_ptr = NULL; if (entry_) { rv = net::OK; } else if (!disk_cache_) { rv = net::ERR_FAILED; } else { entry_ptr = new AppCacheDiskCacheInterface::Entry*; open_callback_ = base::Bind(&AppCacheResponseReader::OnOpenEntryComplete, weak_factory_.GetWeakPtr(), base::Owned(entry_ptr)); rv = disk_cache_->OpenEntry(response_id_, entry_ptr, open_callback_); } if (rv != net::ERR_IO_PENDING) OnOpenEntryComplete(entry_ptr, rv); } void AppCacheResponseReader::OnOpenEntryComplete( AppCacheDiskCacheInterface::Entry** entry, int rv) { DCHECK(info_buffer_.get() || buffer_.get()); if (!open_callback_.is_null()) { if (rv == net::OK) { DCHECK(entry); entry_ = *entry; } open_callback_.Reset(); } if (info_buffer_.get()) ContinueReadInfo(); else ContinueReadData(); } // AppCacheResponseWriter ---------------------------------------------- AppCacheResponseWriter::AppCacheResponseWriter( int64 response_id, int64 group_id, AppCacheDiskCacheInterface* disk_cache) : AppCacheResponseIO(response_id, group_id, disk_cache), info_size_(0), write_position_(0), write_amount_(0), creation_phase_(INITIAL_ATTEMPT), weak_factory_(this) { } AppCacheResponseWriter::~AppCacheResponseWriter() { } void AppCacheResponseWriter::WriteInfo( HttpResponseInfoIOBuffer* info_buf, const net::CompletionCallback& callback) { DCHECK(!callback.is_null()); DCHECK(!IsWritePending()); DCHECK(info_buf); DCHECK(info_buf->http_info.get()); DCHECK(!buffer_.get()); DCHECK(!info_buffer_.get()); DCHECK(info_buf->http_info->headers.get()); info_buffer_ = info_buf; callback_ = callback; // cleared on completion CreateEntryIfNeededAndContinue(); } void AppCacheResponseWriter::ContinueWriteInfo() { if (!entry_) { ScheduleIOCompletionCallback(net::ERR_FAILED); return; } const bool kSkipTransientHeaders = true; const bool kTruncated = false; Pickle* pickle = new Pickle; info_buffer_->http_info->Persist(pickle, kSkipTransientHeaders, kTruncated); write_amount_ = static_cast<int>(pickle->size()); buffer_ = new WrappedPickleIOBuffer(pickle); // takes ownership of pickle WriteRaw(kResponseInfoIndex, 0, buffer_.get(), write_amount_); } void AppCacheResponseWriter::WriteData( net::IOBuffer* buf, int buf_len, const net::CompletionCallback& callback) { DCHECK(!callback.is_null()); DCHECK(!IsWritePending()); DCHECK(buf); DCHECK(buf_len >= 0); DCHECK(!buffer_.get()); DCHECK(!info_buffer_.get()); buffer_ = buf; write_amount_ = buf_len; callback_ = callback; // cleared on completion CreateEntryIfNeededAndContinue(); } void AppCacheResponseWriter::ContinueWriteData() { if (!entry_) { ScheduleIOCompletionCallback(net::ERR_FAILED); return; } WriteRaw( kResponseContentIndex, write_position_, buffer_.get(), write_amount_); } void AppCacheResponseWriter::OnIOComplete(int result) { if (result >= 0) { DCHECK(write_amount_ == result); if (!info_buffer_.get()) write_position_ += result; else info_size_ = result; } InvokeUserCompletionCallback(result); } void AppCacheResponseWriter::CreateEntryIfNeededAndContinue() { int rv; AppCacheDiskCacheInterface::Entry** entry_ptr = NULL; if (entry_) { creation_phase_ = NO_ATTEMPT; rv = net::OK; } else if (!disk_cache_) { creation_phase_ = NO_ATTEMPT; rv = net::ERR_FAILED; } else { creation_phase_ = INITIAL_ATTEMPT; entry_ptr = new AppCacheDiskCacheInterface::Entry*; create_callback_ = base::Bind(&AppCacheResponseWriter::OnCreateEntryComplete, weak_factory_.GetWeakPtr(), base::Owned(entry_ptr)); rv = disk_cache_->CreateEntry(response_id_, entry_ptr, create_callback_); } if (rv != net::ERR_IO_PENDING) OnCreateEntryComplete(entry_ptr, rv); } void AppCacheResponseWriter::OnCreateEntryComplete( AppCacheDiskCacheInterface::Entry** entry, int rv) { DCHECK(info_buffer_.get() || buffer_.get()); if (creation_phase_ == INITIAL_ATTEMPT) { if (rv != net::OK) { // We may try to overwrite existing entries. creation_phase_ = DOOM_EXISTING; rv = disk_cache_->DoomEntry(response_id_, create_callback_); if (rv != net::ERR_IO_PENDING) OnCreateEntryComplete(NULL, rv); return; } } else if (creation_phase_ == DOOM_EXISTING) { creation_phase_ = SECOND_ATTEMPT; AppCacheDiskCacheInterface::Entry** entry_ptr = new AppCacheDiskCacheInterface::Entry*; create_callback_ = base::Bind(&AppCacheResponseWriter::OnCreateEntryComplete, weak_factory_.GetWeakPtr(), base::Owned(entry_ptr)); rv = disk_cache_->CreateEntry(response_id_, entry_ptr, create_callback_); if (rv != net::ERR_IO_PENDING) OnCreateEntryComplete(entry_ptr, rv); return; } if (!create_callback_.is_null()) { if (rv == net::OK) entry_ = *entry; create_callback_.Reset(); } if (info_buffer_.get()) ContinueWriteInfo(); else ContinueWriteData(); } } // namespace content