| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_H_ |
| #define SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_H_ |
| |
| #include <list> |
| #include <map> |
| #include <set> |
| #include <string> |
| |
| #include "base/component_export.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/strings/pattern.h" |
| #include "base/time/time.h" |
| #include "base/types/expected.h" |
| #include "net/shared_dictionary/shared_dictionary_isolation_key.h" |
| #include "url/gurl.h" |
| #include "url/scheme_host_port.h" |
| |
| class GURL; |
| |
| namespace net { |
| class HttpResponseHeaders; |
| class SharedDictionary; |
| } // namespace net |
| |
| namespace url_pattern { |
| class SimpleUrlPatternMatcher; |
| } |
| |
| namespace network { |
| namespace mojom { |
| enum class FetchResponseType : int32_t; |
| enum class RequestDestination : int32_t; |
| enum class RequestMode : int32_t; |
| enum class SharedDictionaryError : int32_t; |
| } // namespace mojom |
| |
| class SharedDictionaryWriter; |
| |
| // Shared Dictionary Storage manages dictionaries for a particular |
| // net::SharedDictionaryIsolationKey. |
| class COMPONENT_EXPORT(NETWORK_SERVICE) SharedDictionaryStorage |
| : public base::RefCounted<SharedDictionaryStorage> { |
| public: |
| SharedDictionaryStorage(const SharedDictionaryStorage&) = delete; |
| SharedDictionaryStorage& operator=(const SharedDictionaryStorage&) = delete; |
| |
| // Returns a SharedDictionaryWriter if `headers` has a valid |
| // `use-as-dictionary` header, and `access_allowed_check_callback` |
| // returns true, |
| static base::expected<scoped_refptr<SharedDictionaryWriter>, |
| mojom::SharedDictionaryError> |
| MaybeCreateWriter(const std::string& use_as_dictionary_header, |
| bool shared_dictionary_writer_enabled, |
| SharedDictionaryStorage* storage, |
| mojom::RequestMode request_mode, |
| mojom::FetchResponseType response_tainting, |
| const GURL& url, |
| const base::Time request_time, |
| const base::Time response_time, |
| const net::HttpResponseHeaders& headers, |
| bool was_fetched_via_cache, |
| base::OnceCallback<bool()> access_allowed_check_callback); |
| |
| // Returns a matching SharedDictionary for `url`. If the metadata has not been |
| // read from the database, this method returns nullptr. |
| virtual scoped_refptr<net::SharedDictionary> GetDictionarySync( |
| const GURL& url, |
| mojom::RequestDestination destination) = 0; |
| |
| // If the metadata has already been read from the database, this method calls |
| // `callback` synchronously with a matching `SharedDictionary`. Otherwise, |
| // this method waits until the metadata is available, and then calls |
| // `callback` with a matching `SharedDictionary`. |
| virtual void GetDictionary( |
| const GURL& url, |
| mojom::RequestDestination destination, |
| base::OnceCallback<void(scoped_refptr<net::SharedDictionary>)> |
| callback) = 0; |
| |
| // Returns the isolation key for this storage. |
| virtual const net::SharedDictionaryIsolationKey& isolation_key() const = 0; |
| |
| protected: |
| friend class base::RefCounted<SharedDictionaryStorage>; |
| |
| SharedDictionaryStorage(); |
| virtual ~SharedDictionaryStorage(); |
| |
| // Called to create a SharedDictionaryWriter. |
| virtual base::expected<scoped_refptr<SharedDictionaryWriter>, |
| mojom::SharedDictionaryError> |
| CreateWriter( |
| const GURL& url, |
| base::Time last_fetch_time, |
| base::Time response_time, |
| base::TimeDelta expiration, |
| const std::string& match, |
| const std::set<mojom::RequestDestination>& match_dest, |
| const std::string& id, |
| std::unique_ptr<url_pattern::SimpleUrlPatternMatcher> matcher) = 0; |
| |
| // If the matching dictionary is already registered, this method updates the |
| // `last_fetch_time` of the registered dictionary, and returns true. |
| // Otherwise, this method returns false. |
| virtual bool UpdateLastFetchTimeIfAlreadyRegistered( |
| const GURL& url, |
| base::Time response_time, |
| base::TimeDelta expiration, |
| const std::string& match, |
| const std::set<mojom::RequestDestination>& match_dest, |
| const std::string& id, |
| const std::optional<base::TimeDelta>& ttl, |
| base::Time last_fetch_time) = 0; |
| }; |
| |
| // Returns a matching dictionary for `url` from `dictionary_info_map`. |
| // This is a template method because SharedDictionaryStorageInMemory and |
| // SharedDictionaryStorageOnDisk are using different class for |
| // DictionaryInfoType. |
| template <class DictionaryInfoType> |
| DictionaryInfoType* GetMatchingDictionaryFromDictionaryInfoMap( |
| std::map< |
| url::SchemeHostPort, |
| std::map<std::tuple<std::string, std::set<mojom::RequestDestination>>, |
| DictionaryInfoType>>& dictionary_info_map, |
| const GURL& url, |
| mojom::RequestDestination destination, |
| std::list<DictionaryInfoType*>& expired_entries) { |
| auto it = dictionary_info_map.find(url::SchemeHostPort(url)); |
| if (it == dictionary_info_map.end()) { |
| return nullptr; |
| } |
| base::Time now = base::Time::Now(); |
| DictionaryInfoType* matched_info = nullptr; |
| for (auto& item : it->second) { |
| DictionaryInfoType& info = item.second; |
| CHECK(std::make_tuple(info.match(), info.match_dest()) == item.first); |
| |
| // Keep track of (but don't match) expired entries. |
| if (info.response_time() + info.expiration() <= now) { |
| expired_entries.push_back(&info); |
| continue; |
| } |
| |
| if (matched_info && |
| ((matched_info->match().size() > info.match().size()) || |
| (matched_info->match().size() == info.match().size() && |
| matched_info->last_fetch_time() > info.last_fetch_time()))) { |
| continue; |
| } |
| // When `match_dest` is empty, we don't check the `destination`. |
| if (!info.match_dest().empty() && |
| !info.match_dest().contains(destination)) { |
| continue; |
| } |
| CHECK(info.matcher()); |
| if (info.matcher()->Match(url)) { |
| matched_info = &info; |
| } |
| } |
| return matched_info; |
| } |
| |
| // Returns the matching registered dictionary in `dictionary_info_map`. This is |
| // used to avoid registering the same dictionary from the disk cache. |
| // This is a template method because SharedDictionaryStorageInMemory and |
| // SharedDictionaryStorageOnDisk are using different class for |
| // DictionaryInfoType. |
| template <class DictionaryInfoType> |
| DictionaryInfoType* FindRegisteredInDictionaryInfoMap( |
| std::map< |
| url::SchemeHostPort, |
| std::map<std::tuple<std::string, std::set<mojom::RequestDestination>>, |
| DictionaryInfoType>>& dictionary_info_map, |
| const GURL& url, |
| base::Time response_time, |
| base::TimeDelta expiration, |
| const std::string& match, |
| const std::set<mojom::RequestDestination>& match_dest, |
| const std::string& id, |
| const std::optional<base::TimeDelta>& ttl) { |
| auto it1 = dictionary_info_map.find(url::SchemeHostPort(url)); |
| if (it1 == dictionary_info_map.end()) { |
| return nullptr; |
| } |
| auto it2 = it1->second.find(std::make_tuple(match, match_dest)); |
| if (it2 == it1->second.end()) { |
| return nullptr; |
| } |
| // The response_time can update on every fetch if "ttl" is used so only |
| // check for the exact match of response_time if a ttl isn't present. |
| if (it2->second.url() == url && |
| (ttl || it2->second.response_time() == response_time) && |
| it2->second.expiration() == expiration && it2->second.id() == id) { |
| return &it2->second; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| } // namespace network |
| |
| #endif // SERVICES_NETWORK_SHARED_DICTIONARY_SHARED_DICTIONARY_STORAGE_H_ |