blob: dd47432bcb68a0c8782ed43c6a06247382cd0d7b [file] [log] [blame]
// 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 <stddef.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "base/pickle.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.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"
#include "storage/common/storage_histograms.h"
namespace content {
namespace {
// Disk cache entry data indices.
enum { kResponseInfoIndex, kResponseContentIndex, kResponseMetadataIndex };
// 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 base::Pickle* pickle)
: net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data())),
pickle_(pickle) {
DCHECK(pickle->data());
}
private:
~WrappedPickleIOBuffer() override {}
std::unique_ptr<const base::Pickle> pickle_;
};
} // anon namespace
// AppCacheResponseInfo ----------------------------------------------
AppCacheResponseInfo::AppCacheResponseInfo(AppCacheStorage* storage,
const GURL& manifest_url,
int64_t response_id,
net::HttpResponseInfo* http_info,
int64_t 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() {}
// AppCacheDiskCacheInterface ----------------------------------------
AppCacheDiskCacheInterface::AppCacheDiskCacheInterface(const char* uma_name)
: uma_name_(uma_name), weak_factory_(this) {}
base::WeakPtr<AppCacheDiskCacheInterface>
AppCacheDiskCacheInterface::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
AppCacheDiskCacheInterface::~AppCacheDiskCacheInterface() {}
// AppCacheResponseIO ----------------------------------------------
AppCacheResponseIO::AppCacheResponseIO(
int64_t response_id,
const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
: response_id_(response_id),
disk_cache_(disk_cache),
entry_(nullptr),
buffer_len_(0),
weak_factory_(this) {}
AppCacheResponseIO::~AppCacheResponseIO() {
if (entry_)
entry_->Close();
}
void AppCacheResponseIO::ScheduleIOCompletionCallback(int result) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&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_ = nullptr;
info_buffer_ = nullptr;
OnceCompletionCallback cb = std::move(callback_);
callback_.Reset();
std::move(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);
}
void AppCacheResponseIO::OpenEntryIfNeeded() {
int rv;
AppCacheDiskCacheInterface::Entry** entry_ptr = nullptr;
if (entry_) {
rv = net::OK;
} else if (!disk_cache_) {
rv = net::ERR_FAILED;
} else {
entry_ptr = new AppCacheDiskCacheInterface::Entry*;
open_callback_ =
base::Bind(&AppCacheResponseIO::OpenEntryCallback,
weak_factory_.GetWeakPtr(), base::Owned(entry_ptr));
rv = disk_cache_->OpenEntry(response_id_, entry_ptr, open_callback_);
}
if (rv != net::ERR_IO_PENDING)
OpenEntryCallback(entry_ptr, rv);
}
void AppCacheResponseIO::OpenEntryCallback(
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();
}
OnOpenEntryComplete();
}
// AppCacheResponseReader ----------------------------------------------
AppCacheResponseReader::AppCacheResponseReader(
int64_t response_id,
const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
: AppCacheResponseIO(response_id, disk_cache),
range_offset_(0),
range_length_(std::numeric_limits<int32_t>::max()),
read_position_(0),
reading_metadata_size_(0),
weak_factory_(this) {}
AppCacheResponseReader::~AppCacheResponseReader() {
}
void AppCacheResponseReader::ReadInfo(HttpResponseInfoIOBuffer* info_buf,
OnceCompletionCallback 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_ = std::move(callback); // cleared on completion
OpenEntryIfNeeded();
}
void AppCacheResponseReader::ContinueReadInfo() {
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,
OnceCompletionCallback 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_ = std::move(callback); // cleared on completion
OpenEntryIfNeeded();
}
void AppCacheResponseReader::ContinueReadData() {
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 (reading_metadata_size_) {
DCHECK(reading_metadata_size_ == result);
DCHECK(info_buffer_->http_info->metadata);
reading_metadata_size_ = 0;
} else if (info_buffer_.get()) {
// Deserialize the http info structure, ensuring we got headers.
base::Pickle pickle(buffer_->data(), result);
std::unique_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);
int64_t metadata_size = entry_->GetSize(kResponseMetadataIndex);
if (metadata_size > 0) {
reading_metadata_size_ = metadata_size;
info_buffer_->http_info->metadata = new net::IOBufferWithSize(
base::checked_cast<size_t>(metadata_size));
ReadRaw(kResponseMetadataIndex, 0,
info_buffer_->http_info->metadata.get(), metadata_size);
return;
}
} else {
read_position_ += result;
}
}
if (result > 0 && disk_cache_)
storage::RecordBytesRead(disk_cache_->uma_name(), result);
InvokeUserCompletionCallback(result);
// Note: |this| may have been deleted by the completion callback.
}
void AppCacheResponseReader::OnOpenEntryComplete() {
if (!entry_) {
ScheduleIOCompletionCallback(net::ERR_CACHE_MISS);
return;
}
if (info_buffer_.get())
ContinueReadInfo();
else
ContinueReadData();
}
// AppCacheResponseWriter ----------------------------------------------
AppCacheResponseWriter::AppCacheResponseWriter(
int64_t response_id,
const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
: AppCacheResponseIO(response_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,
OnceCompletionCallback 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_ = std::move(callback); // cleared on completion
CreateEntryIfNeededAndContinue();
}
void AppCacheResponseWriter::ContinueWriteInfo() {
if (!entry_) {
ScheduleIOCompletionCallback(net::ERR_FAILED);
return;
}
const bool kSkipTransientHeaders = true;
const bool kTruncated = false;
base::Pickle* pickle = new base::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,
OnceCompletionCallback 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_ = std::move(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;
}
if (result > 0 && disk_cache_)
storage::RecordBytesWritten(disk_cache_->uma_name(), result);
InvokeUserCompletionCallback(result);
// Note: |this| may have been deleted by the completion callback.
}
void AppCacheResponseWriter::CreateEntryIfNeededAndContinue() {
int rv;
AppCacheDiskCacheInterface::Entry** entry_ptr = nullptr;
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 (!disk_cache_) {
ScheduleIOCompletionCallback(net::ERR_FAILED);
return;
} else 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(nullptr, 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();
}
// AppCacheResponseMetadataWriter ----------------------------------------------
AppCacheResponseMetadataWriter::AppCacheResponseMetadataWriter(
int64_t response_id,
const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
: AppCacheResponseIO(response_id, disk_cache),
write_amount_(0),
weak_factory_(this) {}
AppCacheResponseMetadataWriter::~AppCacheResponseMetadataWriter() {
}
void AppCacheResponseMetadataWriter::WriteMetadata(
net::IOBuffer* buf,
int buf_len,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
DCHECK(!IsIOPending());
DCHECK(buf);
DCHECK(buf_len >= 0);
DCHECK(!buffer_.get());
buffer_ = buf;
write_amount_ = buf_len;
callback_ = callback; // cleared on completion
OpenEntryIfNeeded();
}
void AppCacheResponseMetadataWriter::OnOpenEntryComplete() {
if (!entry_) {
ScheduleIOCompletionCallback(net::ERR_FAILED);
return;
}
WriteRaw(kResponseMetadataIndex, 0, buffer_.get(), write_amount_);
}
void AppCacheResponseMetadataWriter::OnIOComplete(int result) {
DCHECK(result < 0 || write_amount_ == result);
if (result > 0 && disk_cache_)
storage::RecordBytesWritten(disk_cache_->uma_name(), result);
InvokeUserCompletionCallback(result);
// Note: |this| may have been deleted by the completion callback.
}
} // namespace content