blob: 4fccb64cb8b4e357f6d96200ac77fdf71524e34a [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_WEB_APPLICATIONS_ISOLATED_WEB_APPS_ISOLATED_WEB_APP_READER_REGISTRY_H_
#define CHROME_BROWSER_WEB_APPLICATIONS_ISOLATED_WEB_APPS_ISOLATED_WEB_APP_READER_REGISTRY_H_
#include <memory>
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/types/expected.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_response_reader.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_response_reader_factory.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_validator.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/web_package/signed_web_bundles/signed_web_bundle_signature_verifier.h"
#include "services/network/public/cpp/resource_request.h"
namespace web_package {
class SignedWebBundleId;
} // namespace web_package
namespace web_app {
// A registry to create and keep track of `IsolatedWebAppResponseReader`
// instances used to read Isolated Web Apps. At its core, it contains a map from
// file paths to `IsolatedWebAppResponseReader`s to cache them for repeated
// calls. On non-ChromeOS devices, the first request for a particular file path
// will also check the integrity of the Signed Web Bundle. On ChromeOS, it is
// assumed that the Signed Web Bundle has not been corrupted due to its location
// inside cryptohome, and signatures are not checked.
class IsolatedWebAppReaderRegistry : public KeyedService {
public:
explicit IsolatedWebAppReaderRegistry(
std::unique_ptr<IsolatedWebAppValidator> validator,
base::RepeatingCallback<
std::unique_ptr<web_package::SignedWebBundleSignatureVerifier>()>
signature_verifier_factory);
~IsolatedWebAppReaderRegistry() override;
IsolatedWebAppReaderRegistry(const IsolatedWebAppReaderRegistry&) = delete;
IsolatedWebAppReaderRegistry& operator=(const IsolatedWebAppReaderRegistry&) =
delete;
struct ReadResponseError {
enum class Type {
kOtherError,
kResponseNotFound,
};
static ReadResponseError ForError(
const IsolatedWebAppResponseReaderFactory::Error& error);
static ReadResponseError ForError(
const IsolatedWebAppResponseReader::Error& error);
Type type;
std::string message;
private:
static ReadResponseError ForOtherError(const std::string& message) {
return ReadResponseError(Type::kOtherError, message);
}
static ReadResponseError ForResponseNotFound(const std::string& message) {
return ReadResponseError(Type::kResponseNotFound, message);
}
ReadResponseError(Type type, const std::string& message)
: type(type), message(message) {}
};
using ReadResponseCallback = base::OnceCallback<void(
base::expected<IsolatedWebAppResponseReader::Response, ReadResponseError>
response)>;
// Given a path to a Signed Web Bundle, the expected Signed Web Bundle ID, and
// a request, read the corresponding response from it. The `callback` receives
// both the response head and a closure it can call to read the response body,
// or a string if an error occurs.
void ReadResponse(const base::FilePath& web_bundle_path,
const web_package::SignedWebBundleId& web_bundle_id,
const network::ResourceRequest& resource_request,
ReadResponseCallback callback);
// This enum represents every error type that can occur during response head
// parsing, after integrity block and metadata have been read successfully.
//
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ReadResponseHeadStatus {
kSuccess = 0,
kResponseHeadParserInternalError = 1,
kResponseHeadParserFormatError = 2,
kResponseNotFoundError = 3,
kMaxValue = kResponseNotFoundError
};
private:
FRIEND_TEST_ALL_PREFIXES(IsolatedWebAppReaderRegistryTest,
TestConcurrentRequests);
void OnResponseReaderCreated(
const base::FilePath& web_bundle_path,
const web_package::SignedWebBundleId& web_bundle_id,
base::expected<std::unique_ptr<IsolatedWebAppResponseReader>,
IsolatedWebAppResponseReaderFactory::Error> reader);
void DoReadResponse(IsolatedWebAppResponseReader& reader,
network::ResourceRequest resource_request,
ReadResponseCallback callback);
void OnResponseRead(
ReadResponseCallback callback,
base::expected<IsolatedWebAppResponseReader::Response,
IsolatedWebAppResponseReader::Error> response);
ReadResponseHeadStatus GetStatusFromError(
const IsolatedWebAppResponseReader::Error& error);
enum class ReaderCacheState;
// A thin wrapper around `base::flat_map<base::FilePath, Cache::Entry>` that
// automatically removes entries from the cache if they have not been accessed
// for some time. This makes sure that `IsolatedWebAppResponseReader`s are not
// kept alive indefinitely, since each of them holds an open file handle and
// memory.
class Cache {
public:
class Entry;
Cache();
~Cache();
Cache(Cache&& other) = delete;
Cache& operator=(Cache&& other) = delete;
base::flat_map<base::FilePath, Entry>::iterator Find(
const base::FilePath& file_path);
base::flat_map<base::FilePath, Entry>::iterator End();
template <class... Args>
std::pair<base::flat_map<base::FilePath, Entry>::iterator, bool> Emplace(
Args&&... args);
void Erase(base::flat_map<base::FilePath, Entry>::iterator iterator);
// A cache `Entry` has two states: In its initial `kPending` state, it
// caches requests made to a Signed Web Bundle until an
// `IsolatedWebAppResponseReader` is ready. Once the
// `IsolatedWebAppResponseReader` is ready and set via `set_reader`, all
// queued requests are run and the state is updated to `kReady`.
class Entry {
public:
Entry();
~Entry();
Entry(const Entry& other) = delete;
Entry& operator=(const Entry& other) = delete;
Entry(Entry&& other);
Entry& operator=(Entry&& other);
IsolatedWebAppResponseReader& GetReader() {
DCHECK(reader_);
last_access_ = base::TimeTicks::Now();
return *reader_;
}
const base::TimeTicks last_access() const { return last_access_; }
ReaderCacheState AsReaderCacheState() {
switch (state()) {
case State::kPending:
return ReaderCacheState::kCachedPending;
case State::kReady:
return ReaderCacheState::kCachedReady;
}
}
enum class State { kPending, kReady };
State state() const { return reader_ ? State::kReady : State::kPending; }
void set_reader(std::unique_ptr<IsolatedWebAppResponseReader> reader) {
reader_ = std::move(reader);
}
std::vector<std::pair<network::ResourceRequest,
IsolatedWebAppReaderRegistry::ReadResponseCallback>>
pending_requests;
private:
std::unique_ptr<IsolatedWebAppResponseReader> reader_;
// The point in time when the `reader` was last accessed.
base::TimeTicks last_access_;
};
private:
void StartCleanupTimerIfNotRunning();
void StopCleanupTimerIfCacheIsEmpty();
void CleanupOldEntries();
base::flat_map<base::FilePath, Entry> cache_;
base::RepeatingTimer cleanup_timer_;
SEQUENCE_CHECKER(sequence_checker_);
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ReaderCacheState {
kNotCached = 0,
kCachedReady = 1,
kCachedPending = 2,
kMaxValue = kCachedPending
};
Cache reader_cache_;
// A set of files whose signatures have been verified successfully during the
// current browser session. Signatures of these files are not re-verified even
// if their corresponding `CacheEntry` is cleaned up and later re-created.
base::flat_set<base::FilePath> verified_files_;
std::unique_ptr<IsolatedWebAppResponseReaderFactory> reader_factory_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<IsolatedWebAppReaderRegistry> weak_ptr_factory_{this};
};
} // namespace web_app
#endif // CHROME_BROWSER_WEB_APPLICATIONS_ISOLATED_WEB_APPS_ISOLATED_WEB_APP_READER_REGISTRY_H_