blob: 22cdb5bd31e7d8372a003b398bd3795ba6ed7303 [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 <utility>
#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_disk_cache.h"
#include "content/browser/appcache/appcache_storage.h"
#include "net/base/completion_once_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "storage/common/storage_histograms.h"
#include "third_party/blink/public/mojom/appcache/appcache_info.mojom.h"
namespace content {
namespace {
using OnceCompletionCallback = base::OnceCallback<void(int)>;
// Disk cache entry data indices.
enum { kResponseInfoIndex, kResponseContentIndex, kResponseMetadataIndex };
// An IOBuffer that wraps a pickle's data.
class WrappedPickleIOBuffer : public net::WrappedIOBuffer {
public:
explicit WrappedPickleIOBuffer(std::unique_ptr<const base::Pickle> pickle)
: net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data())),
pickle_(std::move(pickle)) {
DCHECK(pickle_->data());
}
private:
~WrappedPickleIOBuffer() override = default;
const std::unique_ptr<const base::Pickle> pickle_;
};
} // anon namespace
// AppCacheResponseInfo ----------------------------------------------
AppCacheResponseInfo::AppCacheResponseInfo(
base::WeakPtr<AppCacheStorage> storage,
const GURL& manifest_url,
int64_t response_id,
std::unique_ptr<net::HttpResponseInfo> http_info,
int64_t response_data_size)
: manifest_url_(manifest_url),
response_id_(response_id),
http_response_info_(std::move(http_info)),
response_data_size_(response_data_size),
storage_(std::move(storage)) {
DCHECK(http_response_info_);
DCHECK(response_id != blink::mojom::kAppCacheNoResponseId);
storage_->working_set()->AddResponseInfo(this);
}
AppCacheResponseInfo::~AppCacheResponseInfo() {
if (storage_)
storage_->working_set()->RemoveResponseInfo(this);
}
// HttpResponseInfoIOBuffer ------------------------------------------
HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer()
: response_data_size(kUnknownResponseDataSize) {}
HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer(
std::unique_ptr<net::HttpResponseInfo> info)
: http_info(std::move(info)),
response_data_size(kUnknownResponseDataSize) {}
HttpResponseInfoIOBuffer::~HttpResponseInfoIOBuffer() = default;
// AppCacheResponseIO ----------------------------------------------
AppCacheResponseIO::AppCacheResponseIO(
int64_t response_id,
base::WeakPtr<AppCacheDiskCache> disk_cache)
: response_id_(response_id),
disk_cache_(std::move(disk_cache)),
entry_(nullptr),
buffer_len_(0) {}
AppCacheResponseIO::~AppCacheResponseIO() {
if (entry_)
entry_->Close();
}
void AppCacheResponseIO::ScheduleIOCompletionCallback(int result) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&AppCacheResponseIO::OnIOComplete, 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::BindOnce(&AppCacheResponseIO::OnRawIOComplete, 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::BindOnce(&AppCacheResponseIO::OnRawIOComplete, 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;
AppCacheDiskCacheEntry** entry_ptr = nullptr;
if (entry_) {
rv = net::OK;
} else if (!disk_cache_) {
rv = net::ERR_FAILED;
} else {
entry_ptr = new AppCacheDiskCacheEntry*;
rv = disk_cache_->OpenEntry(
response_id_, entry_ptr,
base::BindOnce(&AppCacheResponseIO::OpenEntryCallback, GetWeakPtr(),
entry_ptr));
}
if (rv != net::ERR_IO_PENDING)
OpenEntryCallback(GetWeakPtr(), entry_ptr, rv);
}
// static
void AppCacheResponseIO::OpenEntryCallback(
base::WeakPtr<AppCacheResponseIO> response,
AppCacheDiskCacheEntry** entry,
int rv) {
if (!response) {
delete entry;
return;
}
DCHECK(response->info_buffer_.get() || response->buffer_.get());
if (!response->entry_ && rv == net::OK) {
DCHECK(entry);
response->entry_ = *entry;
}
delete entry;
response->OnOpenEntryComplete();
}
// AppCacheResponseReader ----------------------------------------------
AppCacheResponseReader::AppCacheResponseReader(
int64_t response_id,
base::WeakPtr<AppCacheDiskCache> disk_cache)
: AppCacheResponseIO(response_id, std::move(disk_cache)),
range_offset_(0),
range_length_(std::numeric_limits<int32_t>::max()),
read_position_(0),
reading_metadata_size_(0) {}
AppCacheResponseReader::~AppCacheResponseReader() = default;
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_ = base::MakeRefCounted<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() {
// Since every read reads at most (range_length_ - read_position_) bytes,
// read_position_ can never become larger than range_length_.
DCHECK_GE(range_length_, read_position_);
if (range_length_ - read_position_ < buffer_len_)
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);
auto info = std::make_unique<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 =
base::MakeRefCounted<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();
}
base::WeakPtr<AppCacheResponseIO> AppCacheResponseReader::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
// AppCacheResponseWriter ----------------------------------------------
AppCacheResponseWriter::AppCacheResponseWriter(
int64_t response_id,
base::WeakPtr<AppCacheDiskCache> disk_cache)
: AppCacheResponseIO(response_id, std::move(disk_cache)),
info_size_(0),
write_position_(0),
write_amount_(0),
creation_phase_(INITIAL_ATTEMPT) {}
AppCacheResponseWriter::~AppCacheResponseWriter() = default;
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;
std::unique_ptr<base::Pickle> pickle = std::make_unique<base::Pickle>();
info_buffer_->http_info->Persist(pickle.get(), kSkipTransientHeaders,
kTruncated);
write_amount_ = static_cast<int>(pickle->size());
buffer_ = base::MakeRefCounted<WrappedPickleIOBuffer>(std::move(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;
AppCacheDiskCacheEntry** 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 AppCacheDiskCacheEntry*;
rv = disk_cache_->CreateEntry(
response_id_, entry_ptr,
base::BindOnce(&AppCacheResponseWriter::OnCreateEntryComplete,
weak_factory_.GetWeakPtr(), entry_ptr));
}
if (rv != net::ERR_IO_PENDING)
OnCreateEntryComplete(weak_factory_.GetWeakPtr(), entry_ptr, rv);
}
// static
void AppCacheResponseWriter::OnCreateEntryComplete(
base::WeakPtr<AppCacheResponseWriter> writer,
AppCacheDiskCacheEntry** entry,
int rv) {
if (!writer) {
if (entry) {
delete entry;
}
return;
}
DCHECK(writer->info_buffer_.get() || writer->buffer_.get());
if (!writer->disk_cache_) {
if (entry) {
delete entry;
}
writer->ScheduleIOCompletionCallback(net::ERR_FAILED);
return;
} else if (writer->creation_phase_ == INITIAL_ATTEMPT) {
if (rv != net::OK) {
// We may try to overwrite existing entries.
delete entry;
writer->creation_phase_ = DOOM_EXISTING;
rv = writer->disk_cache_->DoomEntry(
writer->response_id_,
base::BindOnce(&AppCacheResponseWriter::OnCreateEntryComplete, writer,
nullptr));
if (rv != net::ERR_IO_PENDING)
OnCreateEntryComplete(writer, nullptr, rv);
return;
}
} else if (writer->creation_phase_ == DOOM_EXISTING) {
DCHECK_EQ(nullptr, entry);
writer->creation_phase_ = SECOND_ATTEMPT;
AppCacheDiskCacheEntry** entry_ptr = new AppCacheDiskCacheEntry*;
rv = writer->disk_cache_->CreateEntry(
writer->response_id_, entry_ptr,
base::BindOnce(&AppCacheResponseWriter::OnCreateEntryComplete, writer,
entry_ptr));
if (rv != net::ERR_IO_PENDING)
OnCreateEntryComplete(writer, entry_ptr, rv);
return;
}
if (!writer->entry_ && rv == net::OK) {
DCHECK(entry);
writer->entry_ = *entry;
}
delete entry;
if (writer->info_buffer_.get())
writer->ContinueWriteInfo();
else
writer->ContinueWriteData();
}
base::WeakPtr<AppCacheResponseIO> AppCacheResponseWriter::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
// AppCacheResponseMetadataWriter ----------------------------------------------
AppCacheResponseMetadataWriter::AppCacheResponseMetadataWriter(
int64_t response_id,
base::WeakPtr<AppCacheDiskCache> disk_cache)
: AppCacheResponseIO(response_id, std::move(disk_cache)),
write_amount_(0) {}
AppCacheResponseMetadataWriter::~AppCacheResponseMetadataWriter() = default;
void AppCacheResponseMetadataWriter::WriteMetadata(
net::IOBuffer* buf,
int buf_len,
net::CompletionOnceCallback callback) {
DCHECK(!callback.is_null());
DCHECK(!IsIOPending());
DCHECK(buf);
DCHECK(buf_len >= 0);
DCHECK(!buffer_.get());
buffer_ = buf;
write_amount_ = buf_len;
callback_ = std::move(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.
}
base::WeakPtr<AppCacheResponseIO> AppCacheResponseMetadataWriter::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace content