| // Copyright 2018 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/code_cache/generated_code_cache.h" |
| #include "base/memory/ptr_util.h" |
| #include "net/base/completion_callback.h" |
| #include "net/base/completion_once_callback.h" |
| #include "net/http/http_util.h" |
| |
| namespace content { |
| |
| namespace { |
| // Checks if |requesting_origin| is allowed to cache code for |resource_url|. |
| // |resource_url| is the url corresponding to the requested resource. |
| // If this url is invalid we don't cache the code. |
| // |requesting_origin| is the origin that has requested the resource. |
| // If this is a unique origin, then we don't cache the code. |
| // For example, if http://script.com/script1.js is requested by |
| // http://example.com, then http://script.com/script.js is the resource_url |
| // and example.com is the requesting_origin. |
| bool IsAllowedToCache(const GURL& resource_url, |
| const url::Origin& requesting_origin) { |
| // Don't cache the code corresponding to unique origins. The same-origin |
| // checks should always fail for unique origins but the serialized value of |
| // unique origins does not ensure this. |
| if (requesting_origin.unique()) |
| return false; |
| |
| // If the resource url or requesting url is invalid don't cache the code. |
| if (!resource_url.is_valid()) |
| return false; |
| |
| return true; |
| } |
| |
| // Generates the cache key for the given |resource_url| and the |
| // |requesting_origin|. This returns the key by concatenating the |
| // serialized url and origin with a separator in between. |
| std::string GetCacheKey(const GURL& resource_url, |
| const url::Origin& requesting_origin) { |
| DCHECK(!requesting_origin.unique()); |
| DCHECK(resource_url.is_valid()); |
| // Add a prefix _ so it can't be parsed as a valid URL. |
| std::string key = "_key"; |
| // Remove reference, username and password sections of the URL. |
| key.append(net::HttpUtil::SpecForRequest(resource_url)); |
| // Add a separator between URL and origin to avoid any possibility of |
| // attacks by crafting the URL. URLs do not contain any control ASCII |
| // characters, and also space is encoded. So use ' \n' as a seperator. |
| key.append(" \n"); |
| key.append(requesting_origin.Serialize()); |
| return key; |
| } |
| } // namespace |
| |
| // Stores the information about a pending request while disk backend is |
| // being initialized. |
| class GeneratedCodeCache::PendingOperation { |
| public: |
| static std::unique_ptr<PendingOperation> CreateWritePendingOp( |
| std::string key, |
| scoped_refptr<net::IOBufferWithSize>); |
| static std::unique_ptr<PendingOperation> CreateFetchPendingOp( |
| std::string key, |
| const ReadDataCallback&); |
| static std::unique_ptr<PendingOperation> CreateDeletePendingOp( |
| std::string key); |
| static std::unique_ptr<PendingOperation> CreateClearCachePendingOp( |
| net::CompletionCallback callback); |
| |
| ~PendingOperation(); |
| |
| Operation operation() const { return op_; } |
| const std::string& key() const { return key_; } |
| const scoped_refptr<net::IOBufferWithSize> data() const { return data_; } |
| ReadDataCallback ReleaseReadCallback() { return std::move(read_callback_); } |
| net::CompletionCallback ReleaseCallback() { return std::move(callback_); } |
| |
| private: |
| PendingOperation(Operation op, |
| std::string key, |
| scoped_refptr<net::IOBufferWithSize>, |
| const ReadDataCallback&, |
| net::CompletionCallback); |
| |
| const Operation op_; |
| const std::string key_; |
| const scoped_refptr<net::IOBufferWithSize> data_; |
| ReadDataCallback read_callback_; |
| net::CompletionCallback callback_; |
| }; |
| |
| std::unique_ptr<GeneratedCodeCache::PendingOperation> |
| GeneratedCodeCache::PendingOperation::CreateWritePendingOp( |
| std::string key, |
| scoped_refptr<net::IOBufferWithSize> buffer) { |
| return base::WrapUnique( |
| new PendingOperation(Operation::kWrite, std::move(key), buffer, |
| ReadDataCallback(), net::CompletionCallback())); |
| } |
| |
| std::unique_ptr<GeneratedCodeCache::PendingOperation> |
| GeneratedCodeCache::PendingOperation::CreateFetchPendingOp( |
| std::string key, |
| const ReadDataCallback& read_callback) { |
| return base::WrapUnique(new PendingOperation( |
| Operation::kFetch, std::move(key), scoped_refptr<net::IOBufferWithSize>(), |
| read_callback, net::CompletionCallback())); |
| } |
| |
| std::unique_ptr<GeneratedCodeCache::PendingOperation> |
| GeneratedCodeCache::PendingOperation::CreateDeletePendingOp(std::string key) { |
| return base::WrapUnique( |
| new PendingOperation(Operation::kDelete, std::move(key), |
| scoped_refptr<net::IOBufferWithSize>(), |
| ReadDataCallback(), net::CompletionCallback())); |
| } |
| |
| std::unique_ptr<GeneratedCodeCache::PendingOperation> |
| GeneratedCodeCache::PendingOperation::CreateClearCachePendingOp( |
| net::CompletionCallback callback) { |
| return base::WrapUnique( |
| new PendingOperation(Operation::kClearCache, std::string(), |
| scoped_refptr<net::IOBufferWithSize>(), |
| ReadDataCallback(), std::move(callback))); |
| } |
| |
| GeneratedCodeCache::PendingOperation::PendingOperation( |
| Operation op, |
| std::string key, |
| scoped_refptr<net::IOBufferWithSize> buffer, |
| const ReadDataCallback& read_callback, |
| net::CompletionCallback callback) |
| : op_(op), |
| key_(std::move(key)), |
| data_(buffer), |
| read_callback_(read_callback), |
| callback_(std::move(callback)) {} |
| |
| GeneratedCodeCache::PendingOperation::~PendingOperation() = default; |
| |
| GeneratedCodeCache::GeneratedCodeCache(const base::FilePath& path, |
| int max_size_bytes) |
| : backend_state_(kUnInitialized), |
| path_(path), |
| max_size_bytes_(max_size_bytes), |
| weak_ptr_factory_(this) { |
| CreateBackend(); |
| } |
| |
| GeneratedCodeCache::~GeneratedCodeCache() = default; |
| |
| void GeneratedCodeCache::WriteData(const GURL& url, |
| const url::Origin& origin, |
| const base::Time& response_time, |
| const std::vector<uint8_t>& data) { |
| // Silently ignore the requests. |
| if (backend_state_ == kFailed) |
| return; |
| |
| // If the url is invalid or if it is from a unique origin, we should not |
| // cache the code. |
| if (!IsAllowedToCache(url, origin)) |
| return; |
| |
| // Append the response time to the metadata. Code caches store |
| // response_time + generated code as a single entry. |
| scoped_refptr<net::IOBufferWithSize> buffer( |
| new net::IOBufferWithSize(data.size() + kResponseTimeSizeInBytes)); |
| int64_t serialized_time = |
| response_time.ToDeltaSinceWindowsEpoch().InMicroseconds(); |
| memcpy(buffer->data(), &serialized_time, kResponseTimeSizeInBytes); |
| if (!data.empty()) |
| memcpy(buffer->data() + kResponseTimeSizeInBytes, &data.front(), |
| data.size()); |
| |
| std::string key = GetCacheKey(url, origin); |
| if (backend_state_ != kInitialized) { |
| // Insert it into the list of pending operations while the backend is |
| // still being opened. |
| pending_ops_.push_back( |
| GeneratedCodeCache::PendingOperation::CreateWritePendingOp( |
| std::move(key), buffer)); |
| return; |
| } |
| |
| WriteDataImpl(key, buffer); |
| } |
| |
| void GeneratedCodeCache::FetchEntry(const GURL& url, |
| const url::Origin& origin, |
| ReadDataCallback read_data_callback) { |
| if (backend_state_ == kFailed) { |
| // Silently ignore the requests. |
| std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>()); |
| return; |
| } |
| |
| // If the url is invalid or if it is from a unique origin, we should not |
| // cache the code. |
| if (!IsAllowedToCache(url, origin)) { |
| std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>()); |
| return; |
| } |
| |
| std::string key = GetCacheKey(url, origin); |
| if (backend_state_ != kInitialized) { |
| // Insert it into the list of pending operations while the backend is |
| // still being opened. |
| pending_ops_.push_back( |
| GeneratedCodeCache::PendingOperation::CreateFetchPendingOp( |
| std::move(key), read_data_callback)); |
| return; |
| } |
| |
| FetchEntryImpl(key, read_data_callback); |
| } |
| |
| void GeneratedCodeCache::DeleteEntry(const GURL& url, |
| const url::Origin& origin) { |
| // Silently ignore the requests. |
| if (backend_state_ == kFailed) |
| return; |
| |
| // If the url is invalid or if it is from a unique origin, we should not |
| // cache the code. |
| if (!IsAllowedToCache(url, origin)) |
| return; |
| |
| std::string key = GetCacheKey(url, origin); |
| if (backend_state_ != kInitialized) { |
| // Insert it into the list of pending operations while the backend is |
| // still being opened. |
| pending_ops_.push_back( |
| GeneratedCodeCache::PendingOperation::CreateDeletePendingOp( |
| std::move(key))); |
| return; |
| } |
| |
| DeleteEntryImpl(key); |
| } |
| |
| int GeneratedCodeCache::ClearCache(net::CompletionCallback callback) { |
| if (backend_state_ == kFailed) { |
| return net::ERR_FAILED; |
| } |
| |
| if (backend_state_ != kInitialized) { |
| pending_ops_.push_back( |
| GeneratedCodeCache::PendingOperation::CreateClearCachePendingOp( |
| std::move(callback))); |
| return net::ERR_IO_PENDING; |
| } |
| |
| return backend_->DoomAllEntries(std::move(callback)); |
| } |
| |
| void GeneratedCodeCache::CreateBackend() { |
| // Create a new Backend pointer that cleans itself if the GeneratedCodeCache |
| // instance is not live when the CreateCacheBackend finishes. |
| scoped_refptr<base::RefCountedData<ScopedBackendPtr>> shared_backend_ptr = |
| new base::RefCountedData<ScopedBackendPtr>(); |
| |
| net::CompletionOnceCallback create_backend_complete = |
| base::BindOnce(&GeneratedCodeCache::DidCreateBackend, |
| weak_ptr_factory_.GetWeakPtr(), shared_backend_ptr); |
| |
| // If the initialization of the existing cache fails, this call would delete |
| // all the contents and recreates a new one. |
| int rv = disk_cache::CreateCacheBackend( |
| net::GENERATED_CODE_CACHE, net::CACHE_BACKEND_SIMPLE, path_, |
| max_size_bytes_, true, nullptr, &shared_backend_ptr->data, |
| std::move(create_backend_complete)); |
| if (rv != net::ERR_IO_PENDING) { |
| DidCreateBackend(shared_backend_ptr, rv); |
| } |
| } |
| |
| void GeneratedCodeCache::DidCreateBackend( |
| scoped_refptr<base::RefCountedData<ScopedBackendPtr>> backend_ptr, |
| int rv) { |
| if (rv != net::OK) { |
| backend_state_ = kFailed; |
| // Process pending operations to process any required callbacks. |
| IssuePendingOperations(); |
| return; |
| } |
| |
| backend_ = std::move(backend_ptr->data); |
| backend_state_ = kInitialized; |
| IssuePendingOperations(); |
| } |
| |
| void GeneratedCodeCache::IssuePendingOperations() { |
| DCHECK_EQ(backend_state_, kInitialized); |
| // Issue all the pending operations that were received when creating |
| // the backend. |
| for (auto const& op : pending_ops_) { |
| switch (op->operation()) { |
| case kFetch: |
| FetchEntryImpl(op->key(), op->ReleaseReadCallback()); |
| break; |
| case kWrite: |
| WriteDataImpl(op->key(), op->data()); |
| break; |
| case kDelete: |
| DeleteEntryImpl(op->key()); |
| break; |
| case kClearCache: |
| DoPendingClearCache(op->ReleaseCallback()); |
| break; |
| } |
| } |
| pending_ops_.clear(); |
| } |
| |
| void GeneratedCodeCache::WriteDataImpl( |
| const std::string& key, |
| scoped_refptr<net::IOBufferWithSize> buffer) { |
| if (backend_state_ != kInitialized) |
| return; |
| |
| scoped_refptr<base::RefCountedData<disk_cache::Entry*>> entry_ptr = |
| new base::RefCountedData<disk_cache::Entry*>(); |
| net::CompletionOnceCallback callback = |
| base::BindOnce(&GeneratedCodeCache::OpenCompleteForWriteData, |
| weak_ptr_factory_.GetWeakPtr(), buffer, key, entry_ptr); |
| |
| int result = |
| backend_->OpenEntry(key, net::LOW, &entry_ptr->data, std::move(callback)); |
| if (result != net::ERR_IO_PENDING) { |
| OpenCompleteForWriteData(buffer, key, entry_ptr, result); |
| } |
| } |
| |
| void GeneratedCodeCache::OpenCompleteForWriteData( |
| scoped_refptr<net::IOBufferWithSize> buffer, |
| const std::string& key, |
| scoped_refptr<base::RefCountedData<disk_cache::Entry*>> entry, |
| int rv) { |
| if (rv != net::OK) { |
| net::CompletionOnceCallback callback = |
| base::BindOnce(&GeneratedCodeCache::CreateCompleteForWriteData, |
| weak_ptr_factory_.GetWeakPtr(), buffer, entry); |
| |
| int result = |
| backend_->CreateEntry(key, net::LOW, &entry->data, std::move(callback)); |
| if (result != net::ERR_IO_PENDING) { |
| CreateCompleteForWriteData(buffer, entry, result); |
| } |
| return; |
| } |
| |
| DCHECK(entry->data); |
| disk_cache::ScopedEntryPtr disk_entry(entry->data); |
| |
| // This call will truncate the data. This is safe to do since we read the |
| // entire data at the same time currently. If we want to read in parts we have |
| // to doom the entry first. |
| disk_entry->WriteData(kDataIndex, 0, buffer.get(), buffer->size(), |
| net::CompletionOnceCallback(), true); |
| } |
| |
| void GeneratedCodeCache::CreateCompleteForWriteData( |
| scoped_refptr<net::IOBufferWithSize> buffer, |
| scoped_refptr<base::RefCountedData<disk_cache::Entry*>> entry, |
| int rv) { |
| if (rv != net::OK) |
| return; |
| |
| DCHECK(entry->data); |
| disk_cache::ScopedEntryPtr disk_entry(entry->data); |
| disk_entry->WriteData(kDataIndex, 0, buffer.get(), buffer->size(), |
| net::CompletionOnceCallback(), true); |
| } |
| |
| void GeneratedCodeCache::FetchEntryImpl(const std::string& key, |
| ReadDataCallback read_data_callback) { |
| if (backend_state_ != kInitialized) { |
| std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>()); |
| return; |
| } |
| |
| scoped_refptr<base::RefCountedData<disk_cache::Entry*>> entry_ptr = |
| new base::RefCountedData<disk_cache::Entry*>(); |
| |
| net::CompletionOnceCallback callback = base::BindOnce( |
| &GeneratedCodeCache::OpenCompleteForReadData, |
| weak_ptr_factory_.GetWeakPtr(), read_data_callback, entry_ptr); |
| |
| // This is a part of loading cycle and hence should run with a high priority. |
| int result = backend_->OpenEntry(key, net::HIGHEST, &entry_ptr->data, |
| std::move(callback)); |
| if (result != net::ERR_IO_PENDING) { |
| OpenCompleteForReadData(read_data_callback, entry_ptr, result); |
| } |
| } |
| |
| void GeneratedCodeCache::OpenCompleteForReadData( |
| ReadDataCallback read_data_callback, |
| scoped_refptr<base::RefCountedData<disk_cache::Entry*>> entry, |
| int rv) { |
| if (rv != net::OK) { |
| std::move(read_data_callback).Run(base::Time(), std::vector<uint8_t>()); |
| return; |
| } |
| |
| // There should be a valid entry if the open was successful. |
| DCHECK(entry->data); |
| |
| disk_cache::ScopedEntryPtr disk_entry(entry->data); |
| int size = disk_entry->GetDataSize(kDataIndex); |
| scoped_refptr<net::IOBufferWithSize> buffer(new net::IOBufferWithSize(size)); |
| net::CompletionOnceCallback callback = base::BindOnce( |
| &GeneratedCodeCache::ReadDataComplete, weak_ptr_factory_.GetWeakPtr(), |
| read_data_callback, buffer); |
| int result = disk_entry->ReadData(kDataIndex, 0, buffer.get(), size, |
| std::move(callback)); |
| if (result != net::ERR_IO_PENDING) { |
| ReadDataComplete(read_data_callback, buffer, result); |
| } |
| } |
| |
| void GeneratedCodeCache::ReadDataComplete( |
| ReadDataCallback callback, |
| scoped_refptr<net::IOBufferWithSize> buffer, |
| int rv) { |
| if (rv != buffer->size()) { |
| std::move(callback).Run(base::Time(), std::vector<uint8_t>()); |
| } else { |
| int64_t raw_response_time = *(reinterpret_cast<int64_t*>(buffer->data())); |
| base::Time response_time = base::Time::FromDeltaSinceWindowsEpoch( |
| base::TimeDelta::FromMicroseconds(raw_response_time)); |
| std::vector<uint8_t> data(buffer->data() + kResponseTimeSizeInBytes, |
| buffer->data() + buffer->size()); |
| std::move(callback).Run(response_time, data); |
| } |
| } |
| |
| void GeneratedCodeCache::DeleteEntryImpl(const std::string& key) { |
| if (backend_state_ != kInitialized) |
| return; |
| |
| backend_->DoomEntry(key, net::LOWEST, net::CompletionOnceCallback()); |
| } |
| |
| void GeneratedCodeCache::DoPendingClearCache( |
| net::CompletionCallback user_callback) { |
| int result = backend_->DoomAllEntries(user_callback); |
| if (result != net::ERR_IO_PENDING) { |
| // Call the callback here because we returned ERR_IO_PENDING for initial |
| // request. |
| std::move(user_callback).Run(result); |
| } |
| } |
| |
| } // namespace content |