From b345de9fa8a2bde7a8ca5ba28b1b30e1a66dd1e1 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Mon, 29 Jan 2024 17:17:16 -0500 Subject: [PATCH] signature verification moved to library. --- component/chromium_ipfs_context.cc | 8 +- component/chromium_ipfs_context.h | 4 - component/crypto_api.cc | 58 - component/crypto_api.h | 17 - component/patches/122.0.6182.0.patch | 11249 ---------------- library/include/ipfs_client/context_api.h | 9 +- .../ipfs_client/crypto/signature_verifier.h | 17 + .../{ => crypto}/signing_key_type.h | 2 +- library/include/ipfs_client/test_context.h | 7 - library/src/ipfs_client/context_api.cc | 22 + .../crypto/openssl_signature_verifier.cc | 35 + .../crypto/openssl_signature_verifier.h | 31 + .../{ => crypto}/signing_key_type.cc | 4 +- library/src/ipfs_client/gw/gateway_request.cc | 3 + library/src/ipfs_client/identity_cid.cc | 3 + library/src/ipfs_client/ipns_record.cc | 4 +- .../src/ipfs_client/ipns_record_unittest.cc | 2 +- 17 files changed, 124 insertions(+), 11351 deletions(-) delete mode 100644 component/crypto_api.cc delete mode 100644 component/crypto_api.h delete mode 100644 component/patches/122.0.6182.0.patch create mode 100644 library/include/ipfs_client/crypto/signature_verifier.h rename library/include/ipfs_client/{ => crypto}/signing_key_type.h (88%) create mode 100644 library/src/ipfs_client/crypto/openssl_signature_verifier.cc create mode 100644 library/src/ipfs_client/crypto/openssl_signature_verifier.h rename library/src/ipfs_client/{ => crypto}/signing_key_type.cc (86%) diff --git a/component/chromium_ipfs_context.cc b/component/chromium_ipfs_context.cc index ae21bc12..6b68cf65 100644 --- a/component/chromium_ipfs_context.cc +++ b/component/chromium_ipfs_context.cc @@ -3,8 +3,8 @@ #include "block_http_request.h" #include "chromium_cbor_adapter.h" #include "chromium_json_adapter.h" -#include "crypto_api.h" #include "inter_request_state.h" +#include "ipfs_client/crypto_api.h" #include "preferences.h" #include @@ -78,12 +78,6 @@ void Self::SendHttpRequest(HttpRequestDescription req_inf, auto ptr = std::make_shared(req_inf, cb); ptr->send(loader_factory_); } -bool Self::VerifyKeySignature(SigningKeyType t, - ByteView signature, - ByteView data, - ByteView key_bytes) const { - return crypto_api::VerifySignature(t, signature, data, key_bytes); -} auto Self::ParseCbor(ipfs::ContextApi::ByteView bytes) const -> std::unique_ptr { cbor::Reader::Config cfg; diff --git a/component/chromium_ipfs_context.h b/component/chromium_ipfs_context.h index e48e64ef..216907d8 100644 --- a/component/chromium_ipfs_context.h +++ b/component/chromium_ipfs_context.h @@ -43,10 +43,6 @@ class ChromiumIpfsContext final : public ContextApi { DnsTextCompleteCallback) override; void SendHttpRequest(HttpRequestDescription req_inf, HttpCompleteCallback cb) const override; - bool VerifyKeySignature(SigningKeyType, - ByteView signature, - ByteView data, - ByteView key_bytes) const override; std::unique_ptr ParseCbor(ByteView) const override; std::unique_ptr ParseJson(std::string_view) const override; diff --git a/component/crypto_api.cc b/component/crypto_api.cc deleted file mode 100644 index 205adaba..00000000 --- a/component/crypto_api.cc +++ /dev/null @@ -1,58 +0,0 @@ -#include "crypto_api.h" - -#include "base/logging.h" -#include "components/webcrypto/algorithm_implementations.h" -#include "components/webcrypto/status.h" -#include "third_party/blink/public/platform/web_crypto_key.h" -#include "third_party/boringssl/src/include/openssl/evp.h" - -namespace { -int ToEvpKeyType(ipfs::ContextApi::SigningKeyType t) { - using T = ipfs::ContextApi::SigningKeyType; - switch (t) { - case T::ECDSA: - LOG(ERROR) << "TODO Check on ECDSA key type translation."; - return EVP_PKEY_EC; - case T::Ed25519: - return EVP_PKEY_ED25519; - case T::RSA: - return EVP_PKEY_RSA; - case T::Secp256k1: - LOG(ERROR) << "TODO Check on Secp256k1 key type translation."; - return EVP_PKEY_DSA; - default: - LOG(ERROR) << "Invalid key type: " << static_cast(t); - return EVP_PKEY_NONE; - } -} -} // namespace - -namespace cpto = ipfs::crypto_api; - -bool cpto::VerifySignature(ipfs::ContextApi::SigningKeyType key_type, - ipfs::ByteView signature, - ipfs::ByteView data, - ipfs::ByteView key_bytes) { - auto* key_p = reinterpret_cast(key_bytes.data()); - auto* data_p = reinterpret_cast(data.data()); - auto* sig_p = reinterpret_cast(signature.data()); - auto kt = ToEvpKeyType(key_type); - std::clog << "data:"; - for (auto b : data) { - std::clog << ' ' << std::hex << static_cast(b); - } - std::clog << ' ' << data.size() << " bytes.\n"; - bssl::UniquePtr pkey(EVP_PKEY_new_raw_public_key( - kt, /*engine*/ nullptr, key_p, key_bytes.size())); - bssl::ScopedEVP_MD_CTX ctx; - if (!EVP_DigestVerifyInit(ctx.get(), /*pctx=*/nullptr, /*type=*/nullptr, - /*e=*/nullptr, pkey.get())) { - LOG(ERROR) << "EVP_DigestVerifyInit failed"; - return false; - } - auto result = - EVP_DigestVerify(ctx.get(), sig_p, signature.size(), data_p, data.size()); - // to_verify.data(), to_verify.size()); - VLOG(1) << "EVP_DigestVerify returned " << result; - return result == 1; -} diff --git a/component/crypto_api.h b/component/crypto_api.h deleted file mode 100644 index 3b3861e1..00000000 --- a/component/crypto_api.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef IPFS_VALIDATE_SIGNATURE_H_ -#define IPFS_VALIDATE_SIGNATURE_H_ - -#include -#include -#include "components/webcrypto/algorithm_implementation.h" - -namespace ipfs::crypto_api { - -bool VerifySignature(ipfs::ContextApi::SigningKeyType, - ByteView signature, - ByteView data, - ByteView key); - -} // namespace ipfs::crypto_api - -#endif // IPFS_VALIDATE_SIGNATURE_H_ diff --git a/component/patches/122.0.6182.0.patch b/component/patches/122.0.6182.0.patch deleted file mode 100644 index 00ebf668..00000000 --- a/component/patches/122.0.6182.0.patch +++ /dev/null @@ -1,11249 +0,0 @@ -diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn -index a188528a9e262..88df13b162858 100644 ---- a/chrome/browser/BUILD.gn -+++ b/chrome/browser/BUILD.gn -@@ -40,6 +40,7 @@ import("//rlz/buildflags/buildflags.gni") - import("//sandbox/features.gni") - import("//testing/libfuzzer/fuzzer_test.gni") - import("//third_party/blink/public/public_features.gni") -+import("//third_party/ipfs_client/args.gni") - import("//third_party/protobuf/proto_library.gni") - import("//third_party/webrtc/webrtc.gni") - import("//third_party/widevine/cdm/widevine.gni") -@@ -1912,7 +1913,6 @@ static_library("browser") { - "user_education/user_education_service_factory.h", - ] - } -- - configs += [ - "//build/config/compiler:wexit_time_destructors", - "//build/config:precompiled_headers", -@@ -2604,6 +2604,14 @@ static_library("browser") { - ] - } - -+ if (enable_ipfs) { -+ sources += [ -+ "ipfs_extra_parts.cc", -+ "ipfs_extra_parts.h", -+ ] -+ deps += [ "//components/ipfs" ] -+ } -+ - if (is_chromeos_ash) { - deps += [ "//chrome/browser/screen_ai:screen_ai_dlc_installer" ] - } -diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc -index a7907d8b188d8..68a96934ccf48 100644 ---- a/chrome/browser/about_flags.cc -+++ b/chrome/browser/about_flags.cc -@@ -213,6 +213,7 @@ - #include "third_party/blink/public/common/features_generated.h" - #include "third_party/blink/public/common/forcedark/forcedark_switches.h" - #include "third_party/blink/public/common/switches.h" -+#include "third_party/ipfs_client/ipfs_buildflags.h" - #include "ui/accessibility/accessibility_features.h" - #include "ui/accessibility/accessibility_switches.h" - #include "ui/base/ui_base_features.h" -@@ -308,6 +309,10 @@ - #include "extensions/common/switches.h" - #endif // BUILDFLAG(ENABLE_EXTENSIONS) - -+#if BUILDFLAG(ENABLE_IPFS) -+#include "components/ipfs/ipfs_features.h" -+#endif -+ - #if BUILDFLAG(ENABLE_PDF) - #include "pdf/pdf_features.h" - #endif -@@ -9731,6 +9736,14 @@ const FeatureEntry kFeatureEntries[] = { - flag_descriptions::kOmitCorsClientCertDescription, kOsAll, - FEATURE_VALUE_TYPE(network::features::kOmitCorsClientCert)}, - -+#if BUILDFLAG(ENABLE_IPFS) -+ {"enable-ipfs", -+ flag_descriptions::kEnableIpfsName, -+ flag_descriptions::kEnableIpfsDescription, -+ kOsMac | kOsWin | kOsLinux,//TODO: These are the only variants currently getting built, but that is not likely to remain the case -+ FEATURE_VALUE_TYPE(ipfs::kEnableIpfs)}, -+#endif -+ - {"use-idna2008-non-transitional", - flag_descriptions::kUseIDNA2008NonTransitionalName, - flag_descriptions::kUseIDNA2008NonTransitionalDescription, kOsAll, -diff --git a/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc b/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc -index 4c88614c68c25..f8bb12a3b0c2e 100644 ---- a/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc -+++ b/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc -@@ -10,6 +10,8 @@ - #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h" - #include "chrome/browser/external_protocol/external_protocol_handler.h" - #include "chrome/browser/profiles/profile.h" -+#include "third_party/ipfs_client/ipfs_buildflags.h" -+ - #if BUILDFLAG(IS_ANDROID) - #include "chrome/browser/profiles/profile_android.h" - #endif -@@ -18,6 +20,9 @@ - #include "chrome/browser/ui/android/omnibox/jni_headers/ChromeAutocompleteSchemeClassifier_jni.h" - #endif - #include "components/custom_handlers/protocol_handler_registry.h" -+#if BUILDFLAG(ENABLE_IPFS) -+#include "components/ipfs/ipfs_features.h" -+#endif - #include "content/public/common/url_constants.h" - #include "url/url_util.h" - -@@ -55,12 +60,20 @@ ChromeAutocompleteSchemeClassifier::GetInputTypeForScheme( - if (scheme.empty()) { - return metrics::OmniboxInputType::EMPTY; - } -- if (base::IsStringASCII(scheme) && -- (ProfileIOData::IsHandledProtocol(scheme) || -- base::EqualsCaseInsensitiveASCII(scheme, content::kViewSourceScheme) || -- base::EqualsCaseInsensitiveASCII(scheme, url::kJavaScriptScheme) || -- base::EqualsCaseInsensitiveASCII(scheme, url::kDataScheme))) { -- return metrics::OmniboxInputType::URL; -+ if (base::IsStringASCII(scheme)) { -+ if (ProfileIOData::IsHandledProtocol(scheme) || -+ base::EqualsCaseInsensitiveASCII(scheme, content::kViewSourceScheme) || -+ base::EqualsCaseInsensitiveASCII(scheme, url::kJavaScriptScheme) || -+ base::EqualsCaseInsensitiveASCII(scheme, url::kDataScheme)) { -+ return metrics::OmniboxInputType::URL; -+ } -+#if BUILDFLAG(ENABLE_IPFS) -+ if (base::FeatureList::IsEnabled(ipfs::kEnableIpfs) && -+ (base::EqualsCaseInsensitiveASCII(scheme, "ipfs") || base::EqualsCaseInsensitiveASCII(scheme, "ipns")) -+ ) { -+ return metrics::OmniboxInputType::URL; -+ } -+#endif - } - - // Also check for schemes registered via registerProtocolHandler(), which -diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc -index d3d67d83a514e..a5b1ef6339211 100644 ---- a/chrome/browser/chrome_content_browser_client.cc -+++ b/chrome/browser/chrome_content_browser_client.cc -@@ -378,6 +378,7 @@ - #include "third_party/blink/public/common/switches.h" - #include "third_party/blink/public/mojom/browsing_topics/browsing_topics.mojom.h" - #include "third_party/blink/public/public_buildflags.h" -+#include "third_party/ipfs_client/ipfs_buildflags.h" - #include "third_party/widevine/cdm/buildflags.h" - #include "ui/base/clipboard/clipboard_format_type.h" - #include "ui/base/l10n/l10n_util.h" -@@ -500,6 +501,13 @@ - #include "chrome/browser/fuchsia/chrome_browser_main_parts_fuchsia.h" - #endif - -+#if BUILDFLAG(ENABLE_IPFS) -+#include "chrome/browser/ipfs_extra_parts.h" -+#include "components/ipfs/interceptor.h" -+#include "components/ipfs/ipfs_features.h" -+#include "components/ipfs/url_loader_factory.h" -+#endif -+ - #if BUILDFLAG(IS_CHROMEOS) - #include "base/debug/leak_annotations.h" - #include "chrome/browser/apps/app_service/app_install/app_install_navigation_throttle.h" -@@ -1712,6 +1720,11 @@ ChromeContentBrowserClient::CreateBrowserMainParts(bool is_integration_test) { - main_parts->AddParts( - std::make_unique()); - -+#if BUILDFLAG(ENABLE_IPFS) -+ if (base::FeatureList::IsEnabled(ipfs::kEnableIpfs)) { -+ main_parts->AddParts(std::make_unique()); -+ } -+#endif - return main_parts; - } - -@@ -6084,12 +6097,25 @@ void ChromeContentBrowserClient:: - const absl::optional& request_initiator_origin, - NonNetworkURLLoaderFactoryMap* factories) { - #if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(ENABLE_EXTENSIONS) || \ -- !BUILDFLAG(IS_ANDROID) -+ !BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_IPFS) - content::RenderFrameHost* frame_host = - RenderFrameHost::FromID(render_process_id, render_frame_id); - WebContents* web_contents = WebContents::FromRenderFrameHost(frame_host); - #endif // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(ENABLE_EXTENSIONS) || \ -- // !BUILDFLAG(IS_ANDROID) -+ // !BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_IPFS) -+#if BUILDFLAG(ENABLE_IPFS) -+ if (base::FeatureList::IsEnabled(ipfs::kEnableIpfs)) { -+ network::mojom::URLLoaderFactory* default_factory = g_browser_process->system_network_context_manager()->GetURLLoaderFactory(); -+ auto* context = web_contents->GetBrowserContext(); -+ ipfs::IpfsURLLoaderFactory::Create( -+ factories, -+ context, -+ default_factory, -+ GetSystemNetworkContext(), -+ Profile::FromBrowserContext(context)->GetPrefs() -+ ); -+ } -+#endif // BUILDFLAG(ENABLE_IPFS) - - #if BUILDFLAG(IS_CHROMEOS_ASH) - if (web_contents) { -@@ -6231,6 +6257,11 @@ ChromeContentBrowserClient::WillCreateURLLoaderRequestInterceptors( - scoped_refptr navigation_response_task_runner) { - std::vector> - interceptors; -+#if BUILDFLAG(ENABLE_IPFS) -+ if (base::FeatureList::IsEnabled(ipfs::kEnableIpfs)) { -+ interceptors.push_back(std::make_unique(g_browser_process->system_network_context_manager()->GetURLLoaderFactory(), GetSystemNetworkContext())); -+ } -+#endif - #if BUILDFLAG(ENABLE_OFFLINE_PAGES) - interceptors.push_back( - std::make_unique( -diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json -index 0b51e78fcb8b9..9571b2c92c57f 100644 ---- a/chrome/browser/flag-metadata.json -+++ b/chrome/browser/flag-metadata.json -@@ -2948,6 +2948,11 @@ - "owners": [ "hanxi@chromium.org", "wychen@chromium.org" ], - "expiry_milestone": 130 - }, -+ { -+ "name": "enable-ipfs", -+ "owners": [ "//components/ipfs/OWNERS" ], -+ "expiry_milestone": 150 -+ }, - { - "name": "enable-isolated-sandboxed-iframes", - "owners": [ "wjmaclean@chromium.org", "alexmos@chromium.org", "creis@chromium.org" ], -diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc -index b2992e30f9811..f92d8a322b634 100644 ---- a/chrome/browser/flag_descriptions.cc -+++ b/chrome/browser/flag_descriptions.cc -@@ -288,6 +288,11 @@ const char kEnableBenchmarkingDescription[] = - "after 3 restarts. On the third restart, the flag will appear to be off " - "but the effect is still active."; - -+#if BUILDFLAG(ENABLE_IPFS) -+extern const char kEnableIpfsName[] = "Enable IPFS"; -+extern const char kEnableIpfsDescription[] = "Enable ipfs:// and ipns:// URLs"; -+#endif -+ - const char kPreloadingOnPerformancePageName[] = - "Preloading Settings on Performance Page"; - const char kPreloadingOnPerformancePageDescription[] = -diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h -index ad76d832395a1..438facecff519 100644 ---- a/chrome/browser/flag_descriptions.h -+++ b/chrome/browser/flag_descriptions.h -@@ -23,6 +23,7 @@ - #include "pdf/buildflags.h" - #include "printing/buildflags/buildflags.h" - #include "third_party/blink/public/common/buildflags.h" -+#include "third_party/ipfs_client/ipfs_buildflags.h" - - // This file declares strings used in chrome://flags. These messages are not - // translated, because instead of end-users they target Chromium developers and -@@ -179,6 +180,11 @@ extern const char kDownloadWarningImprovementsDescription[]; - extern const char kEnableBenchmarkingName[]; - extern const char kEnableBenchmarkingDescription[]; - -+#if BUILDFLAG(ENABLE_IPFS) -+extern const char kEnableIpfsName[]; -+extern const char kEnableIpfsDescription[]; -+#endif -+ - #if BUILDFLAG(USE_FONTATIONS_BACKEND) - extern const char kFontationsFontBackendName[]; - extern const char kFontationsFontBackendDescription[]; -diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc -index fc9fcf1ff478a..800961b3c8767 100644 ---- a/chrome/browser/prefs/browser_prefs.cc -+++ b/chrome/browser/prefs/browser_prefs.cc -@@ -190,6 +190,7 @@ - #include "printing/buildflags/buildflags.h" - #include "rlz/buildflags/buildflags.h" - #include "third_party/abseil-cpp/absl/types/optional.h" -+#include "third_party/ipfs_client/ipfs_buildflags.h" - - #if BUILDFLAG(ENABLE_BACKGROUND_MODE) - #include "chrome/browser/background/background_mode_manager.h" -@@ -241,6 +242,11 @@ - #include "chrome/browser/pdf/pdf_pref_names.h" - #endif // BUILDFLAG(ENABLE_PDF) - -+#if BUILDFLAG(ENABLE_IPFS) -+#include "components/ipfs/ipfs_features.h" -+#include "components/ipfs/preferences.h" -+#endif -+ - #if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE) - #include "chrome/browser/screen_ai/pref_names.h" - #endif -@@ -1658,6 +1664,11 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry, - IncognitoModePrefs::RegisterProfilePrefs(registry); - invalidation::PerUserTopicSubscriptionManager::RegisterProfilePrefs(registry); - invalidation::InvalidatorRegistrarWithMemory::RegisterProfilePrefs(registry); -+#if BUILDFLAG(ENABLE_IPFS) -+ if (base::FeatureList::IsEnabled(ipfs::kEnableIpfs)) { -+ ipfs::RegisterPreferences(registry); -+ } -+#endif - language::LanguagePrefs::RegisterProfilePrefs(registry); - login_detection::prefs::RegisterProfilePrefs(registry); - lookalikes::RegisterProfilePrefs(registry); -diff --git a/chrome/common/chrome_content_client.cc b/chrome/common/chrome_content_client.cc -index 246ec9c5c911f..5d66d133a7907 100644 ---- a/chrome/common/chrome_content_client.cc -+++ b/chrome/common/chrome_content_client.cc -@@ -296,6 +296,12 @@ void ChromeContentClient::AddAdditionalSchemes(Schemes* schemes) { - #if BUILDFLAG(IS_ANDROID) - schemes->local_schemes.push_back(url::kContentScheme); - #endif -+ for ( const char* ip_s : {"ipfs", "ipns"} ) { -+ schemes->standard_schemes.push_back(ip_s); -+ schemes->cors_enabled_schemes.push_back(ip_s); -+ schemes->secure_schemes.push_back(ip_s); -+ schemes->csp_bypassing_schemes.push_back(ip_s); -+ } - } - - std::u16string ChromeContentClient::GetLocalizedString(int message_id) { -diff --git a/components/cbor/reader.cc b/components/cbor/reader.cc -index 306ba52fa4944..6b13b3a679a65 100644 ---- a/components/cbor/reader.cc -+++ b/components/cbor/reader.cc -@@ -22,7 +22,7 @@ - namespace cbor { - - namespace constants { --const char kUnsupportedMajorType[] = "Unsupported major type."; -+const char kUnsupportedMajorType[] = "Unsupported major type operation."; - } - - namespace { -@@ -156,7 +156,11 @@ absl::optional Reader::DecodeCompleteDataItem(const Config& config, - case Value::Type::FLOAT_VALUE: - // Floating point values also go here since they are also type 7. - return DecodeToSimpleValueOrFloat(*header, config); -- case Value::Type::TAG: // We explicitly don't support TAG. -+ case Value::Type::TAG: -+ if (config.parse_tags) { -+ return ReadTagContent(*header, config, max_nesting_level); -+ } -+ break; - case Value::Type::NONE: - case Value::Type::INVALID_UTF8: - break; -@@ -347,6 +351,17 @@ absl::optional Reader::ReadByteStringContent( - return Value(std::move(cbor_byte_string)); - } - -+absl::optional Reader::ReadTagContent( -+ const Reader::DataItemHeader& header, -+ const Config& config, -+ int max_nesting_level) { -+ auto tagged_content = DecodeCompleteDataItem(config, max_nesting_level); -+ if (tagged_content.has_value()) { -+ tagged_content.value().SetTag(header.value); -+ } -+ return tagged_content; -+} -+ - absl::optional Reader::ReadArrayContent( - const Reader::DataItemHeader& header, - const Config& config, -diff --git a/components/cbor/reader.h b/components/cbor/reader.h -index f0b43a5517528..a57e277a1bc66 100644 ---- a/components/cbor/reader.h -+++ b/components/cbor/reader.h -@@ -130,6 +130,11 @@ class CBOR_EXPORT Reader { - // during decoding will set raise the `UNSUPPORTED_FLOATING_POINT_VALUE` - // error. - bool allow_floating_point = false; -+ -+ // If the parser encounters a TAG element, should it be parsed out and -+ // the tag value saved (true), or should the entire node and its content -+ // be discarded (false) -+ bool parse_tags = false; - }; - - Reader(const Reader&) = delete; -@@ -204,6 +209,9 @@ class CBOR_EXPORT Reader { - absl::optional ReadMapContent(const DataItemHeader& header, - const Config& config, - int max_nesting_level); -+ absl::optional ReadTagContent(const DataItemHeader& header, -+ const Config& config, -+ int max_nesting_level); - absl::optional ReadByte(); - absl::optional> ReadBytes(uint64_t num_bytes); - bool IsKeyInOrder(const Value& new_key, -diff --git a/components/cbor/reader_unittest.cc b/components/cbor/reader_unittest.cc -index 83d44a48d6dfa..a6ec5299b3241 100644 ---- a/components/cbor/reader_unittest.cc -+++ b/components/cbor/reader_unittest.cc -@@ -1451,5 +1451,42 @@ TEST(CBORReaderTest, AllowInvalidUTF8) { - EXPECT_FALSE(cbor); - EXPECT_EQ(Reader::DecoderError::INVALID_UTF8, error); - } -+TEST(CBORReaderTest, RejectsTagUnderDefaultConfig) { -+ static const uint8_t kTaggedCbor[] = { -+ 0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x71, 0x12, 0x20, 0x69, 0xea, 0x07, -+ 0x40, 0xf9, 0x80, 0x7a, 0x28, 0xf4, 0xd9, 0x32, 0xc6, 0x2e, 0x7c, 0x1c, -+ 0x83, 0xbe, 0x05, 0x5e, 0x55, 0x07, 0x2c, 0x90, 0x26, 0x6a, 0xb3, 0xe7, -+ 0x9d, 0xf6, 0x3a, 0x36, 0x5b -+ }; -+ Reader::Config config; -+ absl::optional cbor = Reader::Read(kTaggedCbor, config); -+ EXPECT_FALSE(cbor.has_value()); -+} -+TEST(CBORReaderTest, ReadsTagWhenConfiguredToDoSo) { -+ static const uint8_t kTaggedCbor[] = { -+ 0xd8, 0x2a, 0x58, 0x25, 0x00, 0x01, 0x71, 0x12, 0x20, 0x69, 0xea, 0x07, -+ 0x40, 0xf9, 0x80, 0x7a, 0x28, 0xf4, 0xd9, 0x32, 0xc6, 0x2e, 0x7c, 0x1c, -+ 0x83, 0xbe, 0x05, 0x5e, 0x55, 0x07, 0x2c, 0x90, 0x26, 0x6a, 0xb3, 0xe7, -+ 0x9d, 0xf6, 0x3a, 0x36, 0x5b -+ }; -+ Reader::Config config; -+ config.parse_tags = true; -+ absl::optional cbor = Reader::Read(kTaggedCbor, config); -+ EXPECT_TRUE(cbor.has_value()); -+ auto& v = cbor.value(); -+ EXPECT_TRUE(v.has_tag()); -+ EXPECT_EQ(v.GetTag(),42UL); -+ EXPECT_TRUE(v.is_bytestring()); -+ EXPECT_EQ(v.type(), Value::Type::BYTE_STRING); -+ auto& bytes = v.GetBytestring(); -+ EXPECT_EQ(bytes.size(), 37UL); -+ EXPECT_EQ(bytes.at(0), 0x00);//identity multibase (e.g. not base-encoded, bytes are themselves) -+ EXPECT_EQ(bytes.at(1), 0x01);//CID version 1 -+ EXPECT_EQ(bytes.at(2), 0x71);//codec = dag-cbor -+ EXPECT_EQ(bytes.at(3), 0x12);//multihash = 18 = sha2-256 -+ EXPECT_EQ(bytes.at(4), 0x20);//hash length = 32 bytes -+ EXPECT_EQ(bytes.at(5), 0x69);//first byte of hash digest -+ EXPECT_EQ(bytes.at(36),0x5b);//last byte of hash digest -+} - - } // namespace cbor -diff --git a/components/cbor/values.cc b/components/cbor/values.cc -index 02498209c820e..34055aef24cfe 100644 ---- a/components/cbor/values.cc -+++ b/components/cbor/values.cc -@@ -66,32 +66,34 @@ Value::Value(Type type) : type_(type) { - NOTREACHED(); - } - --Value::Value(SimpleValue in_simple) -- : type_(Type::SIMPLE_VALUE), simple_value_(in_simple) { -+Value::Value(SimpleValue in_simple, uint64_t tag) -+ : type_(Type::SIMPLE_VALUE), simple_value_(in_simple), tag_(tag) { - CHECK(static_cast(in_simple) >= 20 && static_cast(in_simple) <= 23); - } - --Value::Value(bool boolean_value) : type_(Type::SIMPLE_VALUE) { -+Value::Value(bool boolean_value, uint64_t tag) : type_(Type::SIMPLE_VALUE), tag_(tag) { - simple_value_ = boolean_value ? Value::SimpleValue::TRUE_VALUE - : Value::SimpleValue::FALSE_VALUE; - } - --Value::Value(double float_value) -- : type_(Type::FLOAT_VALUE), float_value_(float_value) {} -+Value::Value(double float_value, uint64_t tag) -+ : type_(Type::FLOAT_VALUE), float_value_(float_value), tag_(tag) {} - --Value::Value(int integer_value) -- : Value(base::checked_cast(integer_value)) {} -+Value::Value(int integer_value, uint64_t tag) -+ : Value(base::checked_cast(integer_value), tag) {} - --Value::Value(int64_t integer_value) : integer_value_(integer_value) { -+Value::Value(int64_t integer_value, uint64_t tag) : integer_value_(integer_value), tag_(tag) { - type_ = integer_value >= 0 ? Type::UNSIGNED : Type::NEGATIVE; - } - --Value::Value(base::span in_bytes) -+Value::Value(base::span in_bytes, uint64_t tag) - : type_(Type::BYTE_STRING), -- bytestring_value_(in_bytes.begin(), in_bytes.end()) {} -+ bytestring_value_(in_bytes.begin(), in_bytes.end()), -+ tag_(tag) -+ {} - --Value::Value(base::span in_bytes, Type type) -- : type_(type), bytestring_value_(in_bytes.begin(), in_bytes.end()) { -+Value::Value(base::span in_bytes, Type type, uint64_t tag) -+ : type_(type), bytestring_value_(in_bytes.begin(), in_bytes.end()), tag_(tag) { - DCHECK(type_ == Type::BYTE_STRING || type_ == Type::INVALID_UTF8); - } - -@@ -117,7 +119,8 @@ Value::Value(std::string&& in_string, Type type) noexcept : type_(type) { - } - } - --Value::Value(base::StringPiece in_string, Type type) : type_(type) { -+Value::Value(base::StringPiece in_string, Type type, uint64_t tag) -+: type_(type), tag_(tag) { - switch (type_) { - case Type::STRING: - new (&string_value_) std::string(); -@@ -133,16 +136,18 @@ Value::Value(base::StringPiece in_string, Type type) : type_(type) { - } - } - --Value::Value(const ArrayValue& in_array) : type_(Type::ARRAY), array_value_() { -+Value::Value(const ArrayValue& in_array, uint64_t tag) -+: type_(Type::ARRAY), array_value_(), tag_(tag) { - array_value_.reserve(in_array.size()); - for (const auto& val : in_array) - array_value_.emplace_back(val.Clone()); - } - --Value::Value(ArrayValue&& in_array) noexcept -- : type_(Type::ARRAY), array_value_(std::move(in_array)) {} -+Value::Value(ArrayValue&& in_array, uint64_t tag) noexcept -+ : type_(Type::ARRAY), array_value_(std::move(in_array)), tag_(tag) {} - --Value::Value(const MapValue& in_map) : type_(Type::MAP), map_value_() { -+Value::Value(const MapValue& in_map, uint64_t tag) -+: type_(Type::MAP), map_value_(), tag_(tag) { - map_value_.reserve(in_map.size()); - for (const auto& it : in_map) - map_value_.emplace_hint(map_value_.end(), it.first.Clone(), -@@ -168,31 +173,36 @@ Value Value::Clone() const { - case Type::NONE: - return Value(); - case Type::INVALID_UTF8: -- return Value(bytestring_value_, Type::INVALID_UTF8); -+ return Value(bytestring_value_, Type::INVALID_UTF8, tag_); - case Type::UNSIGNED: - case Type::NEGATIVE: -- return Value(integer_value_); -+ return Value(integer_value_, tag_); - case Type::BYTE_STRING: -- return Value(bytestring_value_); -+ return Value(bytestring_value_, tag_); - case Type::STRING: -- return Value(string_value_); -+ return Value(string_value_, Type::STRING, tag_); - case Type::ARRAY: -- return Value(array_value_); -+ return Value(array_value_, tag_); - case Type::MAP: -- return Value(map_value_); -+ return Value(map_value_, tag_); - case Type::TAG: - NOTREACHED() << constants::kUnsupportedMajorType; - return Value(); - case Type::SIMPLE_VALUE: -- return Value(simple_value_); -+ return Value(simple_value_, tag_); - case Type::FLOAT_VALUE: -- return Value(float_value_); -+ return Value(float_value_, tag_); - } - - NOTREACHED(); - return Value(); - } - -+Value& Value::SetTag(uint64_t tag) noexcept { -+ tag_ = tag; -+ return *this; -+} -+ - Value::SimpleValue Value::GetSimpleValue() const { - CHECK(is_simple()); - return simple_value_; -@@ -258,9 +268,14 @@ const Value::BinaryValue& Value::GetInvalidUTF8() const { - return bytestring_value_; - } - -+uint64_t Value::GetTag() const { -+ CHECK(has_tag()); -+ return tag_; -+} -+ - void Value::InternalMoveConstructFrom(Value&& that) { - type_ = that.type_; -- -+ tag_ = that.tag_; - switch (type_) { - case Type::UNSIGNED: - case Type::NEGATIVE: -diff --git a/components/cbor/values.h b/components/cbor/values.h -index d81ef5607c55a..10216a8dcdc57 100644 ---- a/components/cbor/values.h -+++ b/components/cbor/values.h -@@ -127,28 +127,29 @@ class CBOR_EXPORT Value { - - explicit Value(Type type); - -- explicit Value(SimpleValue in_simple); -- explicit Value(bool boolean_value); -- explicit Value(double in_float); -+ explicit Value(SimpleValue in_simple, uint64_t tag = NO_TAG); -+ explicit Value(bool boolean_value, uint64_t tag = NO_TAG); -+ explicit Value(double in_float, uint64_t tag = NO_TAG); - -- explicit Value(int integer_value); -- explicit Value(int64_t integer_value); -+ explicit Value(int integer_value, uint64_t tag = NO_TAG); -+ explicit Value(int64_t integer_value, uint64_t tag = NO_TAG); - explicit Value(uint64_t integer_value) = delete; - -- explicit Value(base::span in_bytes); -+ explicit Value(base::span in_bytes, uint64_t tag = NO_TAG); - explicit Value(BinaryValue&& in_bytes) noexcept; - - explicit Value(const char* in_string, Type type = Type::STRING); - explicit Value(std::string&& in_string, Type type = Type::STRING) noexcept; -- explicit Value(base::StringPiece in_string, Type type = Type::STRING); -+ explicit Value(base::StringPiece in_string, Type type = Type::STRING, uint64_t tag = NO_TAG); - -- explicit Value(const ArrayValue& in_array); -- explicit Value(ArrayValue&& in_array) noexcept; -+ explicit Value(const ArrayValue& in_array, uint64_t tag = NO_TAG); -+ explicit Value(ArrayValue&& in_array, uint64_t tag = NO_TAG) noexcept; - -- explicit Value(const MapValue& in_map); -+ explicit Value(const MapValue& in_map, uint64_t tag = NO_TAG); - explicit Value(MapValue&& in_map) noexcept; - - Value& operator=(Value&& that) noexcept; -+ Value& SetTag(uint64_t) noexcept; - - Value(const Value&) = delete; - Value& operator=(const Value&) = delete; -@@ -179,6 +180,7 @@ class CBOR_EXPORT Value { - bool is_string() const { return type() == Type::STRING; } - bool is_array() const { return type() == Type::ARRAY; } - bool is_map() const { return type() == Type::MAP; } -+ bool has_tag() const { return tag_ != NO_TAG; } - - // These will all fatally assert if the type doesn't match. - SimpleValue GetSimpleValue() const; -@@ -194,12 +196,13 @@ class CBOR_EXPORT Value { - const ArrayValue& GetArray() const; - const MapValue& GetMap() const; - const BinaryValue& GetInvalidUTF8() const; -+ uint64_t GetTag() const; - - private: - friend class Reader; - // This constructor allows INVALID_UTF8 values to be created, which only - // |Reader| and InvalidUTF8StringValueForTesting() may do. -- Value(base::span in_bytes, Type type); -+ Value(base::span in_bytes, Type type, uint64_t tag = NO_TAG); - - Type type_; - -@@ -213,6 +216,11 @@ class CBOR_EXPORT Value { - MapValue map_value_; - }; - -+ //This value specified as Invalid, -+ // used here to represent absence of TAG -+ constexpr static uint64_t NO_TAG = 0xFFFF; -+ uint64_t tag_ = NO_TAG; -+ - void InternalMoveConstructFrom(Value&& that); - void InternalCleanup(); - }; -diff --git a/components/cbor/writer.cc b/components/cbor/writer.cc -index bb22754d36a07..aae4027836377 100644 ---- a/components/cbor/writer.cc -+++ b/components/cbor/writer.cc -@@ -47,6 +47,9 @@ bool Writer::EncodeCBOR(const Value& node, - if (max_nesting_level < 0) - return false; - -+ if (node.has_tag()) { -+ StartItem(Value::Type::TAG, node.GetTag()); -+ } - switch (node.type()) { - case Value::Type::NONE: { - StartItem(Value::Type::BYTE_STRING, 0); -diff --git a/components/cbor/writer_unittest.cc b/components/cbor/writer_unittest.cc -index e3bffe20734bc..0ed569ae164a0 100644 ---- a/components/cbor/writer_unittest.cc -+++ b/components/cbor/writer_unittest.cc -@@ -522,4 +522,31 @@ TEST(CBORWriterTest, OverlyNestedCBOR) { - EXPECT_FALSE(Writer::Write(Value(map), 4).has_value()); - } - -+TEST(CBORWriterTest, CanWriteTag) { -+ std::array content{ -+ 0x00, 0x01, 0x71, 0x12, 0x20, -+ 0x69, 0xea, 0x07, 0x40, 0xf9, -+ 0x80, 0x7a, 0x28, 0xf4, 0xd9, -+ 0x32, 0xc6, 0x2e, 0x7c, 0x1c, -+ 0x83, 0xbe, 0x05, 0x5e, 0x55, -+ 0x07, 0x2c, 0x90, 0x26, 0x6a, -+ 0xb3, 0xe7, 0x9d, 0xf6, 0x3a, -+ 0x36, 0x5b -+ }; -+ Value to_write(content); -+ to_write.SetTag(42); -+ auto result = Writer::Write(to_write); -+ EXPECT_TRUE(result.has_value()); -+ auto& bytes = result.value(); -+ EXPECT_EQ(bytes.size(), 41UL); -+ EXPECT_EQ(bytes.at(0), 0xd8); -+ EXPECT_EQ(bytes.at(1), 0x2a); -+ EXPECT_EQ(bytes.at(2), 0x58); -+ EXPECT_EQ(bytes.at(3), 0x25); -+ for (auto i = 0UL; i < content.size(); ++i) { -+ ASSERT_LT(i + 4UL, bytes.size()); -+ ASSERT_EQ(content.at(i), bytes.at(i+4UL)); -+ } -+} -+ - } // namespace cbor -diff --git a/components/ipfs/BUILD.gn b/components/ipfs/BUILD.gn -new file mode 100644 -index 0000000000000..572e93e493e7a ---- /dev/null -+++ b/components/ipfs/BUILD.gn -@@ -0,0 +1,62 @@ -+import("//testing/test.gni") -+import("//third_party/ipfs_client/args.gni") -+ -+if (enable_ipfs) { -+ -+ component("ipfs") { -+ sources = [ -+ "block_http_request.cc", -+ "block_http_request.h", -+ "cache_requestor.cc", -+ "cache_requestor.h", -+ "chromium_cbor_adapter.cc", -+ "chromium_cbor_adapter.h", -+ "chromium_ipfs_context.cc", -+ "chromium_ipfs_context.h", -+ "chromium_json_adapter.cc", -+ "chromium_json_adapter.h", -+ "crypto_api.cc", -+ "crypto_api.h", -+ "dns_txt_request.cc", -+ "dns_txt_request.h", -+ "export.h", -+ "inter_request_state.cc", -+ "inter_request_state.h", -+ "interceptor.cc", -+ "interceptor.h", -+ "ipfs_features.cc", -+ "ipfs_features.h", -+ "ipfs_url_loader.cc", -+ "ipfs_url_loader.h", -+ "preferences.cc", -+ "preferences.h", -+ "url_loader_factory.cc", -+ "url_loader_factory.h", -+ ] -+ defines = [ ] -+ include_dirs = [ -+ ".", -+ "ipfs_client", -+ "ipfs_client/unix_fs", -+ ] -+ deps = [ -+ "//content", -+ "//crypto", -+ "//base", -+ "//components/cbor", -+ "//components/prefs", -+ "//components/webcrypto:webcrypto", -+ "//mojo/public/cpp/bindings", -+ "//services/network:network_service", -+ "//services/network/public/cpp:cpp", -+ "//services/network/public/mojom:url_loader_base", -+ "//url", -+ "//third_party/blink/public:blink", -+ ] -+ public_deps = [ -+ "//third_party/ipfs_client", -+ ] -+ defines = [ "IS_IPFS_IMPL" ] -+ } -+ -+} -diff --git a/components/ipfs/README.md b/components/ipfs/README.md -new file mode 100644 -index 0000000000000..1333ed77b7e1e ---- /dev/null -+++ b/components/ipfs/README.md -@@ -0,0 +1 @@ -+TODO -diff --git a/components/ipfs/block_http_request.cc b/components/ipfs/block_http_request.cc -new file mode 100644 -index 0000000000000..c48ddd8f77c8d ---- /dev/null -+++ b/components/ipfs/block_http_request.cc -@@ -0,0 +1,102 @@ -+#include "block_http_request.h" -+ -+#include -+#include -+#include -+ -+using Self = ipfs::BlockHttpRequest; -+ -+namespace { -+constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation = -+ net::DefineNetworkTrafficAnnotation("ipfs_gateway_request", R"( -+ semantics { -+ sender: "IPFS component" -+ description: -+ "Sends a request to an IPFS gateway." -+ trigger: -+ "Processing of an ipfs:// or ipns:// URL." -+ data: "None" -+ destination: WEBSITE -+ } -+ policy { -+ cookies_allowed: NO -+ setting: "EnableIpfs" -+ } -+ )"); -+} -+ -+Self::BlockHttpRequest(ipfs::HttpRequestDescription req_inf, -+ HttpCompleteCallback cb) -+ : inf_{req_inf}, callback_{cb} {} -+Self::~BlockHttpRequest() noexcept {} -+ -+void Self::send(raw_ptr loader_factory) { -+ auto req = std::make_unique(); -+ req->url = GURL{inf_.url}; -+ req->priority = net::HIGHEST; // TODO -+ if (!inf_.accept.empty()) { -+ req->headers.SetHeader("Accept", inf_.accept); -+ } -+ using L = network::SimpleURLLoader; -+ loader_ = L::Create(std::move(req), kTrafficAnnotation, FROM_HERE); -+ loader_->SetTimeoutDuration(base::Seconds(inf_.timeout_seconds)); -+ loader_->SetAllowHttpErrorResults(true); -+ loader_->SetOnResponseStartedCallback( -+ base::BindOnce(&Self::OnResponseHead, base::Unretained(this))); -+ auto bound = base::BindOnce(&Self::OnResponse, base::Unretained(this), -+ shared_from_this()); -+ DCHECK(loader_factory); -+ if (auto sz = inf_.max_response_size) { -+ loader_->DownloadToString(loader_factory, std::move(bound), sz.value()); -+ } else { -+ loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(loader_factory, -+ std::move(bound)); -+ } -+} -+void Self::OnResponse(std::shared_ptr, -+ std::unique_ptr body) { -+ DCHECK(loader_); -+ int status; -+ switch (loader_->NetError()) { -+ case net::Error::OK: -+ status = 200; -+ break; -+ case net::Error::ERR_TIMED_OUT: -+ VLOG(2) << "HTTP request timed out: " << inf_.url << " after " -+ << inf_.timeout_seconds << "s."; -+ status = 408; -+ break; -+ default: -+ VLOG(2) << "NetErr " << loader_->NetError() << " for " << inf_.url; -+ status = 500; -+ } -+ // auto sz = body ? body->size() : 0UL; -+ auto const* head = loader_->ResponseInfo(); -+ if (head) { -+ OnResponseHead({}, *head); -+ } -+ auto sp = status_line_.find(' '); -+ if (sp < status_line_.size()) { -+ VLOG(2) << "HTTP response status='" << status_line_ << "'."; -+ status = std::atoi(status_line_.c_str() + sp + 1); -+ } -+ if (body) { -+ callback_(status, *body, header_accessor_); -+ } else { -+ callback_(status, "", header_accessor_); -+ } -+} -+void Self::OnResponseHead( -+ GURL const&, -+ network::mojom::URLResponseHead const& response_head) { -+ if (!response_head.headers) { -+ return; -+ } -+ auto head = response_head.headers; -+ status_line_ = head->GetStatusLine(); -+ header_accessor_ = [head](std::string_view k) { -+ std::string val; -+ head->EnumerateHeader(nullptr, k, &val); -+ return val; -+ }; -+} -diff --git a/components/ipfs/block_http_request.h b/components/ipfs/block_http_request.h -new file mode 100644 -index 0000000000000..a34d88b7a54cf ---- /dev/null -+++ b/components/ipfs/block_http_request.h -@@ -0,0 +1,46 @@ -+#ifndef IPFS_BLOCK_HTTP_REQUEST_H_ -+#define IPFS_BLOCK_HTTP_REQUEST_H_ -+ -+#include -+ -+#include -+#include -+ -+namespace network { -+struct ResourceRequest; -+class SimpleURLLoader; -+} // namespace network -+namespace network::mojom { -+class URLLoaderFactory; -+class URLResponseHead; -+} // namespace network::mojom -+class GURL; -+ -+namespace ipfs { -+class BlockHttpRequest : public std::enable_shared_from_this { -+ // TODO ween oneself off of SimpleURLLoader -+ // std::array buffer_; -+ std::unique_ptr loader_; -+ -+ public: -+ using HttpCompleteCallback = ipfs::ContextApi::HttpCompleteCallback; -+ BlockHttpRequest(ipfs::HttpRequestDescription, HttpCompleteCallback); -+ ~BlockHttpRequest() noexcept; -+ -+ void send(raw_ptr); -+ -+ private: -+ ipfs::HttpRequestDescription const inf_; -+ HttpCompleteCallback callback_; -+ std::string status_line_; -+ ContextApi::HeaderAccess header_accessor_ = [](auto) { -+ return std::string{}; -+ }; -+ -+ void OnResponseHead(GURL const&, network::mojom::URLResponseHead const&); -+ void OnResponse(std::shared_ptr, -+ std::unique_ptr body); -+}; -+} // namespace ipfs -+ -+#endif // IPFS_BLOCK_HTTP_REQUEST_H_ -diff --git a/components/ipfs/cache_requestor.cc b/components/ipfs/cache_requestor.cc -new file mode 100644 -index 0000000000000..ce446b608080a ---- /dev/null -+++ b/components/ipfs/cache_requestor.cc -@@ -0,0 +1,219 @@ -+#include "cache_requestor.h" -+ -+#include "chromium_ipfs_context.h" -+#include "inter_request_state.h" -+ -+#include -+#include -+ -+using Self = ipfs::CacheRequestor; -+namespace dc = disk_cache; -+ -+std::string_view Self::name() const { -+ return "Disk Cache"; -+} -+Self::CacheRequestor(InterRequestState& state, base::FilePath base) -+ : state_{state} { -+ if (!base.empty()) { -+ path_ = base.AppendASCII("IpfsBlockCache"); -+ } -+ Start(); -+} -+void Self::Start() { -+ if (pending_) { -+ return; -+ } -+ auto result = dc::CreateCacheBackend( -+ net::CacheType::DISK_CACHE, net::CACHE_BACKEND_DEFAULT, {}, path_, 0, -+ dc::ResetHandling::kNeverReset, -+ // dc::ResetHandling::kResetOnError, -+ nullptr, base::BindOnce(&Self::Assign, base::Unretained(this))); -+ LOG(INFO) << "Start(" << result.net_error << ')' << result.net_error; -+ pending_ = result.net_error == net::ERR_IO_PENDING; -+ if (!pending_) { -+ Assign(std::move(result)); -+ } -+} -+Self::~CacheRequestor() noexcept = default; -+ -+void Self::Assign(dc::BackendResult res) { -+ pending_ = false; -+ if (res.net_error == net::OK) { -+ LOG(INFO) << "Initialized disk cache"; -+ cache_.swap(res.backend); -+ } else { -+ LOG(ERROR) << "Trouble opening " << name() << ": " << res.net_error; -+ Start(); -+ } -+} -+auto Self::handle(RequestPtr req) -> HandleOutcome { -+ if (pending_) { -+ return HandleOutcome::NOT_HANDLED; -+ } -+ Task task; -+ task.key = req->main_param; -+ task.request = req; -+ StartFetch(task, net::MAXIMUM_PRIORITY); -+ return HandleOutcome::PENDING; -+} -+void Self::StartFetch(Task& task, net::RequestPriority priority) { -+ if (pending_) { -+ Start(); -+ Miss(task); -+ return; -+ } -+ auto bound = base::BindOnce(&Self::OnOpen, base::Unretained(this), task); -+ auto res = cache_->OpenEntry(task.key, priority, std::move(bound)); -+ if (res.net_error() != net::ERR_IO_PENDING) { -+ OnOpen(task, std::move(res)); -+ } -+} -+void Self::Miss(Task& task) { -+ if (task.request) { -+ VLOG(2) << "Cache miss on " << task.request->debug_string(); -+ auto req = task.request; -+ task.request->Hook([this, req](std::string_view bytes) { -+ Store(req->main_param, "TODO", std::string{bytes}); -+ }); -+ forward(req); -+ } -+} -+namespace { -+std::shared_ptr GetEntry(dc::EntryResult& result) { -+ auto* e = result.ReleaseEntry(); -+ auto deleter = [](auto e) { -+ if (e) { -+ e->Close(); -+ } -+ }; -+ return {e, deleter}; -+} -+} // namespace -+ -+void Self::OnOpen(Task task, dc::EntryResult res) { -+ VLOG(2) << "OnOpen(" << res.net_error() << ")"; -+ if (res.net_error() != net::OK) { -+ VLOG(2) << "Failed to find " << task.key << " in " << name(); -+ Miss(task); -+ return; -+ } -+ task.entry = GetEntry(res); -+ DCHECK(task.entry); -+ task.buf = base::MakeRefCounted(2 * 1024 * 1024); -+ DCHECK(task.buf); -+ auto bound = -+ base::BindOnce(&Self::OnHeaderRead, base::Unretained(this), task); -+ auto code = task.entry->ReadData(0, 0, task.buf.get(), task.buf->size(), -+ std::move(bound)); -+ if (code != net::ERR_IO_PENDING) { -+ OnHeaderRead(task, code); -+ } -+} -+void Self::OnHeaderRead(Task task, int code) { -+ if (code <= 0) { -+ LOG(ERROR) << "Failed to read headers for entry " << task.key << " in " -+ << name() << " " << code; -+ // Miss(task); -+ // return; -+ } -+ task.header.assign(task.buf->data(), static_cast(code)); -+ auto bound = base::BindOnce(&Self::OnBodyRead, base::Unretained(this), task); -+ code = task.entry->ReadData(1, 0, task.buf.get(), task.buf->size(), -+ std::move(bound)); -+ if (code != net::ERR_IO_PENDING) { -+ OnBodyRead(task, code); -+ } -+} -+void Self::OnBodyRead(Task task, int code) { -+ if (code <= 0) { -+ LOG(INFO) << "Failed to read body for entry " << task.key << " in " -+ << name(); -+ Miss(task); -+ return; -+ } -+ task.body.assign(task.buf->data(), static_cast(code)); -+ if (task.request) { -+ task.SetHeaders(name()); -+ if (task.request->RespondSuccessfully(task.body, api_)) { -+ VLOG(2) << "Cache hit on " << task.key << " for " -+ << task.request->debug_string(); -+ } else { -+ LOG(ERROR) << "Had a BAD cached response for " << task.key; -+ Expire(task.key); -+ Miss(task); -+ } -+ } -+} -+void Self::Store(std::string cid, std::string headers, std::string body) { -+ VLOG(1) << "Store(" << name() << ',' << cid << ',' << headers.size() << ',' -+ << body.size() << ')'; -+ auto bound = base::BindOnce(&Self::OnEntryCreated, base::Unretained(this), -+ cid, headers, body); -+ auto res = cache_->OpenOrCreateEntry(cid, net::LOW, std::move(bound)); -+ if (res.net_error() != net::ERR_IO_PENDING) { -+ OnEntryCreated(cid, headers, body, std::move(res)); -+ } -+} -+void Self::OnEntryCreated(std::string cid, -+ std::string headers, -+ std::string body, -+ disk_cache::EntryResult result) { -+ if (result.opened()) { -+ VLOG(1) << "No need to write an entry for " << cid << " in " << name() -+ << " as it is already there and immutable."; -+ } else if (result.net_error() == net::OK) { -+ auto entry = GetEntry(result); -+ auto buf = base::MakeRefCounted(headers); -+ DCHECK(buf); -+ auto bound = base::BindOnce(&Self::OnHeaderWritten, base::Unretained(this), -+ buf, body, entry); -+ auto code = -+ entry->WriteData(0, 0, buf.get(), buf->size(), std::move(bound), true); -+ if (code != net::ERR_IO_PENDING) { -+ OnHeaderWritten(buf, body, entry, code); -+ } -+ } else { -+ LOG(ERROR) << "Failed to create an entry for " << cid << " in " << name(); -+ } -+} -+void Self::OnHeaderWritten(scoped_refptr buf, -+ std::string body, -+ std::shared_ptr entry, -+ int code) { -+ if (code < 0) { -+ LOG(ERROR) << "Failed to write header info for " << entry->GetKey() -+ << " in " << name(); -+ return; -+ } -+ buf = base::MakeRefCounted(body); -+ DCHECK(buf); -+ auto f = [](scoped_refptr, int c) { -+ VLOG(1) << "body write " << c; -+ }; -+ auto bound = base::BindOnce(f, buf); -+ entry->WriteData(1, 0, buf.get(), buf->size(), std::move(bound), true); -+} -+ -+void Self::Task::SetHeaders(std::string_view source) { -+ auto heads = base::MakeRefCounted(header); -+ DCHECK(heads); -+ std::string value{"blockcache-"}; -+ value.append(key); -+ value.append(";desc=\"Load from local browser block cache\";dur="); -+ auto dur = base::TimeTicks::Now() - start; -+ value.append(std::to_string(dur.InMillisecondsRoundedUp())); -+ heads->SetHeader("Server-Timing", value); -+ VLOG(2) << "From cache: Server-Timing: " << value << "; Block-Cache-" << key -+ << ": " << source; -+ heads->SetHeader("Block-Cache-" + key, {source.data(), source.size()}); -+ header = heads->raw_headers(); -+} -+void Self::Expire(std::string const& key) { -+ if (cache_ && !pending_) { -+ cache_->DoomEntry(key, net::RequestPriority::LOWEST, base::DoNothing()); -+ } -+} -+ -+Self::Task::Task() = default; -+Self::Task::Task(Task const&) = default; -+Self::Task::~Task() noexcept = default; -diff --git a/components/ipfs/cache_requestor.h b/components/ipfs/cache_requestor.h -new file mode 100644 -index 0000000000000..b8c31d371ecb4 ---- /dev/null -+++ b/components/ipfs/cache_requestor.h -@@ -0,0 +1,71 @@ -+#ifndef CACHE_REQUESTOR_H_ -+#define CACHE_REQUESTOR_H_ -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include -+ -+#include -+ -+namespace ipfs { -+ -+class BlockStorage; -+class InterRequestState; -+ -+class CacheRequestor : public gw::Requestor { -+ public: -+ CacheRequestor(InterRequestState&, base::FilePath); -+ ~CacheRequestor() noexcept override; -+ void Store(std::string cid, std::string headers, std::string body); -+ void Expire(std::string const& key); -+ -+ std::string_view name() const override; -+ -+ private: -+ struct Task { -+ Task(); -+ Task(Task const&); -+ ~Task() noexcept; -+ std::string key; -+ base::TimeTicks start = base::TimeTicks::Now(); -+ std::string header; -+ std::string body; -+ scoped_refptr buf; -+ std::shared_ptr entry; -+ gw::RequestPtr request; -+ -+ void SetHeaders(std::string_view); -+ }; -+ raw_ref state_; -+ std::unique_ptr cache_; -+ bool pending_ = false; -+ base::FilePath path_; -+ -+ void Start(); -+ -+ void StartFetch(Task& t, net::RequestPriority priority); -+ void Assign(disk_cache::BackendResult); -+ void OnOpen(Task, disk_cache::EntryResult); -+ void OnHeaderRead(Task, int); -+ void OnBodyRead(Task, int); -+ -+ void OnEntryCreated(std::string c, -+ std::string h, -+ std::string b, -+ disk_cache::EntryResult); -+ void OnHeaderWritten(scoped_refptr buf, -+ std::string body, -+ std::shared_ptr entry, -+ int); -+ void Miss(Task&); -+ HandleOutcome handle(RequestPtr) override; -+}; -+} // namespace ipfs -+ -+#endif // CACHE_REQUESTOR_H_ -diff --git a/components/ipfs/chromium_cbor_adapter.cc b/components/ipfs/chromium_cbor_adapter.cc -new file mode 100644 -index 0000000000000..d7d43b81be96b ---- /dev/null -+++ b/components/ipfs/chromium_cbor_adapter.cc -@@ -0,0 +1,91 @@ -+#include "chromium_cbor_adapter.h" -+ -+#include -+ -+using Self = ipfs::ChromiumCborAdapter; -+ -+bool Self::is_map() const { -+ return cbor_.is_map(); -+} -+bool Self::is_array() const { -+ return cbor_.is_array(); -+} -+auto Self::at(std::string_view key) const -> std::unique_ptr { -+ if (is_map()) { -+ auto& m = cbor_.GetMap(); -+ auto it = m.find(cbor::Value{base::StringPiece{key}}); -+ if (m.end() != it) { -+ return std::make_unique(it->second.Clone()); -+ } -+ } -+ return {}; -+} -+std::optional Self::as_unsigned() const { -+ if (cbor_.is_unsigned()) { -+ return cbor_.GetUnsigned(); -+ } -+ return std::nullopt; -+} -+std::optional Self::as_signed() const { -+ if (cbor_.is_integer()) { -+ return cbor_.GetInteger(); -+ } -+ return {}; -+} -+std::optional Self::as_float() const { -+ return {}; -+} -+ -+std::optional Self::as_string() const { -+ if (cbor_.is_string()) { -+ return cbor_.GetString(); -+ } -+ return std::nullopt; -+} -+auto Self::as_bytes() const -> std::optional> { -+ if (cbor_.is_bytestring()) { -+ return cbor_.GetBytestring(); -+ } -+ return std::nullopt; -+} -+auto Self::as_link() const -> std::optional { -+ VLOG(1) << "Trying to do an as_link(" << static_cast(cbor_.type()) << ',' << std::boolalpha << cbor_.has_tag() << ")"; -+ if (!cbor_.has_tag() || cbor_.GetTag() != 42UL || !cbor_.is_bytestring()) { -+ VLOG(1) << "This is not a link."; -+ return std::nullopt; -+ } -+ auto& bytes = cbor_.GetBytestring(); -+ auto* byte_ptr = reinterpret_cast(bytes.data()) + 1; -+ auto result = Cid(ByteView{byte_ptr, bytes.size() - 1UL}); -+ if (result.valid()) { -+ return result; -+ } else { -+ LOG(ERROR) << "Unable to decode bytes from DAG-CBOR Link as CID."; -+ return std::nullopt; -+ } -+} -+std::optional Self::as_bool() const { -+ if (cbor_.is_bool()) { -+ return cbor_.GetBool(); -+ } -+ return std::nullopt; -+} -+void Self::iterate_map(MapElementCallback cb) const { -+ auto& m = cbor_.GetMap(); -+ for (auto& [k,v] : m) { -+ cb(k.GetString(), Self{v}); -+ } -+} -+void Self::iterate_array(ArrayElementCallback cb) const { -+ auto& a = cbor_.GetArray(); -+ for (auto& e : a) { -+ cb(Self{e}); -+ } -+} -+ -+Self::ChromiumCborAdapter(cbor::Value const& v) : cbor_{v.Clone()} {} -+Self::ChromiumCborAdapter(cbor::Value&& v) : cbor_{std::move(v)} {} -+Self::ChromiumCborAdapter(ChromiumCborAdapter const& rhs) -+ : cbor_{rhs.cbor_.Clone()} {} -+ -+Self::~ChromiumCborAdapter() noexcept {} -diff --git a/components/ipfs/chromium_cbor_adapter.h b/components/ipfs/chromium_cbor_adapter.h -new file mode 100644 -index 0000000000000..65c2d746e6630 ---- /dev/null -+++ b/components/ipfs/chromium_cbor_adapter.h -@@ -0,0 +1,33 @@ -+#ifndef IPFS_CHROMIUM_CBOR_ADAPTER_H_ -+#define IPFS_CHROMIUM_CBOR_ADAPTER_H_ -+ -+#include -+ -+#include -+ -+namespace ipfs { -+class ChromiumCborAdapter final : public DagCborValue { -+ cbor::Value cbor_; -+ -+ std::unique_ptr at(std::string_view) const override; -+ std::optional as_unsigned() const override; -+ std::optional as_signed() const override; -+ std::optional as_float() const override; -+ std::optional as_string() const override; -+ std::optional> as_bytes() const override; -+ std::optional as_link() const override; -+ std::optional as_bool() const override; -+ bool is_map() const override; -+ bool is_array() const override; -+ void iterate_map(MapElementCallback) const override; -+ void iterate_array(ArrayElementCallback) const override; -+ -+ public: -+ ChromiumCborAdapter(cbor::Value&&); -+ ChromiumCborAdapter(cbor::Value const&); -+ ChromiumCborAdapter(ChromiumCborAdapter const& rhs); -+ ~ChromiumCborAdapter() noexcept override; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_CHROMIUM_CBOR_ADAPTER_H_ -diff --git a/components/ipfs/chromium_ipfs_context.cc b/components/ipfs/chromium_ipfs_context.cc -new file mode 100644 -index 0000000000000..92a56994633bf ---- /dev/null -+++ b/components/ipfs/chromium_ipfs_context.cc -@@ -0,0 +1,133 @@ -+#include "chromium_ipfs_context.h" -+ -+#include "block_http_request.h" -+#include "chromium_cbor_adapter.h" -+#include "chromium_json_adapter.h" -+#include "crypto_api.h" -+#include "inter_request_state.h" -+#include "preferences.h" -+ -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include -+ -+#include -+#include -+ -+#include -+ -+#include -+#include -+ -+ -+using Self = ipfs::ChromiumIpfsContext; -+ -+void Self::SetLoaderFactory(network::mojom::URLLoaderFactory& lf) { -+ loader_factory_ = &lf; -+} -+ -+std::string Self::MimeType(std::string extension, -+ std::string_view content, -+ std::string const& url) const { -+ std::string result; -+ auto fp_ext = base::FilePath::FromUTF8Unsafe(extension).value(); -+ VLOG(2) << "extension=" << extension << "content.size()=" << content.size() -+ << "(as-if) url for mime type:" << url; -+ if (extension.empty()) { -+ result.clear(); -+ } else if (net::GetWellKnownMimeTypeFromExtension(fp_ext, &result)) { -+ VLOG(2) << "Got " << result << " from extension " << extension << " for " -+ << url; -+ } else { -+ result.clear(); -+ } -+ auto head_size = std::min(content.size(), 999'999UL); -+ if (net::SniffMimeType({content.data(), head_size}, GURL{url}, result, -+ net::ForceSniffFileUrlsForHtml::kDisabled, &result)) { -+ VLOG(2) << "Got " << result << " from content of " << url; -+ } -+ if (result.empty() || result == "application/octet-stream") { -+ net::SniffMimeTypeFromLocalData({content.data(), head_size}, &result); -+ VLOG(2) << "Falling all the way back to content type " << result; -+ } -+ return result; -+} -+std::string Self::UnescapeUrlComponent(std::string_view comp) const { -+ using Rule = base::UnescapeRule; -+ auto rules = Rule::PATH_SEPARATORS | -+ Rule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | Rule::SPACES; -+ auto result = base::UnescapeURLComponent({comp.data(), comp.size()}, rules); -+ return result; -+} -+void Self::SendDnsTextRequest(std::string host, -+ DnsTextResultsCallback res, -+ DnsTextCompleteCallback don) { -+ if (dns_reqs_.find(host) != dns_reqs_.end()) { -+ LOG(ERROR) << "Requested resolution of DNSLink host " << host -+ << " multiple times."; -+ } -+ auto don_wrap = [don, this, host]() { -+ don(); -+ LOG(INFO) << "Finished resolving " << host << " via DNSLink"; -+ dns_reqs_.erase(host); -+ }; -+ auto* nc = state_->network_context(); -+ dns_reqs_[host] = std::make_unique(host, res, don_wrap, nc); -+} -+void Self::SendHttpRequest(HttpRequestDescription req_inf, -+ HttpCompleteCallback cb) const { -+ DCHECK(loader_factory_); -+ auto ptr = std::make_shared(req_inf, cb); -+ ptr->send(loader_factory_); -+} -+bool Self::VerifyKeySignature(SigningKeyType t, -+ ByteView signature, -+ ByteView data, -+ ByteView key_bytes) const { -+ return crypto_api::VerifySignature(static_cast(t), signature, -+ data, key_bytes); -+} -+auto Self::ParseCbor(ipfs::ContextApi::ByteView bytes) const -+ -> std::unique_ptr { -+ cbor::Reader::Config cfg; -+ cfg.parse_tags = true; -+ auto parsed = cbor::Reader::Read(as_octets(bytes), cfg); -+ if (parsed.has_value()) { -+ return std::make_unique(std::move(parsed.value())); -+ } -+ LOG(ERROR) << "Failed to parse CBOR."; -+ return {}; -+} -+auto Self::ParseJson(std::string_view j_str) const -+ -> std::unique_ptr { -+ auto d = base::JSONReader::Read(j_str, base::JSON_ALLOW_TRAILING_COMMAS); -+ if (d) { -+ return std::make_unique(std::move(d.value())); -+ } -+ return {}; -+} -+unsigned int Self::GetGatewayRate(std::string_view prefix) { -+ return rates_.GetRate(prefix); -+} -+void Self::SetGatewayRate(std::string_view prefix, unsigned int new_rate) { -+ rates_.SetRate(prefix, new_rate); -+} -+auto Self::GetGateway(std::size_t index) const -> std::optional { -+ auto [gw, r] = rates_.at(index); -+ if (gw) { -+ return GatewaySpec{*gw, r}; -+ } -+ return std::nullopt; -+} -+ -+Self::ChromiumIpfsContext(InterRequestState& state, PrefService* prefs) -+ : state_{state}, rates_{prefs} {} -+Self::~ChromiumIpfsContext() noexcept { -+ LOG(WARNING) << "API dtor - are all URIs loaded?"; -+} -+ -diff --git a/components/ipfs/chromium_ipfs_context.h b/components/ipfs/chromium_ipfs_context.h -new file mode 100644 -index 0000000000000..b274746efac42 ---- /dev/null -+++ b/components/ipfs/chromium_ipfs_context.h -@@ -0,0 +1,66 @@ -+#ifndef IPFS_CHROMIUM_IPFS_CONTEXT_H_ -+#define IPFS_CHROMIUM_IPFS_CONTEXT_H_ -+ -+#include "dns_txt_request.h" -+#include "preferences.h" -+ -+#include -+#include -+ -+#include -+#include -+ -+#include -+ -+#include -+ -+class PrefService; -+ -+namespace network { -+class SimpleURLLoader; -+namespace mojom { -+class URLLoaderFactory; -+} -+} // namespace network -+ -+namespace ipfs { -+class InterRequestState; -+class IpfsRequest; -+class NetworkRequestor; -+ -+class ChromiumIpfsContext final : public ContextApi { -+ raw_ptr loader_factory_ = nullptr; -+ raw_ref state_; -+ std::map> dns_reqs_; -+ GatewayRates rates_; -+ -+ std::string MimeType(std::string extension, -+ std::string_view content, -+ std::string const& url) const override; -+ std::string UnescapeUrlComponent(std::string_view) const override; -+ void SendDnsTextRequest(std::string, -+ DnsTextResultsCallback, -+ DnsTextCompleteCallback) override; -+ void SendHttpRequest(HttpRequestDescription req_inf, -+ HttpCompleteCallback cb) const override; -+ bool VerifyKeySignature(SigningKeyType, -+ ByteView signature, -+ ByteView data, -+ ByteView key_bytes) const override; -+ -+ std::unique_ptr ParseCbor(ByteView) const override; -+ std::unique_ptr ParseJson(std::string_view) const override; -+ -+ std::optional GetGateway(std::size_t index) const override; -+ unsigned int GetGatewayRate(std::string_view) override; -+ void SetGatewayRate(std::string_view, unsigned int) override; -+ -+ public: -+ ChromiumIpfsContext(InterRequestState&, PrefService* prefs); -+ ~ChromiumIpfsContext() noexcept override; -+ void SetLoaderFactory(network::mojom::URLLoaderFactory&); -+}; -+ -+} // namespace ipfs -+ -+#endif // IPFS_CHROMIUM_IPFS_CONTEXT_H_ -diff --git a/components/ipfs/chromium_json_adapter.cc b/components/ipfs/chromium_json_adapter.cc -new file mode 100644 -index 0000000000000..92c1c19aa35ce ---- /dev/null -+++ b/components/ipfs/chromium_json_adapter.cc -@@ -0,0 +1,48 @@ -+#include "chromium_json_adapter.h" -+ -+using Self = ipfs::ChromiumJsonAdapter; -+ -+Self::ChromiumJsonAdapter(base::Value d) : data_(std::move(d)) {} -+Self::~ChromiumJsonAdapter() noexcept {} -+std::string Self::pretty_print() const { -+ return data_.DebugString(); -+} -+std::optional Self::get_if_string() const { -+ auto* s = data_.GetIfString(); -+ if (s) { -+ return *s; -+ } else { -+ return std::nullopt; -+ } -+} -+auto Self::operator[](std::string_view k) const -+ -> std::unique_ptr { -+ if (auto* m = data_.GetIfDict()) { -+ if (auto* v = m->Find(k)) { -+ return std::make_unique(v->Clone()); -+ } -+ } -+ return {}; -+} -+bool Self::iterate_list(std::function cb) const { -+ auto* l = data_.GetIfList(); -+ if (!l) { -+ return false; -+ } -+ for (auto& v : *l) { -+ Self wrap(v.Clone()); -+ cb(wrap); -+ } -+ return true; -+} -+std::optional> Self::object_keys() const { -+ auto* m = data_.GetIfDict(); -+ if (!m) { -+ return std::nullopt; -+ } -+ std::vector rv; -+ for (auto [k, v] : *m) { -+ rv.push_back(k); -+ } -+ return rv; -+} -\ No newline at end of file -diff --git a/components/ipfs/chromium_json_adapter.h b/components/ipfs/chromium_json_adapter.h -new file mode 100644 -index 0000000000000..8e5e26aa3150e ---- /dev/null -+++ b/components/ipfs/chromium_json_adapter.h -@@ -0,0 +1,22 @@ -+#ifndef IPFS_CHROMIUM_JSON_ADAPTER_H_ -+#define IPFS_CHROMIUM_JSON_ADAPTER_H_ -+ -+#include -+#include -+ -+namespace ipfs { -+class ChromiumJsonAdapter final : public ipfs::DagJsonValue { -+ base::Value data_; -+ std::string pretty_print() const override; -+ std::unique_ptr operator[](std::string_view) const override; -+ std::optional get_if_string() const override; -+ std::optional> object_keys() const override; -+ bool iterate_list(std::function) const override; -+ -+ public: -+ ChromiumJsonAdapter(base::Value); -+ ~ChromiumJsonAdapter() noexcept override; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_CHROMIUM_JSON_ADAPTER_H_ -diff --git a/components/ipfs/crypto_api.cc b/components/ipfs/crypto_api.cc -new file mode 100644 -index 0000000000000..d15a63f1f577c ---- /dev/null -+++ b/components/ipfs/crypto_api.cc -@@ -0,0 +1,62 @@ -+#include "crypto_api.h" -+ -+#include "base/logging.h" -+#include "components/webcrypto/algorithm_implementations.h" -+#include "components/webcrypto/status.h" -+#include "third_party/blink/public/platform/web_crypto_key.h" -+#include "third_party/boringssl/src/include/openssl/evp.h" -+ -+namespace { -+int ToEvpKeyType(ipfs::ipns::KeyType t) { -+ using ipfs::ipns::KeyType; -+ switch (t) { -+ case KeyType::ECDSA: -+ LOG(ERROR) << "TODO Check on ECDSA key type translation."; -+ return EVP_PKEY_EC; -+ case KeyType::Ed25519: -+ return EVP_PKEY_ED25519; -+ case KeyType::RSA: -+ return EVP_PKEY_RSA; -+ case KeyType::Secp256k1: -+ LOG(ERROR) << "TODO Check on Secp256k1 key type translation."; -+ return EVP_PKEY_DSA; -+ default: -+ LOG(ERROR) << "Invalid key type: " << static_cast(t); -+ return EVP_PKEY_NONE; -+ } -+} -+} // namespace -+ -+namespace cpto = ipfs::crypto_api; -+ -+bool cpto::VerifySignature(ipfs::ipns::KeyType key_type, -+ ipfs::ByteView signature, -+ ipfs::ByteView data, -+ ipfs::ByteView key_bytes) { -+ auto* key_p = reinterpret_cast(key_bytes.data()); -+ auto* data_p = reinterpret_cast(data.data()); -+ auto* sig_p = reinterpret_cast(signature.data()); -+ auto kt = ToEvpKeyType(key_type); -+ std::clog << "data:"; -+ for (auto b : data) { -+ std::clog << ' ' << std::hex << static_cast(b); -+ } -+ std::clog << ' ' << data.size() << " bytes.\n"; -+ bssl::UniquePtr pkey(EVP_PKEY_new_raw_public_key( -+ kt, /*engine*/ nullptr, key_p, key_bytes.size())); -+ bssl::ScopedEVP_MD_CTX ctx; -+ if (!EVP_DigestVerifyInit(ctx.get(), /*pctx=*/nullptr, /*type=*/nullptr, -+ /*e=*/nullptr, pkey.get())) { -+ LOG(ERROR) << "EVP_DigestVerifyInit failed"; -+ return false; -+ } -+ // auto* prefix = reinterpret_cast( -+ // "\x69\x70\x6e\x73\x2d\x73\x69\x67\x6e\x61\x74\x75\x72\x65\x3a"); -+ // std::basic_string to_verify = prefix; -+ // to_verify.append(data_p, data.size()); -+ auto result = -+ EVP_DigestVerify(ctx.get(), sig_p, signature.size(), data_p, data.size()); -+ // to_verify.data(), to_verify.size()); -+ LOG(INFO) << "EVP_DigestVerify returned " << result; -+ return result == 1; -+} -diff --git a/components/ipfs/crypto_api.h b/components/ipfs/crypto_api.h -new file mode 100644 -index 0000000000000..1363bb1fec6df ---- /dev/null -+++ b/components/ipfs/crypto_api.h -@@ -0,0 +1,22 @@ -+#ifndef IPFS_VALIDATE_SIGNATURE_H_ -+#define IPFS_VALIDATE_SIGNATURE_H_ -+ -+#include "components/webcrypto/algorithm_implementation.h" -+ -+#include "third_party/ipfs_client/keys.pb.h" -+ -+#include -+ -+namespace ipfs::crypto_api { -+/* -+using Algo = std::pair>; -+Algo GetAlgo(ipfs::ipns::KeyType); -+*/ -+bool VerifySignature(ipfs::ipns::KeyType, -+ ByteView signature, -+ ByteView data, -+ ByteView key); -+} // namespace ipfs::crypto_api -+ -+#endif // IPFS_VALIDATE_SIGNATURE_H_ -diff --git a/components/ipfs/dns_txt_request.cc b/components/ipfs/dns_txt_request.cc -new file mode 100644 -index 0000000000000..c7e8e667d5f05 ---- /dev/null -+++ b/components/ipfs/dns_txt_request.cc -@@ -0,0 +1,39 @@ -+#include "dns_txt_request.h" -+ -+#include -+#include -+ -+namespace moj = network::mojom; -+using Self = ipfs::DnsTxtRequest; -+ -+Self::DnsTxtRequest(std::string host, -+ ipfs::ContextApi::DnsTextResultsCallback res, -+ ipfs::ContextApi::DnsTextCompleteCallback don, -+ moj::NetworkContext* network_context) -+ : results_callback_{res}, completion_callback_{don} { -+ auto params = moj::ResolveHostParameters::New(); -+ params->dns_query_type = net::DnsQueryType::TXT; -+ params->initial_priority = net::RequestPriority::HIGHEST; -+ params->source = net::HostResolverSource::ANY; -+ params->cache_usage = moj::ResolveHostParameters_CacheUsage::STALE_ALLOWED; -+ params->secure_dns_policy = moj::SecureDnsPolicy::ALLOW; -+ params->purpose = moj::ResolveHostParameters::Purpose::kUnspecified; -+ LOG(INFO) << "Querying DNS for TXT records on " << host; -+ auto hrh = moj::HostResolverHost::NewHostPortPair({host, 0}); -+ auto nak = net::NetworkAnonymizationKey::CreateTransient(); -+ network_context->ResolveHost(std::move(hrh), nak, std::move(params), -+ recv_.BindNewPipeAndPassRemote()); -+} -+Self::~DnsTxtRequest() {} -+ -+void Self::OnTextResults(std::vector const& results) { -+ LOG(INFO) << "Hit " << results.size() << " DNS TXT results."; -+ results_callback_(results); -+} -+void Self::OnComplete(int32_t result, -+ const ::net::ResolveErrorInfo&, -+ const absl::optional<::net::AddressList>&, -+ const absl::optional&) { -+ LOG(INFO) << "DNS Results done with code: " << result; -+ completion_callback_(); -+} -diff --git a/components/ipfs/dns_txt_request.h b/components/ipfs/dns_txt_request.h -new file mode 100644 -index 0000000000000..a6c72e467d11f ---- /dev/null -+++ b/components/ipfs/dns_txt_request.h -@@ -0,0 +1,36 @@ -+#ifndef IPFS_DNS_TXT_REQUEST_H_ -+#define IPFS_DNS_TXT_REQUEST_H_ -+ -+#include -+ -+#include -+#include -+ -+namespace network::mojom { -+class NetworkContext; -+} -+ -+namespace ipfs { -+class DnsTxtRequest final : public network::ResolveHostClientBase { -+ ipfs::ContextApi::DnsTextResultsCallback results_callback_; -+ ipfs::ContextApi::DnsTextCompleteCallback completion_callback_; -+ mojo::Receiver recv_{this}; -+ -+ using Endpoints = std::vector<::net::HostResolverEndpointResult>; -+ void OnTextResults(std::vector const&) override; -+ void OnComplete(int32_t result, -+ ::net::ResolveErrorInfo const&, -+ absl::optional<::net::AddressList> const&, -+ absl::optional const&) override; -+ -+ public: -+ DnsTxtRequest(std::string, -+ ipfs::ContextApi::DnsTextResultsCallback, -+ ipfs::ContextApi::DnsTextCompleteCallback, -+ network::mojom::NetworkContext*); -+ DnsTxtRequest(DnsTxtRequest&&) = delete; -+ ~DnsTxtRequest() noexcept override; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_DNS_TXT_REQUEST_H_ -diff --git a/components/ipfs/export.h b/components/ipfs/export.h -new file mode 100644 -index 0000000000000..8161da33aca16 ---- /dev/null -+++ b/components/ipfs/export.h -@@ -0,0 +1,16 @@ -+#ifndef IPFS_EXPORT_H_ -+#define IPFS_EXPORT_H_ -+ -+#if __has_include() -+#include -+#else -+ -+#ifndef IS_IPFS_IMPL -+#if !defined(COMPONENT_EXPORT) -+#define COMPONENT_EXPORT(IPFS) -+#endif -+#endif -+ -+#endif -+ -+#endif // IPFS_EXPORT_H_ -diff --git a/components/ipfs/inter_request_state.cc b/components/ipfs/inter_request_state.cc -new file mode 100644 -index 0000000000000..4f704d5ebce46 ---- /dev/null -+++ b/components/ipfs/inter_request_state.cc -@@ -0,0 +1,74 @@ -+#include "inter_request_state.h" -+ -+#include "chromium_ipfs_context.h" -+#include "preferences.h" -+ -+#include -+#include "content/public/browser/browser_context.h" -+ -+#include -+#include -+#include -+#include -+ -+using Self = ipfs::InterRequestState; -+ -+namespace { -+constexpr char user_data_key[] = "ipfs_request_userdata"; -+} -+ -+void Self::CreateForBrowserContext(content::BrowserContext* c, PrefService* p) { -+ DCHECK(c); -+ DCHECK(p); -+ LOG(INFO) << "Creating new IPFS state for this browser context."; -+ auto owned = std::make_unique(c->GetPath(), p); -+ c->SetUserData(user_data_key, std::move(owned)); -+} -+auto Self::FromBrowserContext(content::BrowserContext* context) -+ -> InterRequestState& { -+ if (!context) { -+ LOG(WARNING) << "No browser context! Using a default IPFS state."; -+ static ipfs::InterRequestState static_state({}, {}); -+ return static_state; -+ } -+ base::SupportsUserData::Data* existing = context->GetUserData(user_data_key); -+ if (existing) { -+ VLOG(2) << "Re-using existing IPFS state."; -+ return *static_cast(existing); -+ } else { -+ LOG(ERROR) << "Browser context has no IPFS state! It must be set earlier!"; -+ static ipfs::InterRequestState static_state({}, {}); -+ return static_state; -+ } -+} -+std::shared_ptr Self::api() { -+ return api_; -+} -+auto Self::cache() -> std::shared_ptr& { -+ if (!cache_) { -+ cache_ = std::make_shared(*this, disk_path_); -+ } -+ return cache_; -+} -+auto Self::orchestrator() -> Orchestrator& { -+ if (!orc_) { -+ auto rtor = -+ gw::default_requestor(Gateways::DefaultGateways(), cache(), api()); -+ orc_ = std::make_shared(rtor, api()); -+ } -+ return *orc_; -+} -+void Self::network_context(network::mojom::NetworkContext* val) { -+ network_context_ = val; -+} -+network::mojom::NetworkContext* Self::network_context() const { -+ return network_context_; -+} -+Self::InterRequestState(base::FilePath p, PrefService* prefs) -+ : api_{std::make_shared(*this, prefs)}, disk_path_{p} { -+ DCHECK(prefs); -+} -+Self::~InterRequestState() noexcept { -+ network_context_ = nullptr; -+ cache_.reset(); -+} -diff --git a/components/ipfs/inter_request_state.h b/components/ipfs/inter_request_state.h -new file mode 100644 -index 0000000000000..3471cfe5d15c9 ---- /dev/null -+++ b/components/ipfs/inter_request_state.h -@@ -0,0 +1,51 @@ -+#ifndef IPFS_INTER_REQUEST_STATE_H_ -+#define IPFS_INTER_REQUEST_STATE_H_ -+ -+#include "cache_requestor.h" -+ -+#include "ipfs_client/gateways.h" -+#include "ipfs_client/ipns_names.h" -+#include "ipfs_client/orchestrator.h" -+ -+#include "base/supports_user_data.h" -+#include "services/network/network_context.h" -+ -+class PrefService; -+ -+namespace content { -+class BrowserContext; -+} -+ -+namespace ipfs { -+class Scheduler; -+class ChromiumIpfsContext; -+class COMPONENT_EXPORT(IPFS) InterRequestState -+ : public base::SupportsUserData::Data { -+ IpnsNames names_; -+ std::shared_ptr api_; -+ std::time_t last_discovery_ = 0; -+ std::shared_ptr cache_; -+ base::FilePath const disk_path_; -+ std::shared_ptr orc_; // TODO - map of origin to Orchestrator -+ raw_ptr network_context_; -+ -+ std::shared_ptr& cache(); -+ -+ public: -+ InterRequestState(base::FilePath, PrefService*); -+ ~InterRequestState() noexcept override; -+ -+ IpnsNames& names() { return names_; } -+ Scheduler& scheduler(); -+ std::shared_ptr api(); -+ std::array,2> serialized_caches(); -+ Orchestrator& orchestrator(); -+ void network_context(network::mojom::NetworkContext*); -+ network::mojom::NetworkContext* network_context() const; -+ -+ static void CreateForBrowserContext(content::BrowserContext*, PrefService*); -+ static InterRequestState& FromBrowserContext(content::BrowserContext*); -+}; -+} // namespace ipfs -+ -+#endif // IPFS_INTER_REQUEST_STATE_H_ -diff --git a/components/ipfs/interceptor.cc b/components/ipfs/interceptor.cc -new file mode 100644 -index 0000000000000..39b5de32b87ef ---- /dev/null -+++ b/components/ipfs/interceptor.cc -@@ -0,0 +1,36 @@ -+#include "interceptor.h" -+ -+#include "inter_request_state.h" -+#include "ipfs_url_loader.h" -+ -+#include "base/logging.h" -+#include "services/network/public/cpp/resource_request.h" -+#include "services/network/public/mojom/url_response_head.mojom.h" -+#include "services/network/url_loader_factory.h" -+#include "url/url_util.h" -+ -+using Interceptor = ipfs::Interceptor; -+ -+Interceptor::Interceptor(network::mojom::URLLoaderFactory* handles_http, -+ network::mojom::NetworkContext* network_context) -+ : loader_factory_{handles_http}, network_context_{network_context} {} -+ -+void Interceptor::MaybeCreateLoader(network::ResourceRequest const& req, -+ content::BrowserContext* context, -+ LoaderCallback loader_callback) { -+ auto& state = InterRequestState::FromBrowserContext(context); -+ state.network_context(network_context_); -+ if (req.url.SchemeIs("ipfs") || req.url.SchemeIs("ipns")) { -+ auto hdr_str = req.headers.ToString(); -+ std::replace(hdr_str.begin(), hdr_str.end(), '\r', ' '); -+ VLOG(1) << req.url.spec() << " getting intercepted! Headers: \n" << hdr_str; -+ DCHECK(context); -+ auto loader = -+ std::make_shared(*loader_factory_, state); -+ std::move(loader_callback) -+ .Run(base::BindOnce(&ipfs::IpfsUrlLoader::StartRequest, loader)); -+ -+ } else { -+ std::move(loader_callback).Run({}); // SEP -+ } -+} -diff --git a/components/ipfs/interceptor.h b/components/ipfs/interceptor.h -new file mode 100644 -index 0000000000000..0321ea5481864 ---- /dev/null -+++ b/components/ipfs/interceptor.h -@@ -0,0 +1,30 @@ -+#ifndef IPFS_INTERCEPTOR_H_ -+#define IPFS_INTERCEPTOR_H_ -+ -+#include "content/public/browser/url_loader_request_interceptor.h" -+ -+class PrefService; -+namespace network::mojom { -+class URLLoaderFactory; -+class NetworkContext; -+} // namespace network::mojom -+ -+namespace ipfs { -+ -+class COMPONENT_EXPORT(IPFS) Interceptor final -+ : public content::URLLoaderRequestInterceptor { -+ raw_ptr loader_factory_; -+ raw_ptr network_context_; -+ raw_ptr pref_svc_; -+ -+ void MaybeCreateLoader(network::ResourceRequest const&, -+ content::BrowserContext*, -+ LoaderCallback) override; -+ -+ public: -+ Interceptor(network::mojom::URLLoaderFactory* handles_http, -+ network::mojom::NetworkContext*); -+}; -+} // namespace ipfs -+ -+#endif // IPFS_INTERCEPTOR_H_ -diff --git a/components/ipfs/ipfs_features.cc b/components/ipfs/ipfs_features.cc -new file mode 100644 -index 0000000000000..a0a729d5aa8e6 ---- /dev/null -+++ b/components/ipfs/ipfs_features.cc -@@ -0,0 +1,7 @@ -+#include "ipfs_features.h" -+ -+namespace ipfs { -+ -+BASE_FEATURE(kEnableIpfs, "EnableIpfs", base::FEATURE_DISABLED_BY_DEFAULT); -+ -+} -diff --git a/components/ipfs/ipfs_features.h b/components/ipfs/ipfs_features.h -new file mode 100644 -index 0000000000000..2e54462b135a9 ---- /dev/null -+++ b/components/ipfs/ipfs_features.h -@@ -0,0 +1,13 @@ -+#ifndef IPFS_IPFS_FEATURES_H_ -+#define IPFS_IPFS_FEATURES_H_ -+ -+#include "base/component_export.h" -+#include "base/feature_list.h" -+ -+namespace ipfs { -+ -+COMPONENT_EXPORT(IPFS) BASE_DECLARE_FEATURE(kEnableIpfs); -+ -+} // namespace ipfs -+ -+#endif // IPFS_IPFS_FEATURES_H_ -diff --git a/components/ipfs/ipfs_url_loader.cc b/components/ipfs/ipfs_url_loader.cc -new file mode 100644 -index 0000000000000..afc97dc425b4f ---- /dev/null -+++ b/components/ipfs/ipfs_url_loader.cc -@@ -0,0 +1,194 @@ -+#include "ipfs_url_loader.h" -+ -+#include "chromium_ipfs_context.h" -+#include "inter_request_state.h" -+ -+#include "ipfs_client/gateways.h" -+#include "ipfs_client/ipfs_request.h" -+ -+#include "base/debug/stack_trace.h" -+#include "base/notreached.h" -+#include "base/strings/stringprintf.h" -+#include "base/threading/platform_thread.h" -+#include "net/http/http_status_code.h" -+#include "services/network/public/cpp/parsed_headers.h" -+#include "services/network/public/cpp/simple_url_loader.h" -+#include "services/network/public/mojom/url_loader_factory.mojom.h" -+#include "services/network/public/mojom/url_response_head.mojom.h" -+#include "services/network/url_loader_factory.h" -+ -+#include -+ -+ipfs::IpfsUrlLoader::IpfsUrlLoader( -+ network::mojom::URLLoaderFactory& handles_http, -+ InterRequestState& state) -+ : state_{state}, lower_loader_factory_{handles_http}, api_{state_->api()} {} -+ipfs::IpfsUrlLoader::~IpfsUrlLoader() noexcept { -+ if (!complete_) { -+ LOG(ERROR) << "Premature IPFS URLLoader dtor, uri was '" << original_url_ -+ << "' " << base::debug::StackTrace(); -+ } -+} -+ -+void ipfs::IpfsUrlLoader::FollowRedirect( -+ std::vector const& // removed_headers -+ , -+ net::HttpRequestHeaders const& // modified_headers -+ , -+ net::HttpRequestHeaders const& // modified_cors_exempt_headers -+ , -+ absl::optional<::GURL> const& // new_url -+) { -+ NOTIMPLEMENTED(); -+} -+ -+void ipfs::IpfsUrlLoader::SetPriority(net::RequestPriority priority, -+ int32_t intra_prio_val) { -+ VLOG(1) << "TODO SetPriority(" << priority << ',' << intra_prio_val << ')'; -+} -+ -+void ipfs::IpfsUrlLoader::PauseReadingBodyFromNet() { -+ NOTIMPLEMENTED(); -+} -+ -+void ipfs::IpfsUrlLoader::ResumeReadingBodyFromNet() { -+ NOTIMPLEMENTED(); -+} -+ -+void ipfs::IpfsUrlLoader::StartRequest( -+ std::shared_ptr me, -+ network::ResourceRequest const& resource_request, -+ mojo::PendingReceiver receiver, -+ mojo::PendingRemote client) { -+ DCHECK(!me->receiver_.is_bound()); -+ DCHECK(!me->client_.is_bound()); -+ me->receiver_.Bind(std::move(receiver)); -+ me->client_.Bind(std::move(client)); -+ if (me->original_url_.empty()) { -+ me->original_url_ = resource_request.url.spec(); -+ } -+ if (resource_request.url.SchemeIs("ipfs") || -+ resource_request.url.SchemeIs("ipns")) { -+ auto ns = resource_request.url.scheme(); -+ auto cid_str = resource_request.url.host(); -+ auto path = resource_request.url.path(); -+ auto abs_path = "/" + ns + "/" + cid_str + path; -+ VLOG(1) << resource_request.url.spec() << " -> " << abs_path; -+ me->root_ = cid_str; -+ me->api_->SetLoaderFactory(*(me->lower_loader_factory_)); -+ auto whendone = [me](IpfsRequest const& req, ipfs::Response const& res) { -+ VLOG(1) << "whendone(" << req.path().to_string() << ',' << res.status_ -+ << ',' << res.body_.size() << "B mime=" << res.mime_ << ')'; -+ if (!res.body_.empty()) { -+ me->ReceiveBlockBytes(res.body_); -+ } -+ me->status_ = res.status_; -+ me->resp_loc_ = res.location_; -+ if (res.status_ == Response::IMMUTABLY_GONE.status_) { -+ auto p = req.path(); -+ p.pop(); -+ std::string cid{p.pop()}; -+ me->DoesNotExist(cid, p.to_string()); -+ } else { -+ me->BlocksComplete(res.mime_); -+ } -+ DCHECK(me->complete_); -+ }; -+ auto req = std::make_shared(abs_path, whendone); -+ me->state_->orchestrator().build_response(req); -+ } else { -+ LOG(ERROR) << "Wrong scheme: " << resource_request.url.scheme(); -+ } -+} -+ -+void ipfs::IpfsUrlLoader::OverrideUrl(GURL u) { -+ original_url_ = u.spec(); -+} -+void ipfs::IpfsUrlLoader::AddHeader(std::string_view a, std::string_view b) { -+ VLOG(1) << "AddHeader(" << a << ',' << b << ')'; -+ additional_outgoing_headers_.emplace_back(a, b); -+} -+ -+void ipfs::IpfsUrlLoader::BlocksComplete(std::string mime_type) { -+ VLOG(1) << "Resolved from unix-fs dag a file of type: " << mime_type -+ << " will report it as " << original_url_; -+ if (complete_) { -+ return; -+ } -+ auto result = -+ mojo::CreateDataPipe(partial_block_.size(), pipe_prod_, pipe_cons_); -+ if (result) { -+ LOG(ERROR) << " ERROR: TaskFailed to create data pipe: " << result; -+ return; -+ } -+ complete_ = true; -+ auto head = network::mojom::URLResponseHead::New(); -+ if (mime_type.size()) { -+ head->mime_type = mime_type; -+ } -+ std::uint32_t byte_count = partial_block_.size(); -+ VLOG(1) << "Calling WriteData(" << byte_count << ")"; -+ pipe_prod_->WriteData(partial_block_.data(), &byte_count, -+ MOJO_BEGIN_WRITE_DATA_FLAG_ALL_OR_NONE); -+ VLOG(1) << "Called WriteData(" << byte_count << ")"; -+ head->content_length = byte_count; -+ head->headers = -+ net::HttpResponseHeaders::TryToCreate("access-control-allow-origin: *"); -+ if (resp_loc_.size()) { -+ head->headers->AddHeader("Location", resp_loc_); -+ } -+ if (!head->headers) { -+ LOG(ERROR) << "\n\tFailed to create headers!\n"; -+ return; -+ } -+ auto* reason = -+ net::GetHttpReasonPhrase(static_cast(status_)); -+ auto status_line = base::StringPrintf("HTTP/1.1 %d %s", status_, reason); -+ VLOG(1) << "Returning with status line '" << status_line << "'.\n"; -+ head->headers->ReplaceStatusLine(status_line); -+ if (mime_type.size()) { -+ head->headers->SetHeader("Content-Type", mime_type); -+ } -+ head->headers->SetHeader("Access-Control-Allow-Origin", "*"); -+ head->was_fetched_via_spdy = false; -+ for (auto& [n, v] : additional_outgoing_headers_) { -+ VLOG(1) << "Appending 'additional' header:" << n << '=' << v << '.'; -+ head->headers->AddHeader(n, v); -+ } -+ VLOG(1) << "Calling PopulateParsedHeaders"; -+ head->parsed_headers = -+ network::PopulateParsedHeaders(head->headers.get(), GURL{original_url_}); -+ VLOG(1) << "Sending response for " << original_url_ << " with mime type " -+ << head->mime_type << " and status line " << status_line; -+ if (status_ / 100 == 3 && resp_loc_.size()) { -+ auto ri = net::RedirectInfo::ComputeRedirectInfo( -+ "GET", GURL{original_url_}, net::SiteForCookies{}, -+ net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT, -+ net::ReferrerPolicy::NO_REFERRER, "", status_, GURL{resp_loc_}, -+ std::nullopt, false); -+ client_->OnReceiveRedirect(ri, std::move(head)); -+ } else { -+ client_->OnReceiveResponse(std::move(head), std::move(pipe_cons_), -+ absl::nullopt); -+ } -+ client_->OnComplete(network::URLLoaderCompletionStatus{}); -+ stepper_.reset(); -+} -+ -+void ipfs::IpfsUrlLoader::DoesNotExist(std::string_view cid, -+ std::string_view path) { -+ LOG(ERROR) << "Immutable data 404 for " << cid << '/' << path; -+ complete_ = true; -+ client_->OnComplete( -+ network::URLLoaderCompletionStatus{net::ERR_FILE_NOT_FOUND}); -+ stepper_.reset(); -+} -+void ipfs::IpfsUrlLoader::NotHere(std::string_view cid, std::string_view path) { -+ LOG(INFO) << "TODO " << __func__ << '(' << cid << ',' << path << ')'; -+} -+ -+void ipfs::IpfsUrlLoader::ReceiveBlockBytes(std::string_view content) { -+ partial_block_.append(content); -+ VLOG(2) << "Recived a block of size " << content.size() << " now have " -+ << partial_block_.size() << " bytes."; -+} -diff --git a/components/ipfs/ipfs_url_loader.h b/components/ipfs/ipfs_url_loader.h -new file mode 100644 -index 0000000000000..dc324f7b11f2d ---- /dev/null -+++ b/components/ipfs/ipfs_url_loader.h -@@ -0,0 +1,96 @@ -+#ifndef COMPONENTS_IPFS_URL_LOADER_H_ -+#define COMPONENTS_IPFS_URL_LOADER_H_ 1 -+ -+#include "base/debug/debugging_buildflags.h" -+#include "base/timer/timer.h" -+#include "mojo/public/cpp/bindings/receiver_set.h" -+#include "mojo/public/cpp/system/data_pipe.h" -+#include "net/http/http_request_headers.h" -+#include "services/network/public/cpp/resolve_host_client_base.h" -+#include "services/network/public/cpp/resource_request.h" -+#include "services/network/public/mojom/url_loader.mojom.h" -+ -+#include -+ -+namespace ipfs { -+class ChromiumIpfsContext; -+} // namespace ipfs -+ -+namespace network::mojom { -+class URLLoaderFactory; -+class HostResolver; -+class NetworkContext; -+} // namespace network::mojom -+namespace network { -+class SimpleURLLoader; -+} -+ -+namespace ipfs { -+class InterRequestState; -+ -+class IpfsUrlLoader final : public network::mojom::URLLoader { -+ void FollowRedirect( -+ std::vector const& removed_headers, -+ net::HttpRequestHeaders const& modified_headers, -+ net::HttpRequestHeaders const& modified_cors_exempt_headers, -+ absl::optional<::GURL> const& new_url) override; -+ void SetPriority(net::RequestPriority priority, -+ int32_t intra_priority_value) override; -+ void PauseReadingBodyFromNet() override; -+ void ResumeReadingBodyFromNet() override; -+ -+ public: -+ explicit IpfsUrlLoader(network::mojom::URLLoaderFactory& handles_http, -+ InterRequestState& state); -+ ~IpfsUrlLoader() noexcept override; -+ -+ using ptr = std::shared_ptr; -+ -+ // Passed as the RequestHandler for -+ // Interceptor::MaybeCreateLoader. -+ static void StartRequest( -+ ptr, -+ network::ResourceRequest const& resource_request, -+ mojo::PendingReceiver receiver, -+ mojo::PendingRemote client); -+ -+ void OverrideUrl(GURL); -+ void AddHeader(std::string_view,std::string_view); -+ void extra(std::shared_ptr xtra) { extra_ = xtra; } -+ -+ private: -+ using RequestHandle = std::unique_ptr; -+ -+ raw_ref state_; -+ mojo::Receiver receiver_{this}; -+ mojo::Remote client_; -+ raw_ref lower_loader_factory_; -+ mojo::ScopedDataPipeProducerHandle pipe_prod_ = {}; -+ mojo::ScopedDataPipeConsumerHandle pipe_cons_ = {}; -+ bool complete_ = false; -+ std::shared_ptr api_; -+ std::string original_url_; -+ std::string partial_block_; -+ std::vector> additional_outgoing_headers_; -+ std::shared_ptr extra_; -+ std::unique_ptr stepper_; -+ std::string root_; -+ int status_ = 200; -+ std::string resp_loc_; -+ -+ void CreateBlockRequest(std::string cid); -+ -+ void ReceiveBlockBytes(std::string_view); -+ void BlocksComplete(std::string mime_type); -+ void DoesNotExist(std::string_view cid, std::string_view path); -+ void NotHere(std::string_view cid, std::string_view path); -+ -+ void StartUnixFsProc(ptr, std::string_view); -+ void AppendGatewayHeaders(std::vector const& cids, net::HttpResponseHeaders&); -+ void AppendGatewayInfoHeader(std::string const&, net::HttpResponseHeaders&); -+ void TakeStep(); -+}; -+ -+} // namespace ipfs -+ -+#endif -diff --git a/components/ipfs/url_loader_factory.cc b/components/ipfs/url_loader_factory.cc -new file mode 100644 -index 0000000000000..9a80284098748 ---- /dev/null -+++ b/components/ipfs/url_loader_factory.cc -@@ -0,0 +1,56 @@ -+#include "url_loader_factory.h" -+ -+#include "inter_request_state.h" -+#include "ipfs_url_loader.h" -+ -+void ipfs::IpfsURLLoaderFactory::Create( -+ NonNetworkURLLoaderFactoryMap* in_out, -+ content::BrowserContext* context, -+ URLLoaderFactory* default_factory, -+ network::mojom::NetworkContext* net_ctxt, -+ PrefService* pref_svc) { -+ for (char const* scheme : {"ipfs", "ipns"}) { -+ mojo::PendingRemote pending; -+ new IpfsURLLoaderFactory(scheme, pending.InitWithNewPipeAndPassReceiver(), -+ context, default_factory, net_ctxt, pref_svc); -+ in_out->emplace(scheme, std::move(pending)); -+ } -+} -+ -+ipfs::IpfsURLLoaderFactory::IpfsURLLoaderFactory( -+ std::string scheme, -+ mojo::PendingReceiver factory_receiver, -+ content::BrowserContext* context, -+ URLLoaderFactory* default_factory, -+ network::mojom::NetworkContext* net_ctxt, -+ PrefService* pref_svc) -+ : network::SelfDeletingURLLoaderFactory(std::move(factory_receiver)), -+ scheme_{scheme}, -+ context_{context}, -+ default_factory_{default_factory}, -+ network_context_{net_ctxt}, -+ pref_svc_{pref_svc} {} -+ -+ipfs::IpfsURLLoaderFactory::~IpfsURLLoaderFactory() noexcept { -+ context_ = nullptr; -+ default_factory_ = nullptr; -+ network_context_ = nullptr; -+} -+ -+void ipfs::IpfsURLLoaderFactory::CreateLoaderAndStart( -+ mojo::PendingReceiver loader, -+ int32_t /*request_id*/, -+ uint32_t /*options*/, -+ network::ResourceRequest const& request, -+ mojo::PendingRemote client, -+ net::MutableNetworkTrafficAnnotationTag const& // traffic_annotation -+) { -+ VLOG(2) << "IPFS subresource: case=" << scheme_ -+ << " url=" << request.url.spec(); -+ DCHECK(default_factory_); -+ if (scheme_ == "ipfs" || scheme_ == "ipns") { -+ auto ptr = std::make_shared( -+ *default_factory_, InterRequestState::FromBrowserContext(context_)); -+ ptr->StartRequest(ptr, request, std::move(loader), std::move(client)); -+ } -+} -diff --git a/components/ipfs/url_loader_factory.h b/components/ipfs/url_loader_factory.h -new file mode 100644 -index 0000000000000..01cd66ea6ed8f ---- /dev/null -+++ b/components/ipfs/url_loader_factory.h -@@ -0,0 +1,58 @@ -+#ifndef IPFS_URL_LOADER_FACTORY_H_ -+#define IPFS_URL_LOADER_FACTORY_H_ -+ -+#include "services/network/public/cpp/self_deleting_url_loader_factory.h" -+#include "services/network/public/mojom/url_loader_factory.mojom.h" -+ -+#include -+ -+class PrefService; -+namespace content { -+class BrowserContext; -+} -+namespace network { -+namespace mojom { -+class NetworkContext; -+} -+} // namespace network -+ -+namespace ipfs { -+using NonNetworkURLLoaderFactoryMap = -+ std::map>; -+ -+class COMPONENT_EXPORT(IPFS) IpfsURLLoaderFactory -+ : public network::SelfDeletingURLLoaderFactory { -+ public: -+ static void Create(NonNetworkURLLoaderFactoryMap* in_out, -+ content::BrowserContext*, -+ URLLoaderFactory*, -+ network::mojom::NetworkContext*, -+ PrefService*); -+ -+ private: -+ IpfsURLLoaderFactory(std::string, -+ mojo::PendingReceiver, -+ content::BrowserContext*, -+ network::mojom::URLLoaderFactory*, -+ network::mojom::NetworkContext*, -+ PrefService*); -+ ~IpfsURLLoaderFactory() noexcept override; -+ void CreateLoaderAndStart( -+ mojo::PendingReceiver loader, -+ int32_t request_id, -+ uint32_t options, -+ network::ResourceRequest const& request, -+ mojo::PendingRemote client, -+ net::MutableNetworkTrafficAnnotationTag const& traffic_annotation) -+ override; -+ -+ std::string scheme_; -+ raw_ptr context_; -+ raw_ptr default_factory_; -+ raw_ptr network_context_; -+ raw_ptr pref_svc_; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_URL_LOADER_FACTORY_H_ -diff --git a/components/open_from_clipboard/clipboard_recent_content_generic.cc b/components/open_from_clipboard/clipboard_recent_content_generic.cc -index 4dcafecbc66c6..d205209c08162 100644 ---- a/components/open_from_clipboard/clipboard_recent_content_generic.cc -+++ b/components/open_from_clipboard/clipboard_recent_content_generic.cc -@@ -20,7 +20,7 @@ - namespace { - // Schemes appropriate for suggestion by ClipboardRecentContent. - const char* kAuthorizedSchemes[] = { -- url::kAboutScheme, url::kDataScheme, url::kHttpScheme, url::kHttpsScheme, -+ url::kAboutScheme, url::kDataScheme, url::kHttpScheme, url::kHttpsScheme, "ipfs", "ipns" - // TODO(mpearson): add support for chrome:// URLs. Right now the scheme - // for that lives in content and is accessible via - // GetEmbedderRepresentationOfAboutScheme() or content::kChromeUIScheme -diff --git a/net/dns/dns_config_service_linux.cc b/net/dns/dns_config_service_linux.cc -index 5273da5190277..12b28b86a4c00 100644 ---- a/net/dns/dns_config_service_linux.cc -+++ b/net/dns/dns_config_service_linux.cc -@@ -272,11 +272,11 @@ bool IsNsswitchConfigCompatible( - // Ignore any entries after `kDns` because Chrome will fallback to the - // system resolver if a result was not found in DNS. - return true; -- -+ case NsswitchReader::Service::kResolve: -+ break; - case NsswitchReader::Service::kMdns: - case NsswitchReader::Service::kMdns4: - case NsswitchReader::Service::kMdns6: -- case NsswitchReader::Service::kResolve: - case NsswitchReader::Service::kNis: - RecordIncompatibleNsswitchReason( - IncompatibleNsswitchReason::kIncompatibleService, -diff --git a/third_party/blink/renderer/platform/weborigin/scheme_registry.cc b/third_party/blink/renderer/platform/weborigin/scheme_registry.cc -index 4eadf46ea0c24..d62fc7fb14e01 100644 ---- a/third_party/blink/renderer/platform/weborigin/scheme_registry.cc -+++ b/third_party/blink/renderer/platform/weborigin/scheme_registry.cc -@@ -67,7 +67,7 @@ class URLSchemesRegistry final { - // is considered secure. Additional checks are performed to ensure that - // other http pages are filtered out. - service_worker_schemes({"http", "https"}), -- fetch_api_schemes({"http", "https"}), -+ fetch_api_schemes({"http", "https", "ipfs", "ipns"}), - allowed_in_referrer_schemes({"http", "https"}) { - for (auto& scheme : url::GetCorsEnabledSchemes()) - cors_enabled_schemes.insert(scheme.c_str()); -diff --git a/third_party/ipfs_client/BUILD.gn b/third_party/ipfs_client/BUILD.gn -new file mode 100644 -index 0000000000000..39c8128a02161 ---- /dev/null -+++ b/third_party/ipfs_client/BUILD.gn -@@ -0,0 +1,202 @@ -+import("args.gni") -+import("//build/buildflag_header.gni") -+ -+buildflag_header("ipfs_buildflags") { -+ header = "ipfs_buildflags.h" -+ flags = [ "ENABLE_IPFS=$enable_ipfs" ] -+} -+ -+config("external_config") { -+ include_dirs = [ -+ "include", -+ ] -+} -+ -+if (enable_ipfs) { -+ cxx_sources = [ -+ "include/ipfs_client/block_requestor.h", -+ "include/ipfs_client/block_storage.h", -+ "include/ipfs_client/cid.h", -+ "include/ipfs_client/context_api.h", -+ "include/ipfs_client/crypto/hasher.h", -+ "include/ipfs_client/dag_cbor_value.h", -+ "include/ipfs_client/dag_json_value.h", -+ "include/ipfs_client/gateway_spec.h", -+ "include/ipfs_client/gateways.h", -+ "include/ipfs_client/gw/block_request_splitter.h", -+ "include/ipfs_client/gw/default_requestor.h", -+ "include/ipfs_client/gw/dnslink_requestor.h", -+ "include/ipfs_client/gw/gateway_request.h", -+ "include/ipfs_client/gw/inline_request_handler.h", -+ "include/ipfs_client/gw/requestor.h", -+ "include/ipfs_client/gw/terminating_requestor.h", -+ "include/ipfs_client/http_request_description.h", -+ "include/ipfs_client/identity_cid.h", -+ "include/ipfs_client/ipfs_request.h", -+ "include/ipfs_client/ipld/dag_node.h", -+ "include/ipfs_client/ipld/link.h", -+ "include/ipfs_client/ipld/resolution_state.h", -+ "include/ipfs_client/ipns_cbor_entry.h", -+ "include/ipfs_client/ipns_names.h", -+ "include/ipfs_client/ipns_record.h", -+ "include/ipfs_client/json_cbor_adapter.h", -+ "include/ipfs_client/logger.h", -+ "include/ipfs_client/multi_base.h", -+ "include/ipfs_client/multi_hash.h", -+ "include/ipfs_client/multicodec.h", -+ "include/ipfs_client/orchestrator.h", -+ "include/ipfs_client/pb_dag.h", -+ "include/ipfs_client/response.h", -+ "include/ipfs_client/signing_key_type.h", -+ "include/ipfs_client/url_spec.h", -+ "include/libp2p/common/types.hpp", -+ "include/libp2p/crypto/key.h", -+ "include/libp2p/crypto/protobuf/protobuf_key.hpp", -+ "include/libp2p/multi/multibase_codec.hpp", -+ "include/libp2p/multi/multibase_codec/codecs/base16.h", -+ "include/libp2p/multi/multibase_codec/codecs/base32.hpp", -+ "include/libp2p/multi/multibase_codec/codecs/base_error.hpp", -+ "include/libp2p/multi/multicodec_type.hpp", -+ "include/libp2p/multi/uvarint.hpp", -+ "include/multibase/algorithm.h", -+ "include/multibase/basic_algorithm.h", -+ "include/multibase/encoding.h", -+ "include/smhasher/MurmurHash3.h", -+ "include/vocab/byte.h", -+ "include/vocab/byte_view.h", -+ "include/vocab/endian.h", -+ "include/vocab/expected.h", -+ "include/vocab/flat_mapset.h", -+ "include/vocab/html_escape.h", -+ "include/vocab/i128.h", -+ "include/vocab/raw_ptr.h", -+ "include/vocab/slash_delimited.h", -+ "include/vocab/span.h", -+ "include/vocab/stringify.h", -+ "src/ipfs_client/bases/b16_upper.h", -+ "src/ipfs_client/bases/b32.h", -+ "src/ipfs_client/block_requestor.cc", -+ "src/ipfs_client/car.cc", -+ "src/ipfs_client/car.h", -+ "src/ipfs_client/cid.cc", -+ "src/ipfs_client/context_api.cc", -+ "src/ipfs_client/crypto/openssl_sha2_256.cc", -+ "src/ipfs_client/crypto/openssl_sha2_256.h", -+ "src/ipfs_client/dag_cbor_value.cc", -+ "src/ipfs_client/dag_json_value.cc", -+ "src/ipfs_client/gateways.cc", -+ "src/ipfs_client/generated_directory_listing.cc", -+ "src/ipfs_client/generated_directory_listing.h", -+ "src/ipfs_client/gw/block_request_splitter.cc", -+ "src/ipfs_client/gw/default_requestor.cc", -+ "src/ipfs_client/gw/dnslink_requestor.cc", -+ "src/ipfs_client/gw/gateway_http_requestor.cc", -+ "src/ipfs_client/gw/gateway_http_requestor.h", -+ "src/ipfs_client/gw/gateway_request.cc", -+ "src/ipfs_client/gw/gateway_state.cc", -+ "src/ipfs_client/gw/gateway_state.h", -+ "src/ipfs_client/gw/inline_request_handler.cc", -+ "src/ipfs_client/gw/multi_gateway_requestor.cc", -+ "src/ipfs_client/gw/multi_gateway_requestor.h", -+ "src/ipfs_client/gw/requestor.cc", -+ "src/ipfs_client/gw/requestor_pool.cc", -+ "src/ipfs_client/gw/requestor_pool.h", -+ "src/ipfs_client/gw/terminating_requestor.cc", -+ "src/ipfs_client/http_request_description.cc", -+ "src/ipfs_client/identity_cid.cc", -+ "src/ipfs_client/ipfs_request.cc", -+ "src/ipfs_client/ipld/chunk.cc", -+ "src/ipfs_client/ipld/chunk.h", -+ "src/ipfs_client/ipld/dag_cbor_node.cc", -+ "src/ipfs_client/ipld/dag_cbor_node.h", -+ "src/ipfs_client/ipld/dag_json_node.cc", -+ "src/ipfs_client/ipld/dag_json_node.h", -+ "src/ipfs_client/ipld/dag_node.cc", -+ "src/ipfs_client/ipld/directory_shard.cc", -+ "src/ipfs_client/ipld/directory_shard.h", -+ "src/ipfs_client/ipld/ipns_name.cc", -+ "src/ipfs_client/ipld/ipns_name.h", -+ "src/ipfs_client/ipld/link.cc", -+ "src/ipfs_client/ipld/resolution_state.cc", -+ "src/ipfs_client/ipld/root.cc", -+ "src/ipfs_client/ipld/root.h", -+ "src/ipfs_client/ipld/small_directory.cc", -+ "src/ipfs_client/ipld/small_directory.h", -+ "src/ipfs_client/ipld/symlink.cc", -+ "src/ipfs_client/ipld/symlink.h", -+ "src/ipfs_client/ipld/unixfs_file.cc", -+ "src/ipfs_client/ipld/unixfs_file.h", -+ "src/ipfs_client/ipns_names.cc", -+ "src/ipfs_client/ipns_record.cc", -+ "src/ipfs_client/logger.cc", -+ "src/ipfs_client/multi_base.cc", -+ "src/ipfs_client/multi_hash.cc", -+ "src/ipfs_client/multicodec.cc", -+ "src/ipfs_client/orchestrator.cc", -+ "src/ipfs_client/path2url.cc", -+ "src/ipfs_client/path2url.h", -+ "src/ipfs_client/pb_dag.cc", -+ "src/ipfs_client/redirects.cc", -+ "src/ipfs_client/redirects.h", -+ "src/ipfs_client/response.cc", -+ "src/ipfs_client/signing_key_type.cc", -+ "src/libp2p/crypto/protobuf_key.hpp", -+ "src/libp2p/multi/multibase_codec/codecs/base16.cc", -+ "src/libp2p/multi/uvarint.cc", -+ "src/log_macros.h", -+ "src/smhasher/MurmurHash3.cc", -+ "src/vocab/byte_view.cc", -+ "src/vocab/slash_delimited.cc", -+ ] -+ static_library("ipfs_client") { -+ if (is_nacl) { -+ sources = cxx_sources - [ -+ "src/ipfs_client/dag_block.cc", -+ "src/ipfs_client/gw/gateway_request.cc", -+ "src/ipfs_client/gw/gateway_http_requestor.cc", -+ "src/ipfs_client/gw/requestor.cc", -+ "src/ipfs_client/ipld/dag_node.cc", -+ "src/ipfs_client/ipns_names.cc", -+ "src/ipfs_client/ipns_record.cc", -+ "src/ipfs_client/logger.cc", -+ "src/ipfs_client/signing_key_type.cc", -+ ] -+ } else { -+ sources = cxx_sources -+ } -+ include_dirs = [ -+ "include", -+ "src", -+ "..", -+ "../boringssl/src/include" -+ ] -+ public_configs = [ -+ ":external_config" -+ ] -+ public_deps = [ -+ "//third_party/abseil-cpp:absl", -+ "//base", -+ ] -+ deps = [ -+ "//third_party/abseil-cpp:absl", -+ "//base", -+ ] -+ if (!is_nacl) { -+ public_deps += [ -+ ":protos", -+ "//third_party/protobuf:protobuf_lite", -+ ] -+ } -+ } -+} -+ -+import("//third_party/protobuf/proto_library.gni") -+ -+proto_library("protos") { -+ sources = [ -+ "ipns_record.proto", -+ "keys.proto", -+ "pb_dag.proto", -+ "unix_fs.proto", -+ ] -+} -diff --git a/third_party/ipfs_client/README.chromium b/third_party/ipfs_client/README.chromium -new file mode 100644 -index 0000000000000..e69de29bb2d1d -diff --git a/third_party/ipfs_client/README.md b/third_party/ipfs_client/README.md -new file mode 100644 -index 0000000000000..0e6ffadd2ebbc ---- /dev/null -+++ b/third_party/ipfs_client/README.md -@@ -0,0 +1,6 @@ -+# ipfs-client -+ -+## TODO -+ -+Need to fill out this README to explain how to use ipfs-client in other contexts. -+ -diff --git a/third_party/ipfs_client/args.gni b/third_party/ipfs_client/args.gni -new file mode 100644 -index 0000000000000..bb13519b23e89 ---- /dev/null -+++ b/third_party/ipfs_client/args.gni -@@ -0,0 +1,3 @@ -+declare_args() { -+ enable_ipfs = false -+} -diff --git a/third_party/ipfs_client/conanfile.py b/third_party/ipfs_client/conanfile.py -new file mode 100644 -index 0000000000000..289e3b48f8ad1 ---- /dev/null -+++ b/third_party/ipfs_client/conanfile.py -@@ -0,0 +1,79 @@ -+from conan import ConanFile -+from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout -+from shutil import copyfile, which -+import sys -+from os.path import dirname, isfile, join, realpath -+ -+here = realpath(dirname(__file__)) -+sys.path.append(realpath(join(here, '..', 'cmake'))) -+sys.path.append(here) -+ -+try: -+ import version -+ VERSION = version.deduce() -+except ImportError: -+ VERSION = open(join(here,'version.txt'), 'r').read().strip() -+ -+ -+class IpfsChromium(ConanFile): -+ name = "ipfs_client" -+ version = VERSION -+ settings = "os", "compiler", "build_type", "arch" -+ # generators = "CMakeDeps", 'CMakeToolchain' -+ _PB = 'protobuf/3.20.0' -+ require_transitively = [ -+ 'abseil/20230125.3', -+ 'boost/1.81.0', -+ 'bzip2/1.0.8', -+ 'c-ares/1.22.1', -+ 'nlohmann_json/3.11.2', -+ 'openssl/1.1.1t', -+ _PB, -+ ] -+ # default_options = {"boost/*:header_only": True} -+ default_options = { -+ "boost/*:bzip2": True, -+ "boost/*:with_stacktrace_backtrace": True -+ } -+ tool_requires = [ -+ 'cmake/3.22.6', -+ 'ninja/1.11.1', -+ _PB, -+ ] -+ extensions = ['h', 'cc', 'hpp', 'proto'] -+ exports_sources = [ '*.txt' ] + [f'**/*.{e}' for e in extensions] -+ exports = 'version.txt' -+ package_type = 'static-library' -+ -+ -+ def generate(self): -+ tc = CMakeToolchain(self, 'Ninja') -+ tc.generate() -+ d = CMakeDeps(self) -+ d.generate() -+ -+ def build(self): -+ cmake = CMake(self) -+ cmake.configure(variables={ -+ "CXX_VERSION": 20, -+ "INSIDE_CONAN": True -+ }) -+ cmake.build(build_tool_args=['--verbose']) -+ -+ def package(self): -+ cmake = CMake(self) -+ cmake.install() -+ print(self.cpp_info.objects) -+ -+ def package_info(self): -+ self.cpp_info.libs = ["ipfs_client"] -+ -+ def build_requirements(self): -+ if not which("doxygen"): -+ self.tool_requires("doxygen/1.9.4") -+ def layout(self): -+ cmake_layout(self) -+ -+ def requirements(self): -+ for l in self.require_transitively: -+ self.requires(l, transitive_headers=True) -diff --git a/third_party/ipfs_client/include/ipfs_client/block_requestor.h b/third_party/ipfs_client/include/ipfs_client/block_requestor.h -new file mode 100644 -index 0000000000000..42ae26e519760 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/block_requestor.h -@@ -0,0 +1,48 @@ -+#ifndef BLOCK_REQUESTOR_H_ -+#define BLOCK_REQUESTOR_H_ -+ -+#include -+ -+#include -+#include -+#include -+ -+namespace ipfs { -+ -+/*! -+ * \brief The urgency of a gateway request -+ * \details Determines how many gateways should be involved, and how burdened a -+ * gateway should be before not also taking this one on concurrently. Zero is -+ * a special value that indicates the block isn't actually required now, but -+ * rather might be required soonish (prefetch). There are some cases of -+ * special handling for that. -+ */ -+using Priority = std::uint_least16_t; -+ -+class DagListener; -+ -+/*! -+ * \brief Interface for classes that can asynchronously fetch a block for a CID -+ * \details This is one of the interfaces using code is meant to implement. -+ * Common usages: -+ * * A class that requests blocks from gateways -+ * * A cache that must act asynchronously (perhaps on-disk) -+ * * ChainedRequestors : a chain-of-responsibility combining multiple -+ */ -+class BlockRequestor { -+ public: -+ /** -+ * \brief Request a single block from gateway(s). -+ * \param cid - MB-MH string representation of the Content IDentifier -+ * \param dl - Someone who may be interested -+ * \param priority - Urgency of the request -+ * \note The DagListener is mostly about lifetime extension, since it's -+ * waiting on something which is waiting on this -+ */ -+ virtual void RequestByCid(std::string cid, -+ std::shared_ptr dl, -+ Priority priority) = 0; -+}; -+} // namespace ipfs -+ -+#endif // BLOCK_REQUESTOR_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/block_storage.h b/third_party/ipfs_client/include/ipfs_client/block_storage.h -new file mode 100644 -index 0000000000000..525bae463f50d ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/block_storage.h -@@ -0,0 +1,144 @@ -+#ifndef IPFS_BLOCKS_H_ -+#define IPFS_BLOCKS_H_ -+ -+#include "pb_dag.h" -+#include "vocab/flat_mapset.h" -+ -+#include -+#include -+#include -+ -+namespace libp2p::multi { -+struct ContentIdentifier; -+} -+ -+namespace ipfs { -+class DagListener; -+class ContextApi; -+ -+class UnixFsPathResolver; -+ -+/*! -+ * \brief Immediate access to recently-accessed blocks -+ * \details Blocks are held in-memory, using pretty standard containers, as -+ * already-parsed ipfs::Block objects. -+ */ -+class BlockStorage { -+ public: -+ BlockStorage(); -+ -+ BlockStorage(BlockStorage const&) = delete; -+ -+ ~BlockStorage() noexcept; -+ -+ /*! -+ * \brief Store a Block for later access. -+ * \param cid_str - The string representation of cid -+ * \param cid - The Content IDentifier -+ * \param headers - Associated HTTP headers -+ * \param body - The raw bytes of the block -+ * \param block - The block being stored -+ * \return Whether this block is now stored in *this -+ */ -+ bool Store(std::string cid_str, -+ Cid const& cid, -+ std::string headers, -+ std::string const& body, -+ PbDag&& block); -+ -+ /*! -+ * \name Store (Convenience) -+ * Convenience functions for -+ * ipfs::BlockStorage::Store(std::string,Cid const&,std::string,std::string -+ * const&,Block&&) -+ */ -+ ///@{ -+ bool Store(std::string headers, std::string const& body, PbDag&& block); -+ bool Store(std::string const& cid, std::string headers, std::string body); -+ bool Store(std::string cid_str, -+ Cid const& cid, -+ std::string headers, -+ std::string body); -+ bool Store(Cid const& cid, -+ std::string headers, -+ std::string const& body, -+ PbDag&&); -+ ///@} -+ -+ /*! -+ * \brief Get a block! -+ * \details cid must match string-wise exactly: same multibase & all. -+ * For identity codecs, returns the data even if not stored. -+ * \param cid - String representation of the CID for the block. -+ * \return Non-owning pointer if found, nullptr -+ * otherwise -+ */ -+ PbDag const* Get(std::string const& cid); -+ -+ /*! -+ * \brief Get HTTP headers associated with the block -+ * \param cid - String representation of the CID for the block. -+ * \return nullptr iff ! Get(cid) ; -+ * Empty string if the headers have never been set ; -+ * Otherwise, application-specific std::string (as-stored) -+ */ -+ std::string const* GetHeaders(std::string const& cid); -+ -+ /*! -+ * \brief Indicate that a particular path resolver is waiting on a CID to -+ * become available -+ */ -+ void AddListening(UnixFsPathResolver*); -+ -+ /*! -+ * \brief Indicate that a particular path resolver is no longer waiting -+ */ -+ void StopListening(UnixFsPathResolver*); -+ -+ /*! -+ * \brief Normally called internally -+ * \details Checks to see if any listening path resolver appears to be waiting -+ * on a CID which is now available. -+ */ -+ void CheckListening(); -+ -+ /*! -+ * \brief Type for callbacks about new blocks -+ * \details The parameters to the hook are -+ * * CID string -+ * * HTTP headers -+ * * raw bytes of the block -+ */ -+ using SerializedStorageHook = -+ std::function; -+ -+ /*! -+ * \brief Register a callback that will be called when any new block goes into -+ * storage -+ */ -+ void AddStorageHook(SerializedStorageHook); -+ -+ private: -+ struct Record { -+ Record(); -+ ~Record() noexcept; -+ std::time_t last_access = 0L; -+ std::string cid_str = {}; -+ PbDag block = {}; -+ std::string headers = {}; -+ }; -+ std::list records_ = std::list(0xFFUL); -+ using Iter = decltype(records_)::iterator; -+ flat_map cid2record_; -+ flat_set listening_; -+ bool checking_ = false; -+ std::vector hooks_; -+ -+ Record const* GetInternal(std::string const&); -+ Record* FindFree(std::time_t); -+ Record* Allocate(); -+ Record* StoreIdentity(std::string const&, Cid const&); -+}; -+} // namespace ipfs -+ -+#endif // IPFS_BLOCKS_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/cid.h b/third_party/ipfs_client/include/ipfs_client/cid.h -new file mode 100644 -index 0000000000000..d957d23e5e7e4 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/cid.h -@@ -0,0 +1,38 @@ -+#ifndef IPFS_CID_H_ -+#define IPFS_CID_H_ -+ -+#include "multi_hash.h" -+#include "multicodec.h" -+ -+#include -+ -+#include -+#include -+ -+namespace ipfs { -+class Cid { -+ MultiCodec codec_ = MultiCodec::INVALID; -+ MultiHash hash_; -+ -+ public: -+ Cid() = default; -+ Cid(MultiCodec, MultiHash); -+ explicit Cid(std::string_view); -+ explicit Cid(ByteView); -+ bool ReadStart(ByteView&); -+ -+ bool valid() const; -+ MultiCodec codec() const { return codec_; } -+ MultiHash const& multi_hash() const { return hash_; } -+ ByteView hash() const; -+ HashType hash_type() const; -+ -+ std::string to_string() const; -+ -+ constexpr static std::size_t MinSerializedLength = -+ 1 /*cid version*/ + 1 /*codec*/ + 1 /*hash type*/ + -+ 1 /*hash len, could be zero*/; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_CID_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/context_api.h b/third_party/ipfs_client/include/ipfs_client/context_api.h -new file mode 100644 -index 0000000000000..dc46f903e17ec ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/context_api.h -@@ -0,0 +1,91 @@ -+#ifndef IPFS_CONTEXT_API_H_ -+#define IPFS_CONTEXT_API_H_ -+ -+#include "crypto/hasher.h" -+#include "dag_cbor_value.h" -+#include "gateway_spec.h" -+#include "http_request_description.h" -+#include "ipns_cbor_entry.h" -+#include "multi_hash.h" -+#include "signing_key_type.h" -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+namespace ipfs { -+class IpfsRequest; -+class DagJsonValue; -+ -+/** -+ * \brief Interface that provides functionality from whatever -+ * environment you're using this library in. -+ * \note A user of this library must implement this, but will probably do so -+ * only once. -+ */ -+class ContextApi : public std::enable_shared_from_this { -+ public: -+ ContextApi(); -+ virtual ~ContextApi() noexcept {} -+ -+ using HttpRequestDescription = ::ipfs::HttpRequestDescription; -+ using HeaderAccess = std::function; -+ using HttpCompleteCallback = -+ std::function; -+ virtual void SendHttpRequest(HttpRequestDescription, -+ HttpCompleteCallback cb) const = 0; -+ -+ using DnsTextResultsCallback = -+ std::function const&)>; -+ using DnsTextCompleteCallback = std::function; -+ virtual void SendDnsTextRequest(std::string hostname, -+ DnsTextResultsCallback, -+ DnsTextCompleteCallback) = 0; -+ -+ /*! -+ * \brief Determine a mime type for a given file. -+ * \param extension - "File extension" not including ., e.g. "html" -+ * \param content - The content of the resource or a large prefix thereof -+ * \param url - A URL it was fetched from (of any sort, ipfs:// is fine) -+ */ -+ virtual std::string MimeType(std::string extension, -+ std::string_view content, -+ std::string const& url) const = 0; -+ -+ /*! -+ * \brief Remove URL escaping, e.g. %20 -+ * \param url_comp - a single component of the URL, e.g. a element of the path -+ * not including / -+ * \return The unescaped string -+ */ -+ virtual std::string UnescapeUrlComponent(std::string_view url_comp) const = 0; -+ -+ virtual std::unique_ptr ParseCbor(ByteView) const = 0; -+ virtual std::unique_ptr ParseJson(std::string_view) const = 0; -+ -+ using IpnsCborEntry = ::ipfs::IpnsCborEntry; -+ -+ using SigningKeyType = ::ipfs::SigningKeyType; -+ using ByteView = ::ipfs::ByteView; -+ virtual bool VerifyKeySignature(SigningKeyType, -+ ByteView signature, -+ ByteView data, -+ ByteView key_bytes) const = 0; -+ -+ std::optional> Hash(HashType, ByteView data); -+ -+ virtual std::optional GetGateway(std::size_t index) const = 0; -+ virtual unsigned GetGatewayRate(std::string_view); -+ virtual void SetGatewayRate(std::string_view, unsigned); -+ -+ protected: -+ std::unordered_map> hashers_; -+}; -+ -+} // namespace ipfs -+ -+#endif -diff --git a/third_party/ipfs_client/include/ipfs_client/crypto/hasher.h b/third_party/ipfs_client/include/ipfs_client/crypto/hasher.h -new file mode 100644 -index 0000000000000..5222d622ce998 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/crypto/hasher.h -@@ -0,0 +1,18 @@ -+#ifndef IPFS_HASHER_H_ -+#define IPFS_HASHER_H_ -+ -+#include -+ -+#include -+#include -+ -+namespace ipfs::crypto { -+class Hasher { -+ public: -+ virtual ~Hasher() noexcept {} -+ -+ virtual std::optional> hash(ByteView) = 0; -+}; -+} // namespace ipfs::crypto -+ -+#endif // IPFS_HASHER_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/dag_cbor_value.h b/third_party/ipfs_client/include/ipfs_client/dag_cbor_value.h -new file mode 100644 -index 0000000000000..71cb538776361 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/dag_cbor_value.h -@@ -0,0 +1,35 @@ -+#ifndef IPFS_DAG_CBOR_VALUE_H_ -+#define IPFS_DAG_CBOR_VALUE_H_ -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+namespace ipfs { -+class DagCborValue { -+ public: -+ virtual std::unique_ptr at(std::string_view) const = 0; -+ virtual std::optional as_unsigned() const = 0; -+ virtual std::optional as_signed() const = 0; -+ virtual std::optional as_float() const = 0; -+ virtual std::optional as_string() const = 0; -+ virtual std::optional> as_bytes() const = 0; -+ virtual std::optional as_bool() const = 0; -+ virtual std::optional as_link() const = 0; -+ virtual bool is_map() const = 0; -+ virtual bool is_array() const = 0; -+ using MapElementCallback = std::function; -+ using ArrayElementCallback = std::function; -+ virtual void iterate_map(MapElementCallback) const = 0; -+ virtual void iterate_array(ArrayElementCallback) const = 0; -+ std::string html() const; -+ void html(std::ostream&) const; -+ virtual ~DagCborValue() noexcept {} -+}; -+} -+ -+#endif // IPFS_DAG_CBOR_VALUE_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/dag_json_value.h b/third_party/ipfs_client/include/ipfs_client/dag_json_value.h -new file mode 100644 -index 0000000000000..32e170c439438 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/dag_json_value.h -@@ -0,0 +1,26 @@ -+#ifndef IPFS_DAG_JSON_VALUE_H_ -+#define IPFS_DAG_JSON_VALUE_H_ -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+namespace ipfs { -+class DagJsonValue { -+ public: -+ virtual std::string pretty_print() const = 0; -+ virtual std::unique_ptr operator[](std::string_view) const = 0; -+ virtual std::optional get_if_string() const = 0; -+ virtual std::optional> object_keys() const = 0; -+ virtual bool iterate_list(std::function) const = 0; -+ virtual ~DagJsonValue() noexcept; -+ -+ std::optional get_if_link() const; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_DAG_JSON_VALUE_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gateways.h b/third_party/ipfs_client/include/ipfs_client/gateways.h -new file mode 100644 -index 0000000000000..9852971b36199 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gateways.h -@@ -0,0 +1,57 @@ -+#ifndef CHROMIUM_IPFS_GATEWAYS_H_ -+#define CHROMIUM_IPFS_GATEWAYS_H_ -+ -+#include "gateway_spec.h" -+#include "vocab/flat_mapset.h" -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+namespace ipfs { -+using GatewayList = std::vector; -+class ContextApi; -+ -+/*! -+ * \brief All known IPFS gateways -+ */ -+class Gateways { -+ flat_map known_gateways_; -+ std::default_random_engine random_engine_; -+ std::geometric_distribution dist_; -+ int up_log_ = 1; -+ -+ public: -+ /*! -+ * \brief The hard-coded list of gateways at startup -+ */ -+ static GatewayList DefaultGateways(); -+ -+ Gateways(); -+ ~Gateways(); -+ GatewayList GenerateList(); ///< Get a sorted list of gateways for requesting -+ -+ /*! -+ * \brief Good gateway, handle more! -+ * \param prefix - identify the gateway by its URL prefix -+ */ -+ void promote(std::string const& prefix); -+ -+ /*! -+ * \brief Bad gateway, move toward the back of the line. -+ * \param prefix - identify the gateway by its URL prefix -+ */ -+ void demote(std::string const& prefix); -+ -+ /*! -+ * \brief Bulk load a bunch of new gateways -+ * \param prefices - list of URL gateways by prefix -+ */ -+ void AddGateways(std::vector prefices); -+}; -+} // namespace ipfs -+ -+#endif // CHROMIUM_IPFS_GATEWAYS_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gw/block_request_splitter.h b/third_party/ipfs_client/include/ipfs_client/gw/block_request_splitter.h -new file mode 100644 -index 0000000000000..0f308a996d360 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gw/block_request_splitter.h -@@ -0,0 +1,17 @@ -+#ifndef IPFS_BLOCK_REQUEST_SPLITTER_H_ -+#define IPFS_BLOCK_REQUEST_SPLITTER_H_ -+ -+#include "requestor.h" -+ -+namespace ipfs { -+class ContextApi; -+} -+ -+namespace ipfs::gw { -+class BlockRequestSplitter final : public Requestor { -+ HandleOutcome handle(RequestPtr) override; -+ std::string_view name() const override; -+}; -+} // namespace ipfs::gw -+ -+#endif // IPFS_BLOCK_REQUEST_SPLITTER_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gw/default_requestor.h b/third_party/ipfs_client/include/ipfs_client/gw/default_requestor.h -new file mode 100644 -index 0000000000000..06b5970e1d103 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gw/default_requestor.h -@@ -0,0 +1,14 @@ -+#ifndef IPFS_DEFAULT_REQUESTOR_LIST_H_ -+#define IPFS_DEFAULT_REQUESTOR_LIST_H_ -+ -+#include "requestor.h" -+ -+#include -+ -+namespace ipfs::gw { -+std::shared_ptr default_requestor(GatewayList, -+ std::shared_ptr early, -+ std::shared_ptr); -+} -+ -+#endif // IPFS_DEFAULT_REQUESTOR_LIST_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gw/dnslink_requestor.h b/third_party/ipfs_client/include/ipfs_client/gw/dnslink_requestor.h -new file mode 100644 -index 0000000000000..4910fe61976c8 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gw/dnslink_requestor.h -@@ -0,0 +1,20 @@ -+#ifndef IPFS_DNSLINK_REQUESTOR_H_ -+#define IPFS_DNSLINK_REQUESTOR_H_ -+ -+#include "requestor.h" -+ -+namespace ipfs { -+class ContextApi; -+} -+ -+namespace ipfs::gw { -+class DnsLinkRequestor final : public Requestor { -+ public: -+ explicit DnsLinkRequestor(std::shared_ptr); -+ -+ HandleOutcome handle(RequestPtr) override; -+ std::string_view name() const override; -+}; -+} // namespace ipfs::gw -+ -+#endif // IPFS_DNSLINK_REQUESTOR_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gw/gateway_request.h b/third_party/ipfs_client/include/ipfs_client/gw/gateway_request.h -new file mode 100644 -index 0000000000000..2e792ae9ed044 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gw/gateway_request.h -@@ -0,0 +1,79 @@ -+#ifndef IPFS_TRUSTLESS_REQUEST_H_ -+#define IPFS_TRUSTLESS_REQUEST_H_ -+ -+#include -+#include -+ -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+namespace ipfs { -+class IpfsRequest; -+class Orchestrator; -+namespace ipld { -+class DagNode; -+} -+} // namespace ipfs -+ -+namespace ipfs::gw { -+class Requestor; -+ -+enum class Type : char { -+ Block, -+ Car, -+ Ipns, -+ DnsLink, -+ Providers, -+ Identity, -+ Zombie -+}; -+std::string_view name(Type); -+ -+constexpr std::size_t BLOCK_RESPONSE_BUFFER_SIZE = 2 * 1024 * 1024; -+ -+class GatewayRequest { -+ std::shared_ptr orchestrator_; -+ std::vector> bytes_received_hooks; -+ -+ void ParseNodes(std::string_view, ContextApi* api); -+ -+ public: -+ Type type; -+ std::string main_param; ///< CID, IPNS name, hostname -+ std::string path; ///< For CAR requests -+ std::shared_ptr dependent; -+ std::optional cid; -+ short parallel = 0; -+ std::string affinity; -+ flat_set failures; -+ -+ std::string url_suffix() const; -+ std::string_view accept() const; -+ std::string_view identity_data() const; -+ short timeout_seconds() const; -+ bool is_http() const; -+ std::optional max_response_size() const; -+ std::optional describe_http(std::string_view) const; -+ std::string debug_string() const; -+ void orchestrator(std::shared_ptr const&); -+ -+ bool RespondSuccessfully(std::string_view, -+ std::shared_ptr const& api); -+ void Hook(std::function); -+ bool PartiallyRedundant() const; -+ -+ static std::shared_ptr fromIpfsPath(SlashDelimited); -+}; -+ -+} // namespace ipfs::gw -+ -+inline std::ostream& operator<<(std::ostream& s, ipfs::gw::Type t) { -+ return s << name(t); -+} -+ -+#endif // IPFS_TRUSTLESS_REQUEST_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gw/inline_request_handler.h b/third_party/ipfs_client/include/ipfs_client/gw/inline_request_handler.h -new file mode 100644 -index 0000000000000..0301c561c5735 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gw/inline_request_handler.h -@@ -0,0 +1,14 @@ -+#ifndef IPFS_INLINE_REQUEST_HANDLER_H_ -+#define IPFS_INLINE_REQUEST_HANDLER_H_ -+ -+#include "requestor.h" -+ -+namespace ipfs::gw { -+class InlineRequestHandler final : public Requestor { -+ public: -+ HandleOutcome handle(RequestPtr) override; -+ std::string_view name() const override; -+}; -+} // namespace ipfs::gw -+ -+#endif // IPFS_INLINE_REQUEST_HANDLER_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gw/requestor.h b/third_party/ipfs_client/include/ipfs_client/gw/requestor.h -new file mode 100644 -index 0000000000000..634c36730b1ea ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gw/requestor.h -@@ -0,0 +1,55 @@ -+#ifndef IPFS_REQUESTOR_H_ -+#define IPFS_REQUESTOR_H_ -+ -+#include -+#include -+#include -+ -+namespace ipfs::ipld { -+class DagNode; -+} -+namespace ipfs { -+class ContextApi; -+struct Response; -+} // namespace ipfs -+ -+namespace ipfs::gw { -+class GatewayRequest; -+using RequestPtr = std::shared_ptr; -+ -+class Requestor : public std::enable_shared_from_this { -+ protected: -+ Requestor() {} -+ -+ friend class RequestorPool; -+ enum class HandleOutcome : char { -+ NOT_HANDLED = 'N', -+ PENDING = 'P', -+ DONE = 'D', -+ PARALLEL = 'L', -+ MAYBE_LATER = 'M' -+ }; -+ virtual HandleOutcome handle(RequestPtr) = 0; -+ -+ void definitive_failure(RequestPtr) const; -+ void forward(RequestPtr) const; -+ -+ std::shared_ptr api_; -+ -+ public: -+ using RequestPtr = ::ipfs::gw::RequestPtr; -+ virtual std::string_view name() const = 0; -+ -+ virtual ~Requestor() noexcept {} -+ void request(std::shared_ptr); -+ Requestor& or_else(std::shared_ptr p); -+ void api(std::shared_ptr); -+ -+ void TestAccess(void*); -+ -+ private: -+ std::shared_ptr next_; -+}; -+} // namespace ipfs::gw -+ -+#endif // IPFS_REQUESTOR_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/gw/terminating_requestor.h b/third_party/ipfs_client/include/ipfs_client/gw/terminating_requestor.h -new file mode 100644 -index 0000000000000..3fe7a01e752f5 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/gw/terminating_requestor.h -@@ -0,0 +1,15 @@ -+#ifndef IPFS_TERMINATING_REQUESTOR_H_ -+#define IPFS_TERMINATING_REQUESTOR_H_ -+ -+#include "requestor.h" -+ -+namespace ipfs::gw { -+class TerminatingRequestor : public Requestor { -+ public: -+ using HandleOutcome = Requestor::HandleOutcome; -+ std::string_view name() const override; -+ HandleOutcome handle(RequestPtr) override; -+}; -+} // namespace ipfs::gw -+ -+#endif // IPFS_TERMINATING_REQUESTOR_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/http_request_description.h b/third_party/ipfs_client/include/ipfs_client/http_request_description.h -new file mode 100644 -index 0000000000000..f3f07d58ea199 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/http_request_description.h -@@ -0,0 +1,20 @@ -+#ifndef IPFS_HTTP_REQUEST_DESCRIPTION_H_ -+#define IPFS_HTTP_REQUEST_DESCRIPTION_H_ -+ -+#include -+#include -+ -+#include -+ -+namespace ipfs { -+struct HttpRequestDescription { -+ std::string url; -+ int timeout_seconds; -+ std::string accept; -+ std::optional max_response_size; -+ bool operator==(HttpRequestDescription const&) const; -+ bool operator<(HttpRequestDescription const&) const; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_HTTP_REQUEST_DESCRIPTION_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/identity_cid.h b/third_party/ipfs_client/include/ipfs_client/identity_cid.h -new file mode 100644 -index 0000000000000..29efd30d1c6b2 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/identity_cid.h -@@ -0,0 +1,14 @@ -+#ifndef IPFS_IDENTITY_CID_H_ -+#define IPFS_IDENTITY_CID_H_ 1 -+ -+#include -+ -+#include -+ -+namespace ipfs { -+namespace id_cid { -+ipfs::Cid forText(std::string_view); -+} // namespace id_cid -+} // namespace ipfs -+ -+#endif -diff --git a/third_party/ipfs_client/include/ipfs_client/ipfs_request.h b/third_party/ipfs_client/include/ipfs_client/ipfs_request.h -new file mode 100644 -index 0000000000000..eda8bdfa7010b ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/ipfs_request.h -@@ -0,0 +1,33 @@ -+#ifndef IPFS_IPFS_REQUEST_H_ -+#define IPFS_IPFS_REQUEST_H_ -+ -+#include -+ -+#include -+#include -+#include -+ -+namespace ipfs { -+struct Response; -+class IpfsRequest { -+ public: -+ using Finisher = std::function; -+ -+ private: -+ std::string path_; -+ Finisher callback_; -+ std::size_t waiting_ = 0UL; -+ -+ public: -+ IpfsRequest(std::string path, Finisher); -+ SlashDelimited path() const { return SlashDelimited{path_}; } -+ void finish(Response& r); -+ void till_next(std::size_t); -+ bool ready_after(); -+ void new_path(std::string_view); -+ -+ static std::shared_ptr fromUrl(std::string url, Finisher); -+}; -+} // namespace ipfs -+ -+#endif // IPFS_IPFS_REQUEST_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/ipld/dag_node.h b/third_party/ipfs_client/include/ipfs_client/ipld/dag_node.h -new file mode 100644 -index 0000000000000..1c66f4fd1c755 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/ipld/dag_node.h -@@ -0,0 +1,102 @@ -+#ifndef IPFS_DAG_NODE_H_ -+#define IPFS_DAG_NODE_H_ -+ -+#include "link.h" -+#include "resolution_state.h" -+ -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+namespace ipfs { -+class PbDag; -+class ContextApi; -+struct ValidatedIpns; -+} // namespace ipfs -+namespace libp2p::multi { -+struct ContentIdentifier; -+} -+namespace ipfs::ipld { -+ -+using NodePtr = std::shared_ptr; -+class DirShard; -+ -+struct MoreDataNeeded { -+ MoreDataNeeded(std::string one) : ipfs_abs_paths_{{one}} {} -+ template -+ MoreDataNeeded(Range const& many) -+ : ipfs_abs_paths_(many.begin(), many.end()) {} -+ std::vector ipfs_abs_paths_; -+ bool insist_on_car = false; -+}; -+enum class ProvenAbsent {}; -+struct PathChange { -+ std::string new_path; -+}; -+ -+using ResolveResult = -+ std::variant; -+/** -+ * @brief A block, an IPNS record, etc. -+ */ -+class DagNode : public std::enable_shared_from_this { -+ Link* FindChild(std::string_view); -+ static void Descend(ResolutionState&); -+ -+ protected: -+ std::vector> links_; -+ std::shared_ptr api_; -+ -+ ///< When the next path element is what's needed, and it should already be a -+ ///< link known about... -+ ResolveResult CallChild(ResolutionState&); -+ -+ ///< As before, but it might be possible to create on the fly if not known -+ ResolveResult CallChild(ResolutionState&, -+ std::function gen_child); -+ -+ ///< When the child's name is not the next element in the path, but it must be -+ ///< known about. e.g. index.html for a path ending in a directory -+ ResolveResult CallChild(ResolutionState&, std::string_view link_key); -+ -+ ///< Add the link if not present, then CallChild(ResolutionState) -+ ResolveResult CallChild(ResolutionState&, -+ std::string_view link_key, -+ std::string_view block_key); -+ -+ public: -+ virtual ResolveResult resolve(ResolutionState& params) = 0; -+ ResolveResult resolve(SlashDelimited initial_path, BlockLookup); -+ -+ static NodePtr fromBytes(std::shared_ptr const& api, -+ Cid const&, -+ ByteView bytes); -+ static NodePtr fromBytes(std::shared_ptr const& api, -+ Cid const&, -+ std::string_view bytes); -+ static NodePtr fromBlock(PbDag const&); -+ static NodePtr fromIpnsRecord(ValidatedIpns const&); -+ -+ virtual ~DagNode() noexcept {} -+ -+ virtual NodePtr rooted(); -+ virtual NodePtr deroot(); -+ virtual DirShard* as_hamt(); // Wish I had access to dynamic_cast -+ -+ void set_api(std::shared_ptr); -+}; -+} // namespace ipfs::ipld -+ -+std::ostream& operator<<(std::ostream&, ipfs::ipld::PathChange const&); -+ -+#endif // IPFS_DAG_NODE_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/ipld/link.h b/third_party/ipfs_client/include/ipfs_client/ipld/link.h -new file mode 100644 -index 0000000000000..a0d290b25dd3d ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/ipld/link.h -@@ -0,0 +1,22 @@ -+#ifndef IPFS_LINK_H_ -+#define IPFS_LINK_H_ -+ -+#include -+#include -+ -+namespace ipfs::ipld { -+ -+class DagNode; -+using Ptr = std::shared_ptr; -+ -+class Link { -+ public: -+ std::string cid; -+ Ptr node; -+ -+ Link(std::string); -+ explicit Link(std::string, std::shared_ptr); -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_LINK_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/ipld/resolution_state.h b/third_party/ipfs_client/include/ipfs_client/ipld/resolution_state.h -new file mode 100644 -index 0000000000000..82e330cea4355 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/ipld/resolution_state.h -@@ -0,0 +1,36 @@ -+#ifndef IPFS_RESOLUTION_STATE_H_ -+#define IPFS_RESOLUTION_STATE_H_ -+ -+#include -+ -+#include -+#include -+ -+namespace ipfs { -+class ContextApi; -+} -+ -+namespace ipfs::ipld { -+class DagNode; -+using NodePtr = std::shared_ptr; -+using BlockLookup = std::function; -+ -+class ResolutionState { -+ friend class DagNode; -+ std::string resolved_path_components; -+ SlashDelimited unresolved_path; -+ BlockLookup get_available_block; -+ -+ public: -+ SlashDelimited MyPath() const; -+ SlashDelimited PathToResolve() const; -+ bool IsFinalComponent() const; -+ std::string NextComponent(ContextApi const*) const; -+ NodePtr GetBlock(std::string const& block_key) const; -+ -+ ResolutionState WithPath(std::string_view) const; -+ ResolutionState RestartResolvedPath() const; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_RESOLUTION_STATE_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/ipns_cbor_entry.h b/third_party/ipfs_client/include/ipfs_client/ipns_cbor_entry.h -new file mode 100644 -index 0000000000000..230339793543c ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/ipns_cbor_entry.h -@@ -0,0 +1,21 @@ -+#ifndef IPFS_IPNS_CBOR_ENTRY_H_ -+#define IPFS_IPNS_CBOR_ENTRY_H_ -+ -+#include -+#include -+ -+namespace ipfs { -+/*! -+ * \brief Parsed out data contained in the CBOR data of an IPNS record. -+ */ -+struct IpnsCborEntry { -+ std::string value; ///< The "value" (target) the name points at -+ std::string validity; ///< Value to compare for validity (i.e. expiration) -+ std::uint64_t validityType; ///< Way to deterimine current validity -+ std::uint64_t sequence; ///< Distinguish other IPNS records for the same name -+ std::uint64_t ttl; ///< Recommended caching time -+}; -+ -+} // namespace ipfs -+ -+#endif // IPFS_IPNS_CBOR_ENTRY_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/ipns_names.h b/third_party/ipfs_client/include/ipfs_client/ipns_names.h -new file mode 100644 -index 0000000000000..b611365b87874 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/ipns_names.h -@@ -0,0 +1,69 @@ -+#ifndef IPNS_NAME_RESOLVER_H_ -+#define IPNS_NAME_RESOLVER_H_ -+ -+#include -+#include -+ -+#include -+ -+namespace ipfs { -+ -+/*! -+ * \brief Fast synchronous access to IPNS & DNSLink name resolution -+ */ -+class IpnsNames { -+ flat_map names_; -+ -+ public: -+ IpnsNames(); -+ ~IpnsNames(); -+ -+ /*! -+ * \brief Get the already-known "value"/target of a given name -+ * \param name - either a mb-mf IPNS (key) name, or a host with DNSLink -+ * \return -+ * * if resolution is incomplete: "" -+ * * if it is known not to resolve: kNoSuchName -+ * * otherwise an IPFS path witout leading /, e.g.: -+ * - ipfs/bafybeicfqz46dj67nkhxaylqd5sknnidsr4oaw4hhsjrgdmcwt73sow2d4/ -+ * - ipns/k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8 -+ */ -+ std::string_view NameResolvedTo(std::string_view name) const; -+ -+ /*! -+ * \brief Store an IPNS record that already validated for this name -+ * \param name - The name that resolves with this -+ * \param rec - The record modulo validation bits -+ */ -+ void AssignName(std::string const& name, ValidatedIpns rec); -+ -+ /*! -+ * \brief Assign a target path to a DNSLink host -+ * \param host - The original host NOT including a "_dnslink." prefix -+ * \param target - an IPFS path witout leading / -+ */ -+ void AssignDnsLink(std::string const& host, std::string_view target); -+ -+ /*! -+ * \brief Store the definitive absence of a resolution -+ * \details This is useful because code will check resolution here before -+ * trying to resolve it fresh again, and you can stop that if you know -+ * it will never work. -+ */ -+ void NoSuchName(std::string const& name); -+ -+ /*! -+ * \brief Fetch the all the stored IPNS record data -+ * \param name - the IPNS name it was stored with -+ * \return nullptr if missing, otherwise non-owning pointer to record -+ */ -+ ValidatedIpns const* Entry(std::string const& name); -+ -+ /*! -+ * \brief A special value constant -+ */ -+ static constexpr std::string_view kNoSuchName{"NO_SUCH_NAME"}; -+}; -+} // namespace ipfs -+ -+#endif // IPNS_NAME_RESOLVER_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/ipns_record.h b/third_party/ipfs_client/include/ipfs_client/ipns_record.h -new file mode 100644 -index 0000000000000..a6bd168a4af60 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/ipns_record.h -@@ -0,0 +1,75 @@ -+#ifndef IPFS_IPNS_RECORD_H_ -+#define IPFS_IPNS_RECORD_H_ -+ -+#include -+ -+#include -+ -+#if __has_include() -+#include -+#else -+#include "ipfs_client/keys.pb.h" -+#endif -+ -+#include -+#include -+ -+namespace libp2p::peer { -+class PeerId; -+} -+namespace libp2p::multi { -+struct ContentIdentifier; -+} -+ -+namespace ipfs { -+ -+class Cid; -+class ContextApi; -+ -+constexpr static std::size_t MAX_IPNS_PB_SERIALIZED_SIZE = 10 * 1024; -+ -+std::optional ValidateIpnsRecord(ByteView top_level_bytes, -+ Cid const& name, -+ ContextApi&); -+ -+/*! -+ * \brief Data from IPNS record modulo the verification parts -+ */ -+struct ValidatedIpns { -+ std::string value; ///< The path the record claims the IPNS name points to -+ std::time_t use_until; ///< An expiration timestamp -+ std::time_t cache_until; ///< Inspired by TTL -+ -+ /*! -+ * \brief The version of the record -+ * \details Higher sequence numbers obsolete lower ones -+ */ -+ std::uint64_t sequence; -+ std::int64_t resolution_ms; ///< How long it took to fetch the record -+ -+ /*! -+ * \brief When the record was fetched -+ */ -+ std::time_t fetch_time = std::time(nullptr); -+ std::string gateway_source; ///< Who gave us this record? -+ -+ ValidatedIpns(); ///< Create an invalid default object -+ ValidatedIpns(IpnsCborEntry const&); -+ ValidatedIpns(ValidatedIpns&&); -+ ValidatedIpns(ValidatedIpns const&); -+ ValidatedIpns& operator=(ValidatedIpns const&); -+ -+ std::string Serialize() const; ///< Turn into a well-defined list of bytes -+ -+ /*! -+ * \brief Create a ValidatedIpns from untyped bytes -+ * \param bytes - Output from a former call to Serialize() -+ * \note Is used by disk cache -+ * \return Recreation of the old object -+ */ -+ static ValidatedIpns Deserialize(std::string bytes); -+}; -+ -+} // namespace ipfs -+ -+#endif // IPFS_IPNS_RECORD_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/json_cbor_adapter.h b/third_party/ipfs_client/include/ipfs_client/json_cbor_adapter.h -new file mode 100644 -index 0000000000000..5ed52ad465b0c ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/json_cbor_adapter.h -@@ -0,0 +1,155 @@ -+#ifndef IPFS_JSON_CBOR_ADAPTER_H_ -+#define IPFS_JSON_CBOR_ADAPTER_H_ -+ -+#include -+#include -+ -+#include -+#include -+ -+#if __has_include() -+ -+#include -+#define HAS_JSON_CBOR_ADAPTER 1 -+ -+namespace ipfs { -+// LCOV_EXCL_START -+class JsonCborAdapter final : public DagCborValue, public DagJsonValue { -+ nlohmann::json data_; -+ -+ public: -+ using Cid = ipfs::Cid; -+ JsonCborAdapter(nlohmann::json data) : data_{data} { -+ if (data_.is_array() && data_.size() == 1UL) { -+ data_ = data_[0]; -+ } -+ } -+ std::unique_ptr at(std::string_view k) const override { -+ if (data_.is_object() && data_.contains(k)) { -+ return std::make_unique(data_.at(k)); -+ } -+ return {}; -+ } -+ std::unique_ptr operator[](std::string_view k) const override { -+ if (data_.is_object() && data_.contains(k)) { -+ return std::make_unique(data_[k]); -+ } -+ return {}; -+ } -+ std::optional as_unsigned() const override { -+ if (data_.is_number_unsigned()) { -+ return data_.get(); -+ } -+ return std::nullopt; -+ } -+ std::optional as_signed() const { -+ if (data_.is_number_integer()) { -+ return data_.get(); -+ } else if (auto ui = as_unsigned()) { -+ if (*ui <= std::numeric_limits::max()) { -+ return static_cast(*ui); -+ } -+ } -+ return std::nullopt; -+ } -+ std::optional as_float() const override { -+ if (data_.is_number_float()) { -+ return data_.get(); -+ } -+ return std::nullopt; -+ } -+ std::optional as_string() const override { -+ if (data_.is_string()) { -+ return data_.get(); -+ } -+ return std::nullopt; -+ } -+ std::optional get_if_string() const override { -+ return as_string(); -+ } -+ std::optional as_bool() const override { -+ if (data_.is_boolean()) { -+ return data_.get(); -+ } -+ return std::nullopt; -+ } -+ std::optional> as_bytes() const override { -+ if (data_.is_binary()) { -+ return data_.get_binary(); -+ } -+ return std::nullopt; -+ } -+ std::optional as_link() const override { -+ if (!data_.is_binary()) { -+ return std::nullopt; -+ } -+ auto& bin = data_.get_binary(); -+ if (!bin.has_subtype() || bin.subtype() != 42) { -+ return std::nullopt; -+ } -+ if (bin.size() < 6) { -+ return std::nullopt; -+ } -+ if (bin[0]) { -+ return std::nullopt; -+ } -+ auto p = reinterpret_cast(bin.data()) + 1UL; -+ Cid from_binary(ByteView{p, bin.size() - 1UL}); -+ if (from_binary.valid()) { -+ return from_binary; -+ } else { -+ return std::nullopt; -+ } -+ } -+ bool is_map() const override {return data_.is_object();} -+ bool is_array() const override {return data_.is_array();} -+ void iterate_map(MapElementCallback cb) const override { -+ if (!is_map()) { -+ return; -+ } -+ for (auto& [k,v] : data_.items()) { -+ JsonCborAdapter el(v); -+ cb(k, el); -+ } -+ } -+ void iterate_array(ArrayElementCallback cb) const override { -+ if (!is_array()) { -+ return; -+ } -+ for (auto& v : data_) { -+ JsonCborAdapter el(v); -+ cb(el); -+ } -+ } -+ std::string pretty_print() const override { -+ std::ostringstream result; -+ result << std::setw(2) << data_; -+ return result.str(); -+ } -+ std::optional> object_keys() const override { -+ if (!data_.is_object()) { -+ return std::nullopt; -+ } -+ std::vector rv; -+ for (auto& [k, v] : data_.items()) { -+ rv.push_back(k); -+ } -+ return rv; -+ } -+ bool iterate_list( -+ std::function cb) const override { -+ if (!data_.is_array()) { -+ return false; -+ } -+ for (auto& v : data_) { -+ JsonCborAdapter wrap(v); -+ cb(wrap); -+ } -+ return true; -+ } -+}; -+} // namespace ipfs -+ -+#endif -+ -+#endif // IPFS_JSON_CBOR_ADAPTER_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/logger.h b/third_party/ipfs_client/include/ipfs_client/logger.h -new file mode 100644 -index 0000000000000..35191ac5f832c ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/logger.h -@@ -0,0 +1,34 @@ -+#ifndef IPFS_LOGGER_H_ -+#define IPFS_LOGGER_H_ -+ -+#include -+ -+namespace ipfs::log { -+ -+enum class Level { -+ TRACE = -2, -+ DEBUG = -1, -+ INFO = 0, -+ WARN = 1, -+ ERROR = 2, -+ FATAL = 3, -+ OFF -+}; -+ -+void SetLevel(Level); -+ -+using Handler = void (*)(std::string const&, char const*, int, Level); -+void SetHandler(Handler); -+ -+void DefaultHandler(std::string const& message, -+ char const* source_file, -+ int source_line, -+ Level for_prefix); -+ -+std::string_view LevelDescriptor(Level); -+ -+bool IsInitialized(); -+ -+} // namespace ipfs::log -+ -+#endif // LOGGER_H -diff --git a/third_party/ipfs_client/include/ipfs_client/multi_base.h b/third_party/ipfs_client/include/ipfs_client/multi_base.h -new file mode 100644 -index 0000000000000..8c09b97345635 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/multi_base.h -@@ -0,0 +1,42 @@ -+#ifndef IPFS_MB_PREFIXES_H_ -+#define IPFS_MB_PREFIXES_H_ -+ -+#include -+ -+#include -+#include -+#include -+#include -+ -+namespace ipfs::mb { -+ -+// https://github.com/multiformats/multibase/blob/master/multibase.csv -+enum class Code : char { -+ IDENTITY = '\0', -+ UNSUPPORTED = '1', -+ BASE16_LOWER = 'f', -+ BASE16_UPPER = 'F', -+ BASE32_LOWER = 'b', -+ BASE32_UPPER = 'B', -+ BASE36_LOWER = 'k', -+ BASE36_UPPER = 'K', -+ BASE58_BTC = 'z', -+ BASE64 = 'm' -+}; -+Code CodeFromPrefix(char c); -+std::string_view GetName(Code); -+ -+using Decoder = std::vector (*)(std::string_view); -+using Encoder = std::string (*)(ByteView); -+struct Codec { -+ Decoder const decode; -+ Encoder const encode; -+ std::string_view const name; -+ static Codec const* Get(Code); -+}; -+ -+std::string encode(Code, ByteView); -+std::optional> decode(std::string_view mb_str); -+} // namespace ipfs::mb -+ -+#endif // IPFS_MB_PREFIXES_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/multi_hash.h b/third_party/ipfs_client/include/ipfs_client/multi_hash.h -new file mode 100644 -index 0000000000000..6ed78f5e674dc ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/multi_hash.h -@@ -0,0 +1,32 @@ -+#ifndef IPFS_MULTI_HASH_H_ -+#define IPFS_MULTI_HASH_H_ -+ -+#include -+ -+#include -+ -+namespace ipfs { -+enum class HashType { INVALID = -1, IDENTITY = 0, SHA2_256 = 0X12 }; -+constexpr std::uint16_t MaximumHashLength = 127; -+ -+HashType Validate(HashType); -+std::string_view GetName(HashType); -+class MultiHash { -+ public: -+ MultiHash() = default; -+ explicit MultiHash(ByteView); -+ explicit MultiHash(HashType, ByteView digest); -+ -+ bool ReadPrefix(ByteView&); -+ -+ bool valid() const; -+ HashType type() const { return type_; } -+ ByteView digest() const { return hash_; } -+ -+ private: -+ HashType type_ = HashType::INVALID; -+ std::vector hash_; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_MULTI_HASH_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/multicodec.h b/third_party/ipfs_client/include/ipfs_client/multicodec.h -new file mode 100644 -index 0000000000000..bf8d89b6c27e2 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/multicodec.h -@@ -0,0 +1,23 @@ -+#ifndef IPFS_MUTLICODEC_H_ -+#define IPFS_MUTLICODEC_H_ -+ -+#include -+#include -+ -+#include -+ -+namespace ipfs { -+enum class MultiCodec : std::uint32_t { -+ INVALID = std::numeric_limits::max(), -+ IDENTITY = 0x00, -+ RAW = 0x55, -+ DAG_PB = 0x70, -+ DAG_CBOR = 0x71, -+ LIBP2P_KEY = 0x72, -+ DAG_JSON = 0x0129, -+}; -+MultiCodec Validate(MultiCodec); -+std::string_view GetName(MultiCodec); -+} // namespace ipfs -+ -+#endif // IPFS_MUTLICODEC_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/orchestrator.h b/third_party/ipfs_client/include/ipfs_client/orchestrator.h -new file mode 100644 -index 0000000000000..f204dde799b3e ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/orchestrator.h -@@ -0,0 +1,45 @@ -+#ifndef IPFS_ORCHESTRATOR_H_ -+#define IPFS_ORCHESTRATOR_H_ -+ -+#include "ipfs_client/ipld/dag_node.h" -+ -+#include -+#include -+ -+#include -+#include -+ -+namespace ipfs { -+ -+class ContextApi; -+ -+class Orchestrator : public std::enable_shared_from_this { -+ public: -+ using GatewayAccess = -+ std::function)>; -+ using MimeDetection = std::function< -+ std::string(std::string, std::string_view, std::string const&)>; -+ explicit Orchestrator(std::shared_ptr requestor, -+ std::shared_ptr = {}); -+ void build_response(std::shared_ptr); -+ bool add_node(std::string key, ipld::NodePtr); -+ bool has_key(std::string const& k) const; -+ -+ private: -+ flat_map dags_; -+ // GatewayAccess gw_requestor_; -+ std::shared_ptr api_; -+ std::shared_ptr requestor_; -+ -+ void from_tree(std::shared_ptr, -+ ipld::NodePtr&, -+ SlashDelimited, -+ std::string const&); -+ bool gw_request(std::shared_ptr, -+ SlashDelimited path, -+ std::string const& aff); -+ std::string sniff(SlashDelimited, std::string const&) const; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_ORCHESTRATOR_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/response.h b/third_party/ipfs_client/include/ipfs_client/response.h -new file mode 100644 -index 0000000000000..3c277994d8b9c ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/response.h -@@ -0,0 +1,27 @@ -+#ifndef IPFS_RESPONSE_H_ -+#define IPFS_RESPONSE_H_ -+ -+#include -+ -+#include -+#include -+#include -+ -+namespace ipfs { -+ -+struct Response { -+ std::string mime_; -+ std::uint16_t status_; -+ std::string body_; -+ std::string location_; -+ -+ static Response PLAIN_NOT_FOUND; -+ static Response IMMUTABLY_GONE; -+ static Response HOST_NOT_FOUND; -+ -+ constexpr static std::uint16_t HOST_NOT_FOUND_STATUS = 503; -+}; -+ -+} // namespace ipfs -+ -+#endif // IPFS_RESPONSE_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/signing_key_type.h b/third_party/ipfs_client/include/ipfs_client/signing_key_type.h -new file mode 100644 -index 0000000000000..4a74ad0f6967b ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/signing_key_type.h -@@ -0,0 +1,14 @@ -+#ifndef IPFS_SIGNING_KEY_TYPE_H_ -+#define IPFS_SIGNING_KEY_TYPE_H_ -+ -+namespace ipfs { -+enum class SigningKeyType : int { -+ RSA, -+ Ed25519, -+ Secp256k1, -+ ECDSA, -+ KeyTypeCount -+}; -+} -+ -+#endif // IPFS_SIGNING_KEY_TYPE_H_ -diff --git a/third_party/ipfs_client/include/ipfs_client/url_spec.h b/third_party/ipfs_client/include/ipfs_client/url_spec.h -new file mode 100644 -index 0000000000000..a61aec25d5968 ---- /dev/null -+++ b/third_party/ipfs_client/include/ipfs_client/url_spec.h -@@ -0,0 +1,24 @@ -+#ifndef IPFS_URL_SPEC_H_ -+#define IPFS_URL_SPEC_H_ -+ -+// TODO - Give more thought to how this interplays with gw::Request -+ -+#include -+#include -+ -+namespace ipfs { -+struct UrlSpec { -+ std::string suffix; -+ std::string_view accept; -+ -+ bool operator<(UrlSpec const& rhs) const { -+ if (suffix != rhs.suffix) { -+ return suffix < rhs.suffix; -+ } -+ return accept < rhs.accept; -+ } -+ bool none() const { return suffix.empty(); } -+}; -+} // namespace ipfs -+ -+#endif // IPFS_URL_SPEC_H_ -diff --git a/third_party/ipfs_client/include/libp2p/common/types.hpp b/third_party/ipfs_client/include/libp2p/common/types.hpp -new file mode 100644 -index 0000000000000..a112d1bf5d3db ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/common/types.hpp -@@ -0,0 +1,39 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef LIBP2P_P2P_COMMON_TYPES_HPP -+#define LIBP2P_P2P_COMMON_TYPES_HPP -+ -+#include "vocab/byte_view.h" -+ -+#include -+#include -+#include -+#include -+ -+namespace libp2p::common { -+/** -+ * Sequence of bytes -+ */ -+using ByteArray = std::vector; -+// using ByteArray = std::string; -+ -+template -+void append(Collection& c, Item&& g) { -+ c.insert(c.end(), g.begin(), g.end()); -+} -+ -+template -+void append(Collection& c, char g) { -+ c.push_back(g); -+} -+ -+/// Hash256 as a sequence of 32 bytes -+using Hash256 = std::array; -+/// Hash512 as a sequence of 64 bytes -+using Hash512 = std::array; -+} // namespace libp2p::common -+ -+#endif // LIBP2P_P2P_COMMON_TYPES_HPP -diff --git a/third_party/ipfs_client/include/libp2p/crypto/key.h b/third_party/ipfs_client/include/libp2p/crypto/key.h -new file mode 100644 -index 0000000000000..8198e41122fdd ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/crypto/key.h -@@ -0,0 +1,100 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef LIBP2P_LIBP2P_CRYPTO_KEY_HPP -+#define LIBP2P_LIBP2P_CRYPTO_KEY_HPP -+ -+#include -+ -+#include "libp2p/common/types.hpp" -+ -+namespace libp2p::crypto { -+ -+using Buffer = libp2p::common::ByteArray; -+ -+struct Key { -+ /** -+ * Supported types of all keys -+ */ -+ enum class Type { -+ UNSPECIFIED = 100, -+ RSA = 0, -+ Ed25519 = 1, -+ Secp256k1 = 2, -+ ECDSA = 3 -+ }; -+ -+ Key(Type, std::vector); -+ ~Key() noexcept; -+ Type type = Type::UNSPECIFIED; ///< key type -+ std::vector data{}; ///< key content -+}; -+ -+inline bool operator==(const Key& lhs, const Key& rhs) { -+ return lhs.type == rhs.type && lhs.data == rhs.data; -+} -+ -+inline bool operator!=(const Key& lhs, const Key& rhs) { -+ return !(lhs == rhs); -+} -+ -+struct PublicKey : public Key {}; -+ -+struct PrivateKey : public Key {}; -+ -+struct KeyPair { -+ PublicKey publicKey; -+ PrivateKey privateKey; -+}; -+ -+using Signature = std::vector; -+ -+inline bool operator==(const KeyPair& a, const KeyPair& b) { -+ return a.publicKey == b.publicKey && a.privateKey == b.privateKey; -+} -+ -+/** -+ * Result of ephemeral key generation -+ * -+struct EphemeralKeyPair { -+ Buffer ephemeral_public_key; -+ std::function(Buffer)> shared_secret_generator; -+}; -+*/ -+ -+/** -+ * Type of the stretched key -+ * -+struct StretchedKey { -+ Buffer iv; -+ Buffer cipher_key; -+ Buffer mac_key; -+}; -+*/ -+} // namespace libp2p::crypto -+ -+namespace std { -+template <> -+struct hash { -+ size_t operator()(const libp2p::crypto::Key& x) const; -+}; -+ -+template <> -+struct hash { -+ size_t operator()(const libp2p::crypto::PrivateKey& x) const; -+}; -+ -+template <> -+struct hash { -+ size_t operator()(const libp2p::crypto::PublicKey& x) const; -+}; -+ -+template <> -+struct hash { -+ size_t operator()(const libp2p::crypto::KeyPair& x) const; -+}; -+} // namespace std -+ -+#endif // LIBP2P_LIBP2P_CRYPTO_KEY_HPP -diff --git a/third_party/ipfs_client/include/libp2p/crypto/protobuf/protobuf_key.hpp b/third_party/ipfs_client/include/libp2p/crypto/protobuf/protobuf_key.hpp -new file mode 100644 -index 0000000000000..1a0d7ae7a2d4e ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/crypto/protobuf/protobuf_key.hpp -@@ -0,0 +1,29 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef KAGOME_PROTOBUF_KEY_HPP -+#define KAGOME_PROTOBUF_KEY_HPP -+ -+// #include -+ -+#include -+ -+#include -+ -+namespace libp2p::crypto { -+/** -+ * Strict type for key, which is encoded into Protobuf format -+ */ -+struct ProtobufKey { //: public boost::equality_comparable { -+ explicit ProtobufKey(std::vector key); -+ ~ProtobufKey() noexcept; -+ -+ std::vector key; -+ -+ bool operator==(const ProtobufKey& other) const { return key == other.key; } -+}; -+} // namespace libp2p::crypto -+ -+#endif // KAGOME_PROTOBUF_KEY_HPP -diff --git a/third_party/ipfs_client/include/libp2p/multi/multibase_codec.hpp b/third_party/ipfs_client/include/libp2p/multi/multibase_codec.hpp -new file mode 100644 -index 0000000000000..c7b9cbd1f7d40 ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/multi/multibase_codec.hpp -@@ -0,0 +1,65 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef LIBP2P_MULTIBASE_HPP -+#define LIBP2P_MULTIBASE_HPP -+ -+#include "vocab/expected.h" -+ -+#include -+#include -+#include -+ -+#include -+ -+namespace libp2p::multi { -+/** -+ * Allows to distinguish between different base-encoded binaries -+ * See more: https://github.com/multiformats/multibase -+ */ -+class MultibaseCodec { -+ public: -+ enum class Error { UNSUPPORTED_BASE = 1, INPUT_TOO_SHORT, BASE_CODEC_ERROR }; -+ -+ using ByteBuffer = common::ByteArray; -+ using FactoryResult = ipfs::expected; -+ -+ virtual ~MultibaseCodec() = default; -+ /** -+ * Encodings, supported by this Multibase -+ * @sa https://github.com/multiformats/multibase#multibase-table -+ */ -+ enum class Encoding : char { -+ BASE16_LOWER = 'f', -+ BASE16_UPPER = 'F', -+ BASE32_LOWER = 'b', -+ BASE32_UPPER = 'B', -+ BASE36 = 'k', -+ BASE58 = 'z', -+ BASE64 = 'm' -+ }; -+ -+ /** -+ * Encode the incoming bytes -+ * @param bytes to be encoded -+ * @param encoding - base of the desired encoding -+ * @return encoded string WITH an encoding prefix -+ */ -+ virtual std::string encode(const ByteBuffer& bytes, -+ Encoding encoding) const = 0; -+ -+ /** -+ * Decode the incoming string -+ * @param string to be decoded -+ * @return bytes, if decoding was successful, error otherwise -+ */ -+ virtual FactoryResult decode(std::string_view string) const = 0; -+}; -+ -+bool case_critical(MultibaseCodec::Encoding); -+ -+} // namespace libp2p::multi -+ -+#endif // LIBP2P_MULTIBASE_HPP -diff --git a/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base16.h b/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base16.h -new file mode 100644 -index 0000000000000..72a74237eb2ee ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base16.h -@@ -0,0 +1,24 @@ -+#ifndef IPFS_BASE32_H_ -+#define IPFS_BASE32_H_ -+ -+#include "base_error.hpp" -+ -+#include -+#include -+ -+#include -+ -+#include -+ -+namespace ipfs::base16 { -+std::string encodeLower(ByteView bytes); -+std::string encodeUpper(ByteView bytes); -+ -+using libp2p::common::ByteArray; -+using libp2p::multi::detail::BaseError; -+using Decoded = ipfs::expected; -+Decoded decode(std::string_view string); -+ -+} // namespace ipfs::base16 -+ -+#endif // IPFS_BASE32_H_ -diff --git a/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base32.hpp b/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base32.hpp -new file mode 100644 -index 0000000000000..c24dc59d54121 ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base32.hpp -@@ -0,0 +1,52 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef LIBP2P_BASE32_HPP -+#define LIBP2P_BASE32_HPP -+ -+#include "base_error.hpp" -+ -+#include -+#include -+ -+/** -+ * Encode/decode to/from base32 format -+ * Implementation is taken from -+ * https://github.com/mjg59/tpmtotp/blob/master/base32.c -+ */ -+namespace libp2p::multi::detail { -+ -+/** -+ * Encode bytes to base32 uppercase string -+ * @param bytes to be encoded -+ * @return encoded string -+ */ -+std::string encodeBase32Upper(ipfs::ByteView bytes); -+/** -+ * Encode bytes to base32 lowercase string -+ * @param bytes to be encoded -+ * @return encoded string -+ */ -+std::string encodeBase32Lower(ipfs::ByteView bytes); -+ -+/** -+ * Decode base32 uppercase to bytes -+ * @param string to be decoded -+ * @return decoded bytes in case of success -+ */ -+ipfs::expected decodeBase32Upper( -+ std::string_view string); -+ -+/** -+ * Decode base32 lowercase string to bytes -+ * @param string to be decoded -+ * @return decoded bytes in case of success -+ */ -+ipfs::expected decodeBase32Lower( -+ std::string_view string); -+ -+} // namespace libp2p::multi::detail -+ -+#endif // LIBP2P_BASE32_HPP -diff --git a/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base_error.hpp b/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base_error.hpp -new file mode 100644 -index 0000000000000..a0ab1b6c54be5 ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/multi/multibase_codec/codecs/base_error.hpp -@@ -0,0 +1,24 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef LIBP2P_BASE_ERROR_HPP -+#define LIBP2P_BASE_ERROR_HPP -+ -+namespace libp2p::multi::detail { -+ -+enum class BaseError { -+ INVALID_BASE58_INPUT = 1, -+ INVALID_BASE64_INPUT, -+ INVALID_BASE32_INPUT, -+ INVALID_BASE36_INPUT, -+ NON_UPPERCASE_INPUT, -+ NON_LOWERCASE_INPUT, -+ UNIMPLEMENTED_MULTIBASE, -+ INVALID_BASE16_INPUT -+}; -+ -+} -+ -+#endif // LIBP2P_BASE_ERROR_HPP -diff --git a/third_party/ipfs_client/include/libp2p/multi/multicodec_type.hpp b/third_party/ipfs_client/include/libp2p/multi/multicodec_type.hpp -new file mode 100644 -index 0000000000000..bda027bb29567 ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/multi/multicodec_type.hpp -@@ -0,0 +1,78 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef LIBP2P_MULTICODECTYPE_HPP -+#define LIBP2P_MULTICODECTYPE_HPP -+ -+#include -+ -+namespace libp2p::multi { -+ -+/** -+ * LibP2P uses "protocol tables" to agree upon the mapping from one multicodec -+ * code. These tables can be application specific, though, like with other -+ * multiformats, there is a globally agreed upon table with common protocols -+ * and formats. -+ */ -+class MulticodecType { -+ public: -+ enum class Code { -+ IDENTITY = 0x00, -+ SHA1 = 0x11, -+ SHA2_256 = 0x12, -+ SHA2_512 = 0x13, -+ SHA3_512 = 0x14, -+ SHA3_384 = 0x15, -+ SHA3_256 = 0x16, -+ SHA3_224 = 0x17, -+ RAW = 0x55, -+ DAG_PB = 0x70, -+ DAG_CBOR = 0x71, -+ LIBP2P_KEY = 0x72, -+ DAG_JSON = 0x0129, -+ FILECOIN_COMMITMENT_UNSEALED = 0xf101, -+ FILECOIN_COMMITMENT_SEALED = 0xf102, -+ }; -+ -+ constexpr static std::string_view getName(Code code) { -+ switch (code) { -+ case Code::IDENTITY: -+ return "identity"; -+ case Code::SHA1: -+ return "sha1"; -+ case Code::SHA2_256: -+ return "sha2-256"; -+ case Code::SHA2_512: -+ return "sha2-512"; -+ case Code::SHA3_224: -+ return "sha3-224"; -+ case Code::SHA3_256: -+ return "sha3-256"; -+ case Code::SHA3_384: -+ return "sha3-384"; -+ case Code::SHA3_512: -+ return "sha3-512"; -+ case Code::RAW: -+ return "raw"; -+ case Code::DAG_PB: -+ return "dag-pb"; -+ case Code::DAG_CBOR: -+ return "dag-cbor"; -+ case Code::DAG_JSON: -+ return "dag-json"; -+ case Code::LIBP2P_KEY: -+ return "libp2p-key"; -+ case Code::FILECOIN_COMMITMENT_UNSEALED: -+ return "fil-commitment-unsealed"; -+ case Code::FILECOIN_COMMITMENT_SEALED: -+ return "fil-commitment-sealed"; -+ } -+ return "unknown"; -+ } -+}; -+ -+} // namespace libp2p::multi -+ -+#endif // LIBP2P_MULTICODECTYPE_HPP -diff --git a/third_party/ipfs_client/include/libp2p/multi/uvarint.hpp b/third_party/ipfs_client/include/libp2p/multi/uvarint.hpp -new file mode 100644 -index 0000000000000..4dd452abffba4 ---- /dev/null -+++ b/third_party/ipfs_client/include/libp2p/multi/uvarint.hpp -@@ -0,0 +1,98 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef LIBP2P_VARINT_HPP -+#define LIBP2P_VARINT_HPP -+ -+#include "vocab/byte_view.h" -+ -+#include -+ -+#include -+#include -+#include -+ -+namespace libp2p::multi { -+ -+/** -+ * @class Encodes and decodes unsigned integers into and from -+ * variable-length byte arrays using LEB128 algorithm. -+ */ -+class UVarint { -+ public: -+ /** -+ * Constructs a varint from an unsigned integer 'number' -+ * @param number -+ */ -+ explicit UVarint(uint64_t number); -+ -+ /** -+ * Constructs a varint from an array of raw bytes, which are -+ * meant to be an already encoded unsigned varint -+ * @param varint_bytes an array of bytes representing an unsigned varint -+ */ -+ explicit UVarint(ipfs::ByteView varint_bytes); -+ -+ /** -+ * Constructs a varint from an array of raw bytes, which beginning may or -+ * may not be an encoded varint -+ * @param varint_bytes an array of bytes, possibly representing an unsigned -+ * varint -+ */ -+ static std::optional create(ipfs::ByteView varint_bytes); -+ -+ /** -+ * Converts a varint back to a usual unsigned integer. -+ * @return an integer previously encoded to the varint -+ */ -+ uint64_t toUInt64() const; -+ -+ /** -+ * @return an array view to raw bytes of the stored varint -+ */ -+ ipfs::ByteView toBytes() const; -+ -+ std::vector const& toVector() const; -+ -+ std::string toHex() const; -+ -+ /** -+ * Assigns the varint to an unsigned integer, encoding the latter -+ * @param n the integer to encode and store -+ * @return this varint -+ */ -+ UVarint& operator=(uint64_t n); -+ -+ bool operator==(const UVarint& r) const; -+ bool operator!=(const UVarint& r) const; -+ bool operator<(const UVarint& r) const; -+ -+ /** -+ * @return the number of bytes currently stored in a varint -+ */ -+ size_t size() const; -+ -+ /** -+ * @param varint_bytes an array with a raw byte representation of a varint -+ * @return the size of the varint stored in the array, if its content is a -+ * valid varint. Otherwise, the result is undefined -+ */ -+ static size_t calculateSize(ipfs::ByteView varint_bytes); -+ -+ UVarint() = delete; -+ UVarint(UVarint const&); -+ UVarint& operator=(UVarint const&); -+ ~UVarint() noexcept; -+ -+ private: -+ /// private ctor for unsafe creation -+ UVarint(ipfs::ByteView varint_bytes, size_t varint_size); -+ -+ std::vector bytes_{}; -+}; -+ -+} // namespace libp2p::multi -+ -+#endif // LIBP2P_VARINT_HPP -diff --git a/third_party/ipfs_client/include/multibase/algorithm.h b/third_party/ipfs_client/include/multibase/algorithm.h -new file mode 100644 -index 0000000000000..2cea1cabd296e ---- /dev/null -+++ b/third_party/ipfs_client/include/multibase/algorithm.h -@@ -0,0 +1,27 @@ -+#pragma once -+ -+#include -+ -+namespace multibase { -+ -+class algorithm { -+ public: -+ /** Tag identifying algorithms which operate on blocks */ -+ class block_tag {}; -+ -+ /** Tag identifying algorithms which operate on continuous data */ -+ class stream_tag {}; -+ -+ virtual ~algorithm() = default; -+ -+ /** Returns the input size required to decode a single block */ -+ virtual std::size_t block_size() { return 0; } -+ -+ /** Returns the size of a processed block */ -+ virtual std::size_t output_size() { return 0; } -+ -+ /** Processes an input block returning any intermediate result */ -+ virtual std::string process(std::string_view input) = 0; -+}; -+ -+} // namespace multibase -diff --git a/third_party/ipfs_client/include/multibase/basic_algorithm.h b/third_party/ipfs_client/include/multibase/basic_algorithm.h -new file mode 100644 -index 0000000000000..5da225c885fd4 ---- /dev/null -+++ b/third_party/ipfs_client/include/multibase/basic_algorithm.h -@@ -0,0 +1,322 @@ -+/* From: https://github.com/lockblox/multibase -+ * Copyright (c) 2018 markovchainy -+ * MIT License -+ */ -+#pragma once -+ -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+namespace multibase { -+ -+template -+struct traits { -+ static const std::array charset; -+ static const char name[]; -+ static const char padding = 0; -+ using execution_style = algorithm::block_tag; -+}; -+ -+/** Template implementation of base encoding which computes a lookup table at -+ * compile time and avoids the virtual algorithm lookup penalty */ -+template > -+class basic_algorithm { -+ public: -+ class encoder : public algorithm { -+ public: -+ size_t output_size() override; -+ size_t block_size() override; -+ std::string process(std::string_view input) override; -+ -+ private: -+ constexpr size_t input_size() { return ratio.den; } -+ }; -+ -+ class decoder : public algorithm { -+ public: -+ size_t output_size() override; -+ size_t block_size() override; -+ std::string process(std::string_view input) override; -+ -+ private: -+ constexpr size_t input_size() { return ratio.num; } -+ }; -+ -+ private: -+ constexpr static auto first = Traits::charset.cbegin(); -+ constexpr static auto last = Traits::charset.cend(); -+ using CharsetT = decltype(Traits::charset); -+ using value_type = typename CharsetT::value_type; -+ using iterator = typename CharsetT::const_iterator; -+ -+ /** Find a value at compile time */ -+ constexpr static iterator find(iterator b, iterator e, -+ value_type const& v) noexcept { -+ return (b != e && *b != v) ? find(++b, e, v) : b; -+ } -+ -+ /** Determine the character encoding for a given value -+ @return character encoding, or xFF if none such encoding exists */ -+ constexpr static unsigned char getval(unsigned char p) noexcept { -+ return find(first, last, p) == last -+ ? static_cast(255) -+ : static_cast( -+ std::distance(first, find(first, last, p))); -+ } -+ -+ /** Compute base-2 logarithm */ -+ constexpr static std::intmax_t log2(std::intmax_t n) noexcept { -+ return (n == 1) ? 0 : ((n < 2) ? 1 : 1 + log2(n / 2)); -+ } -+ -+ /** encoding as determined by size of character set */ -+ constexpr static auto radix = sizeof(Traits::charset) / sizeof(value_type); -+ /** Ratio of encoded characters per byte */ -+ constexpr static auto ratio = std::ratio{}; -+ /** Map from value to corresponding character in base encoding */ -+ static const std::array valset; -+ -+ constexpr static auto base = T; -+}; -+ -+template -+const std::array basic_algorithm::valset = { -+ getval(0), getval(1), getval(2), getval(3), getval(4), -+ getval(5), getval(6), getval(7), getval(8), getval(9), -+ getval(10), getval(11), getval(12), getval(13), getval(14), -+ getval(15), getval(16), getval(17), getval(18), getval(19), -+ getval(20), getval(21), getval(22), getval(23), getval(24), -+ getval(25), getval(26), getval(27), getval(28), getval(29), -+ getval(30), getval(31), getval(32), getval(33), getval(34), -+ getval(35), getval(36), getval(37), getval(38), getval(39), -+ getval(40), getval(41), getval(42), getval(43), getval(44), -+ getval(45), getval(46), getval(47), getval(48), getval(49), -+ getval(50), getval(51), getval(52), getval(53), getval(54), -+ getval(55), getval(56), getval(57), getval(58), getval(59), -+ getval(60), getval(61), getval(62), getval(63), getval(64), -+ getval(65), getval(66), getval(67), getval(68), getval(69), -+ getval(70), getval(71), getval(72), getval(73), getval(74), -+ getval(75), getval(76), getval(77), getval(78), getval(79), -+ getval(80), getval(81), getval(82), getval(83), getval(84), -+ getval(85), getval(86), getval(87), getval(88), getval(89), -+ getval(90), getval(91), getval(92), getval(93), getval(94), -+ getval(95), getval(96), getval(97), getval(98), getval(99), -+ getval(100), getval(101), getval(102), getval(103), getval(104), -+ getval(105), getval(106), getval(107), getval(108), getval(109), -+ getval(110), getval(111), getval(112), getval(113), getval(114), -+ getval(115), getval(116), getval(117), getval(118), getval(119), -+ getval(120), getval(121), getval(122), getval(123), getval(124), -+ getval(125), getval(126), getval(127), getval(128), getval(129), -+ getval(130), getval(131), getval(132), getval(133), getval(134), -+ getval(135), getval(136), getval(137), getval(138), getval(139), -+ getval(140), getval(141), getval(142), getval(143), getval(144), -+ getval(145), getval(146), getval(147), getval(148), getval(149), -+ getval(150), getval(151), getval(152), getval(153), getval(154), -+ getval(155), getval(156), getval(157), getval(158), getval(159), -+ getval(160), getval(161), getval(162), getval(163), getval(164), -+ getval(165), getval(166), getval(167), getval(168), getval(169), -+ getval(170), getval(171), getval(172), getval(173), getval(174), -+ getval(175), getval(176), getval(177), getval(178), getval(179), -+ getval(180), getval(181), getval(182), getval(183), getval(184), -+ getval(185), getval(186), getval(187), getval(188), getval(189), -+ getval(190), getval(191), getval(192), getval(193), getval(194), -+ getval(195), getval(196), getval(197), getval(198), getval(199), -+ getval(200), getval(201), getval(202), getval(203), getval(204), -+ getval(205), getval(206), getval(207), getval(208), getval(209), -+ getval(210), getval(211), getval(212), getval(213), getval(214), -+ getval(215), getval(216), getval(217), getval(218), getval(219), -+ getval(220), getval(221), getval(222), getval(223), getval(224), -+ getval(225), getval(226), getval(227), getval(228), getval(229), -+ getval(230), getval(231), getval(232), getval(233), getval(234), -+ getval(235), getval(236), getval(237), getval(238), getval(239), -+ getval(240), getval(241), getval(242), getval(243), getval(244), -+ getval(245), getval(246), getval(247), getval(248), getval(249), -+ getval(250), getval(251), getval(252), getval(253), getval(254), -+ getval(255)}; -+ -+template -+std::string basic_algorithm::encoder::process( -+ std::string_view input) { -+ std::string output; -+ std::size_t isize = input.size(); -+ auto partial_blocks = static_cast(input.size()) / input_size(); -+ auto num_blocks = static_cast(partial_blocks); -+ auto osize = static_cast(std::ceil(partial_blocks * output_size())); -+ if constexpr (std::is_same_v) { -+ num_blocks = static_cast(std::ceil(partial_blocks)); -+ isize = input_size() * num_blocks; -+ } -+ output.resize(std::max(osize, (output_size() * num_blocks))); -+ auto input_it = std::begin(input); -+ int length = 0; -+ for (std::size_t i = 0; i < isize; ++i, ++input_it) { -+ int carry = i >= input.size() ? 0 : static_cast(*input_it); -+ int j = 0; -+ for (auto oi = output.rbegin(); -+ (oi != output.rend()) && (carry != 0 || j < length); ++oi, ++j) { -+ carry += 256 * (*oi); -+ auto byte = (unsigned char*)(&(*oi)); -+ *byte = carry % radix; -+ carry /= radix; -+ } -+ length = j; -+ } -+ std::transform(output.rbegin(), output.rend(), output.rbegin(), -+ [](auto c) { return Traits::charset[c]; }); -+ if constexpr (Traits::padding == 0) { -+ output.resize(osize); -+ } else { -+ auto pad_size = output.size() - osize; -+ output.replace(osize, pad_size, pad_size, Traits::padding); -+ } -+ if constexpr (std::is_same_v) { -+ output.erase(0, output.size() % output_size() ? output.size() - length : 0); -+ } -+ return output; -+} -+ -+template -+std::size_t basic_algorithm::encoder::block_size() { -+ return std::is_same_v -+ ? input_size() -+ : 0; -+} -+ -+template -+std::size_t basic_algorithm::encoder::output_size() { -+ return ratio.num; -+} -+ -+template -+std::size_t basic_algorithm::decoder::block_size() { -+ return std::is_same_v -+ ? input_size() -+ : 0; -+} -+ -+template -+std::size_t basic_algorithm::decoder::output_size() { -+ return ratio.den; -+} -+ -+template -+std::string basic_algorithm::decoder::process( -+ std::string_view input) { -+ std::string output; -+ auto end = std::find(input.begin(), input.end(), Traits::padding); -+ size_t input_size = std::distance(input.begin(), end); -+ auto partial_blocks = static_cast(input_size) / this->input_size(); -+ auto output_size = static_cast(this->output_size() * partial_blocks); -+ if constexpr (std::is_same_v) { -+ std::size_t num_blocks = 0; -+ auto input_size_float = static_cast(input.size()); -+ num_blocks = -+ static_cast(std::ceil(input_size_float / this->input_size())); -+ output.resize(this->output_size() * num_blocks); -+ input_size = this->input_size() * num_blocks; -+ } else { -+ output.resize(output_size); -+ } -+ auto input_it = input.begin(); -+ for (size_t i = 0; i < input_size; ++i, ++input_it) { -+ int carry = i > input.size() || *input_it == Traits::padding -+ ? 0 -+ : valset[(unsigned char)(*input_it)]; -+ if (carry == 255) { -+ // throw std::invalid_argument(std::string{"Invalid input character -+ // "} + *input_it); -+ return {}; -+ } -+ auto j = output.size(); -+ while (carry != 0 || j > 0) { -+ auto index = j - 1; -+ carry += radix * static_cast(output[index]); -+ output[index] = static_cast(carry % 256); -+ carry /= 256; -+ if (carry > 0 && index == 0) { -+ output.insert(0, 1, 0); -+ } else { -+ j = index; -+ } -+ } -+ } -+ if constexpr (std::is_same_v) { -+ output.erase(output_size, output.size()); -+ } -+ return output; -+} -+ -+template <> -+struct traits { -+ constexpr static const std::array charset = { -+ '0', '1', '2', '3', '4', '5', '6', '7', -+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; -+ constexpr static const char name[] = "base_16"; -+ using execution_style = algorithm::block_tag; -+ constexpr static const char padding = 0; -+}; -+using base_16 = basic_algorithm; -+ -+template <> -+struct traits { -+ constexpr static const std::array charset = { -+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', -+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', -+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', -+ 'u', 'v', 'w', 'x', 'y', 'z'}; -+ constexpr static const char name[] = "base_36"; -+ using execution_style = algorithm::stream_tag; -+ constexpr static const char padding = 0; -+}; -+using base_36_btc = basic_algorithm; -+ -+ -+template <> -+struct traits { -+ constexpr static const std::array charset = { -+ '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', -+ 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', -+ 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', -+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; -+ constexpr static const char name[] = "base_58_btc"; -+ using execution_style = algorithm::stream_tag; -+ constexpr static const char padding = 0; -+}; -+using base_58_btc = basic_algorithm; -+ -+template <> -+struct traits { -+ constexpr static const std::array charset = { -+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', -+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', -+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', -+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', -+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; -+ constexpr static const char name[] = "base_64_pad"; -+ using execution_style = algorithm::block_tag; -+ constexpr static const char padding = '='; -+}; -+using base_64_pad = basic_algorithm; -+ -+template <> -+struct traits { -+ constexpr static const std::array charset = { -+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', -+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', -+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', -+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', -+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; -+ constexpr static const char name[] = "base_64"; -+ using base_64 = basic_algorithm; -+ using execution_style = algorithm::block_tag; -+ constexpr static const char padding = 0; -+}; -+using base_64 = basic_algorithm; -+ -+} // namespace multibase -diff --git a/third_party/ipfs_client/include/multibase/encoding.h b/third_party/ipfs_client/include/multibase/encoding.h -new file mode 100644 -index 0000000000000..7675ca6e8445a ---- /dev/null -+++ b/third_party/ipfs_client/include/multibase/encoding.h -@@ -0,0 +1,21 @@ -+#pragma once -+#include -+#include -+ -+namespace multibase { -+ -+enum class encoding : unsigned char { -+ base_unknown = '?', -+ base_256 = 0, -+ base_16 = 'f', -+ base_16_upper = 'F', -+ base_32 = 'b', -+ base_32_upper = 'B', -+ base_36 = 'k', -+ base_58_btc = 'Z', -+ base_64 = 'm', -+ base_64_pad = 'M' -+ -+}; -+ -+} // namespace multibase -diff --git a/third_party/ipfs_client/include/smhasher/MurmurHash3.h b/third_party/ipfs_client/include/smhasher/MurmurHash3.h -new file mode 100644 -index 0000000000000..e1c6d34976c6a ---- /dev/null -+++ b/third_party/ipfs_client/include/smhasher/MurmurHash3.h -@@ -0,0 +1,37 @@ -+//----------------------------------------------------------------------------- -+// MurmurHash3 was written by Austin Appleby, and is placed in the public -+// domain. The author hereby disclaims copyright to this source code. -+ -+#ifndef _MURMURHASH3_H_ -+#define _MURMURHASH3_H_ -+ -+//----------------------------------------------------------------------------- -+// Platform-specific functions and macros -+ -+// Microsoft Visual Studio -+ -+#if defined(_MSC_VER) && (_MSC_VER < 1600) -+ -+typedef unsigned char uint8_t; -+typedef unsigned int uint32_t; -+typedef unsigned __int64 uint64_t; -+ -+// Other compilers -+ -+#else // defined(_MSC_VER) -+ -+#include -+ -+#endif // !defined(_MSC_VER) -+ -+//----------------------------------------------------------------------------- -+ -+void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out ); -+ -+void MurmurHash3_x86_128 ( const void * key, int len, uint32_t seed, void * out ); -+ -+void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out ); -+ -+//----------------------------------------------------------------------------- -+ -+#endif // _MURMURHASH3_H_ -diff --git a/third_party/ipfs_client/include/vocab/byte.h b/third_party/ipfs_client/include/vocab/byte.h -new file mode 100644 -index 0000000000000..17477c11c2a6f ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/byte.h -@@ -0,0 +1,43 @@ -+#ifndef IPFS_BYTE_H_ -+#define IPFS_BYTE_H_ -+ -+#include -+#include -+ -+#include -+#include -+#include -+ -+#ifdef __cpp_lib_byte -+ -+namespace ipfs { -+using Byte = std::byte; -+} // namespace ipfs -+ -+#else -+namespace ipfs { -+enum class Byte : std::uint_least8_t {}; -+} // namespace ipfs -+#endif -+ -+namespace { -+[[maybe_unused]] std::ostream& operator<<(std::ostream& str, ipfs::Byte b) { -+ return str << std::hex << std::setw(2) << std::setfill('0') -+ << static_cast(b); -+} -+} // namespace -+ -+namespace { -+// libc++ provides this, but for some reason libstdc++ does not -+[[maybe_unused]] std::uint8_t to_integer(ipfs::Byte b) { -+ return static_cast(b); -+} -+} // namespace -+ -+namespace ipfs { -+inline bool operator==(Byte a, Byte b) { -+ return to_integer(a) == to_integer(b); -+} -+} // namespace ipfs -+ -+#endif // IPFS_BYTE_H_ -diff --git a/third_party/ipfs_client/include/vocab/byte_view.h b/third_party/ipfs_client/include/vocab/byte_view.h -new file mode 100644 -index 0000000000000..69858d1972a30 ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/byte_view.h -@@ -0,0 +1,24 @@ -+#ifndef CHROMIUM_IPFS_BYTE_VIEW_H -+#define CHROMIUM_IPFS_BYTE_VIEW_H -+ -+#include "byte.h" -+#include "span.h" -+ -+#include -+ -+namespace ipfs { -+using ByteView = span; -+ -+// ByteView is a view over arbitrary opaque byte -+// Cast it to a view over 8-bit unsigned integers for inspection -+inline span as_octets(ByteView bytes) { -+ return {reinterpret_cast(bytes.data()), bytes.size()}; -+} -+template -+inline ByteView as_bytes(ContiguousBytes const& b) { -+ auto p = reinterpret_cast(b.data()); -+ return ByteView{p, b.size()}; -+} -+} // namespace ipfs -+ -+#endif // CHROMIUM_IPFS_BYTE_VIEW_H -diff --git a/third_party/ipfs_client/include/vocab/endian.h b/third_party/ipfs_client/include/vocab/endian.h -new file mode 100644 -index 0000000000000..2423006c7c02b ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/endian.h -@@ -0,0 +1,21 @@ -+#ifndef IPFS_ENDIAN_H_ -+#define IPFS_ENDIAN_H_ -+ -+#if __has_include() -+#include -+#endif -+#if __has_include() -+#include -+#endif -+ -+#ifdef htobe64 -+// Good -+#elif __has_include() -+#include -+#define htobe64 absl::ghtonll -+#elif __has_include() -+#include -+#define htobe64 native_to_big -+#endif -+ -+#endif // IPFS_ENDIAN_H_ -diff --git a/third_party/ipfs_client/include/vocab/expected.h b/third_party/ipfs_client/include/vocab/expected.h -new file mode 100644 -index 0000000000000..2006f2bf01397 ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/expected.h -@@ -0,0 +1,44 @@ -+#ifndef IPFS_EXPECTED_H_ -+#define IPFS_EXPECTED_H_ -+ -+// std::expected isn't available until C++23 and we need to support C++17 -+// boost::outcome isn't available inside the Chromium tree -+// absl::StatusOr doesn't allow templating or extending the error type, and -+// translating the specific error codes into generic ones isn't great. -+ -+#if __has_include("base/types/expected.h") -+#include "base/types/expected.h" -+namespace ipfs { -+template -+using expected = base::expected; -+template -+using unexpected = base::unexpected; -+} // namespace ipfs -+#elif __has_cpp_attribute(__cpp_lib_expected) -+ -+#include -+namespace ipfs { -+template -+using expected = std::expected; -+template -+using unexpected = std::unexpected; -+} // namespace ipfs -+ -+#elif __has_include() -+ -+// If the API differences between std::expected and boost::outcome::checked -+// become a problem, consider wrapping as proposed in the FAQ: -+// https://www.boost.org/doc/libs/master/libs/outcome/doc/html/faq.html#how-far-away-from-the-proposed-std-expected-t-e-is-outcome-s-checked-t-e -+#include -+namespace ipfs { -+template -+using expected = boost::outcome_v2::checked; -+template -+using unexpected = Error; -+} // namespace ipfs -+ -+#else -+#error Get an expected implementation -+#endif -+ -+#endif // IPFS_EXPECTED_H_ -diff --git a/third_party/ipfs_client/include/vocab/flat_mapset.h b/third_party/ipfs_client/include/vocab/flat_mapset.h -new file mode 100644 -index 0000000000000..1630e3f9ca358 ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/flat_mapset.h -@@ -0,0 +1,39 @@ -+#ifndef CHROMIUM_IPFS_VOCAB_MAP_SET_H_ -+#define CHROMIUM_IPFS_VOCAB_MAP_SET_H_ -+ -+#if __has_include("base/containers/flat_map.h") // Chromium -+ -+#include "base/containers/flat_map.h" -+#include "base/containers/flat_set.h" -+#include "base/debug/debugging_buildflags.h" -+namespace ipfs { -+using base::flat_map; -+using base::flat_set; -+} // namespace ipfs -+ -+#elif __has_cpp_attribute(__cpp_lib_flat_map) && \ -+ __has_cpp_attribute(__cpp_lib_flat_set) -+ -+#include -+#include -+namespace ipfs { -+using std::flat_map; -+using std::flat_set; -+} // namespace ipfs -+ -+#elif __has_include() //Boost -+#include -+#include -+namespace ipfs { -+using boost::container::flat_map; -+using boost::container::flat_set; -+} // namespace ipfs -+ -+#else -+ -+#error \ -+ "Provide an implementation for flat_map and flat_set, or install boost or have a Chromium tree or use a newer C++ version." -+ -+#endif -+ -+#endif // CHROMIUM_IPFS_VOCAB_MAP_SET_H_ -diff --git a/third_party/ipfs_client/include/vocab/html_escape.h b/third_party/ipfs_client/include/vocab/html_escape.h -new file mode 100644 -index 0000000000000..60339ad7d45bd ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/html_escape.h -@@ -0,0 +1,23 @@ -+#ifndef IPFS_HTML_ESCAPE_H_ -+#define IPFS_HTML_ESCAPE_H_ -+ -+#include -+ -+constexpr inline std::string_view html_escape(char& c) { -+ switch (c) { -+ case '"': -+ return """; -+ case '\'': -+ return "'"; -+ case '<': -+ return "<"; -+ case '>': -+ return ">"; -+ case '&': -+ return "&"; -+ default: -+ return {&c, 1UL}; -+ } -+} -+ -+#endif // IPFS_HTML_ESCAPE_H_ -diff --git a/third_party/ipfs_client/include/vocab/i128.h b/third_party/ipfs_client/include/vocab/i128.h -new file mode 100644 -index 0000000000000..4aa36cc09877f ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/i128.h -@@ -0,0 +1,16 @@ -+#ifndef IPFS_I128_H_ -+#define IPFS_I128_H_ -+ -+#if __has_include() -+#include -+namespace ipfs { -+using Int_128 = absl::int128; -+} -+#else -+namespace ipfs { -+// TODO Check if available, if not use boost multiprecision -+using Int_128 = __int128; -+} // namespace ipfs -+#endif -+ -+#endif // IPFS_I128_H_ -diff --git a/third_party/ipfs_client/include/vocab/raw_ptr.h b/third_party/ipfs_client/include/vocab/raw_ptr.h -new file mode 100644 -index 0000000000000..25405d3ea30ba ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/raw_ptr.h -@@ -0,0 +1,64 @@ -+#ifndef IPFS_OBSERVER_PTR_H_ -+#define IPFS_OBSERVER_PTR_H_ -+ -+#if __has_include("base/memory/raw_ptr.h") -+#include "base/memory/raw_ptr.h" -+ -+namespace ipfs { -+template -+using raw_ptr = base::raw_ptr; -+} -+ -+#elif defined(__has_cpp_attribute) && \ -+ __has_cpp_attribute(__cpp_lib_experimental_observer_ptr) -+#include -+ -+namespace ipfs { -+template -+using raw_ptr = std::experimental::observer_ptr; -+} -+ -+#else -+ -+#include -+ -+namespace ipfs { -+ -+/*! -+ * \brief Just an observing (non-owning) pointer. -+ */ -+template -+class raw_ptr { -+ T* ptr_; -+ -+ public: -+ // Chromium's raw_ptr has a default ctor whose semantics depend on build -+ // config. For components/ipfs purposes, there is no reason to ever default -+ // construct. Set it to nullptr. We have time needed to read_start a word. -+ raw_ptr() = delete; -+ -+ raw_ptr(T* p) : ptr_{p} {} -+ raw_ptr(raw_ptr&&) = default; -+ raw_ptr(raw_ptr const&) = default; -+ -+ raw_ptr& operator=(raw_ptr const&) = default; -+ -+ T* get() { return ptr_; } -+ T const* get() const { return ptr_; } -+ explicit operator bool() const { return !!ptr_; } -+ T* operator->() { return ptr_; } -+ T const* operator->() const { return ptr_; } -+ raw_ptr& operator=(T* p) { -+ ptr_ = p; -+ return *this; -+ } -+ T& operator*() { -+ assert(ptr_); -+ return *ptr_; -+ } -+}; -+} // namespace ipfs -+ -+#endif -+ -+#endif // IPFS_OBSERVER_PTR_H_ -diff --git a/third_party/ipfs_client/include/vocab/slash_delimited.h b/third_party/ipfs_client/include/vocab/slash_delimited.h -new file mode 100644 -index 0000000000000..53fd142465028 ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/slash_delimited.h -@@ -0,0 +1,35 @@ -+#ifndef IPFS_SLASH_DELIMITED_H_ -+#define IPFS_SLASH_DELIMITED_H_ -+ -+#include -+#include -+#include -+ -+namespace google::protobuf::internal { -+class LogMessage; -+} -+ -+namespace ipfs { -+struct SlashDelimited { -+ std::string_view remainder_; -+ -+ public: -+ SlashDelimited() : remainder_{""} {} -+ explicit SlashDelimited(std::string_view unowned); -+ explicit operator bool() const; -+ std::string_view pop(); -+ std::string_view pop_all(); -+ std::string_view pop_n(std::size_t); -+ std::string_view peek_back() const; -+ std::string pop_back(); -+ std::string to_string() const { return std::string{remainder_}; } -+ std::string_view to_view() const { return remainder_; } -+}; -+} // namespace ipfs -+ -+std::ostream& operator<<(std::ostream&, ipfs::SlashDelimited const&); -+google::protobuf::internal::LogMessage& operator<<( -+ google::protobuf::internal::LogMessage&, -+ ipfs::SlashDelimited const&); -+ -+#endif // IPFS_SLASH_DELIMITED_H_ -diff --git a/third_party/ipfs_client/include/vocab/span.h b/third_party/ipfs_client/include/vocab/span.h -new file mode 100644 -index 0000000000000..f9c05d2a7dd61 ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/span.h -@@ -0,0 +1,65 @@ -+#ifndef IPFS_SPAN_H_ -+#define IPFS_SPAN_H_ -+ -+#if __cpp_lib_span -+#include -+ -+namespace ipfs { -+template -+using span = std::span; -+} // namespace ipfs -+ -+#elif __has_include("base/containers/span.h") -+ -+#include "base/containers/span.h" -+namespace ipfs { -+template -+using span = base::span; -+} // namespace ipfs -+ -+#elif __has_include() -+ -+#include -+namespace ipfs { -+template -+using span = absl::Span; -+} // namespace ipfs -+ -+#elif __has_include() -+ -+#include -+namespace ipfs { -+template -+using span = boost::span; -+} // namespace ipfs -+ -+#elif __has_include() -+ -+// Prior to Boost 1.78, span did not exist in core yet -+#include -+#include -+namespace ipfs { -+template -+class span : public boost::beast::span { -+ public: -+ span(Value* d, std::size_t n) : boost::beast::span{d, n} {} -+ -+ template -+ span(std::vector const& v) -+ : boost::beast::span{v.data(), v.size()} {} -+ -+ span subspan(std::size_t off) const { -+ return span{this->data() + off, this->size() - off}; -+ } -+ Value& operator[](std::size_t i) { return this->data()[i]; } -+}; -+} // namespace ipfs -+ -+#else -+ -+#error \ -+ "No good implementation of span available. Implement one, move to a newer C++, or provide Boost or Abseil." -+ -+#endif -+ -+#endif // IPFS_SPAN_H_ -diff --git a/third_party/ipfs_client/include/vocab/stringify.h b/third_party/ipfs_client/include/vocab/stringify.h -new file mode 100644 -index 0000000000000..8572ebfef7165 ---- /dev/null -+++ b/third_party/ipfs_client/include/vocab/stringify.h -@@ -0,0 +1,17 @@ -+#ifndef IPFS_STRINGIFY_H_ -+#define IPFS_STRINGIFY_H_ -+ -+#include -+ -+namespace ipfs { -+namespace { -+template -+std::string Stringify(T const& t) { -+ std::ostringstream oss; -+ oss << t; -+ return oss.str(); -+} -+} // namespace -+} // namespace ipfs -+ -+#endif // IPFS_STRINGIFY_H_ -diff --git a/third_party/ipfs_client/ipns_record.proto b/third_party/ipfs_client/ipns_record.proto -new file mode 100644 -index 0000000000000..6018931b7466f ---- /dev/null -+++ b/third_party/ipfs_client/ipns_record.proto -@@ -0,0 +1,38 @@ -+syntax = "proto2"; -+option optimize_for = LITE_RUNTIME; -+package ipfs.ipns; -+ -+message IpnsEntry { -+ enum ValidityType { -+ // setting an EOL says "this record is valid until..." -+ EOL = 0; -+ } -+ -+ // deserialized copy of data[value] -+ optional bytes value = 1; -+ -+ // legacy field, verify 'signatureV2' instead -+ optional bytes signatureV1 = 2; -+ -+ // deserialized copies of data[validityType] and data[validity] -+ optional ValidityType validityType = 3; -+ optional bytes validity = 4; -+ -+ // deserialized copy of data[sequence] -+ optional uint64 sequence = 5; -+ -+ // record TTL in nanoseconds, a deserialized copy of data[ttl] -+ optional uint64 ttl = 6; -+ -+ // in order for nodes to properly validate a record upon receipt, they need the public -+ // key associated with it. For old RSA keys, its easiest if we just send this as part of -+ // the record itself. For newer Ed25519 keys, the public key can be embedded in the -+ // IPNS Name itself, making this field unnecessary. -+ optional bytes pubKey = 7; -+ -+ // the signature of the IPNS record -+ optional bytes signatureV2 = 8; -+ -+ // extensible record data in DAG-CBOR format -+ optional bytes data = 9; -+} -diff --git a/third_party/ipfs_client/keys.proto b/third_party/ipfs_client/keys.proto -new file mode 100644 -index 0000000000000..a6f4f75ddba93 ---- /dev/null -+++ b/third_party/ipfs_client/keys.proto -@@ -0,0 +1,22 @@ -+syntax = "proto2"; -+option optimize_for = LITE_RUNTIME; -+package ipfs.ipns; -+ -+enum KeyType { -+ RSA = 0; -+ Ed25519 = 1; -+ Secp256k1 = 2; -+ ECDSA = 3; -+} -+ -+// PublicKey -+message PublicKey { -+ required KeyType Type = 1; -+ required bytes Data = 2; -+} -+ -+// PrivateKey -+message PrivateKey { -+ required KeyType Type = 1; -+ required bytes Data = 2; -+} -diff --git a/third_party/ipfs_client/pb_dag.proto b/third_party/ipfs_client/pb_dag.proto -new file mode 100644 -index 0000000000000..5cd027631c6de ---- /dev/null -+++ b/third_party/ipfs_client/pb_dag.proto -@@ -0,0 +1,23 @@ -+syntax = "proto2"; -+option optimize_for = LITE_RUNTIME; -+package ipfs.pb_dag; -+ -+message PBLink { -+ // binary CID (with no multibase prefix) of the target object -+ optional bytes Hash = 1; -+ -+ // UTF-8 string name -+ optional string Name = 2; -+ -+ // cumulative size of target object -+ optional uint64 Tsize = 3; -+} -+ -+message PBNode { -+ // refs to other objects -+ repeated PBLink Links = 2; -+ -+ // opaque user data -+ optional bytes Data = 1; -+} -+ -diff --git a/third_party/ipfs_client/src/ipfs_client/bases/b16_upper.h b/third_party/ipfs_client/src/ipfs_client/bases/b16_upper.h -new file mode 100644 -index 0000000000000..9d0056ecb98b9 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/bases/b16_upper.h -@@ -0,0 +1,23 @@ -+#ifndef IPFS_B16_UPPER_H_ -+#define IPFS_B16_UPPER_H_ -+ -+#include -+ -+namespace multibase { -+template <> -+struct traits<::multibase::encoding::base_16_upper> { -+ constexpr static const std::array charset = { -+ '0', '1', '2', '3', '4', '5', '6', '7', -+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; -+ constexpr static const char name[] = "BASE_16"; -+ using execution_style = multibase::algorithm::block_tag; -+ constexpr static const char padding = 0; -+}; -+} // namespace multibase -+ -+namespace ipfs::mb { -+using base_16_upper = -+ multibase::basic_algorithm; -+} // namespace ipfs::mb -+ -+#endif // IPFS_B16_UPPER_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/bases/b32.h b/third_party/ipfs_client/src/ipfs_client/bases/b32.h -new file mode 100644 -index 0000000000000..9dac14db53ac3 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/bases/b32.h -@@ -0,0 +1,35 @@ -+#ifndef IPFS_B32_UPPER_H_ -+#define IPFS_B32_UPPER_H_ -+ -+#include -+ -+namespace multibase { -+template <> -+struct traits<::multibase::encoding::base_32> { -+ constexpr static const std::array charset = { -+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', -+ 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', -+ 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7'}; -+ constexpr static const char name[] = "base_32"; -+ using execution_style = multibase::algorithm::block_tag; -+ constexpr static const char padding = 0; -+}; -+template <> -+struct traits<::multibase::encoding::base_32_upper> { -+ constexpr static const std::array charset = { -+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', -+ 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', -+ 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7'}; -+ constexpr static const char name[] = "base_32_upper"; -+ using execution_style = multibase::algorithm::block_tag; -+ constexpr static const char padding = 0; -+}; -+} // namespace multibase -+ -+namespace ipfs::mb { -+using base_32 = multibase::basic_algorithm; -+using base_32_upper = -+ multibase::basic_algorithm; -+} // namespace ipfs::mb -+ -+#endif // IPFS_B32_UPPER_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/block_requestor.cc b/third_party/ipfs_client/src/ipfs_client/block_requestor.cc -new file mode 100644 -index 0000000000000..8a63e6f7ae0cc ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/block_requestor.cc -@@ -0,0 +1 @@ -+#include -diff --git a/third_party/ipfs_client/src/ipfs_client/car.cc b/third_party/ipfs_client/src/ipfs_client/car.cc -new file mode 100644 -index 0000000000000..e36442347415f ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/car.cc -@@ -0,0 +1,132 @@ -+#include "car.h" -+ -+#include -+ -+#include -+ -+#include "log_macros.h" -+ -+#include -+ -+using Self = ipfs::Car; -+using Byte = ipfs::Byte; -+using ByteView = ipfs::ByteView; -+using VarInt = libp2p::multi::UVarint; -+ -+namespace { -+short ReadHeader(ByteView&, ipfs::ContextApi const&); -+std::pair GetV1PayloadPos(ByteView); -+} // namespace -+ -+Self::Car(ByteView bytes, ContextApi const& api) { -+ auto after_header = bytes; -+ auto version = ReadHeader(after_header, api); -+ switch (version) { -+ case 0: -+ LOG(ERROR) << "Problem parsing CAR header."; -+ break; -+ case 1: -+ LOG(INFO) << "Reading CARv1"; -+ data_ = after_header; -+ break; -+ case 2: { -+ auto [off, siz] = GetV1PayloadPos(after_header); -+ LOG(INFO) << "CARv2 carries a payload of " << siz << "B @ " << off; -+ // TODO validate off and siz are sane, e.g. not pointing back into pragma -+ // or whatever -+ data_ = bytes.subspan(off, siz); -+ ReadHeader(data_, api); -+ break; -+ } -+ default: -+ LOG(ERROR) << "Unsupported CAR format version " << version; -+ } -+} -+auto Self::NextBlock() -> std::optional { -+ auto len = VarInt::create(data_); -+ if (!len) { -+ return std::nullopt; -+ } -+ data_ = data_.subspan(len->size()); -+ if (len->toUInt64() > data_.size()) { -+ LOG(ERROR) << "Length prefix claims cid+block is " << len->toUInt64() -+ << " bytes, but I only have " << data_.size() -+ << " bytes left in the CAR payload."; -+ data_ = {}; -+ return std::nullopt; -+ } -+ Block rv; -+ rv.bytes = data_.subspan(0U, len->toUInt64()); -+ data_ = data_.subspan(len->toUInt64()); -+ if (rv.cid.ReadStart(rv.bytes)) { -+ // TODO : check hash -+ return rv; -+ } -+ return std::nullopt; -+} -+ -+namespace { -+// https://ipld.io/specs/transport/car/carv2/ -+short ReadHeader(ByteView& bytes, ipfs::ContextApi const& api) { -+ auto header_len = VarInt::create(bytes); -+ if (!header_len || -+ header_len->toUInt64() + header_len->size() > bytes.size()) { -+ return 0; -+ } -+ bytes = bytes.subspan(header_len->size()); -+ auto header_bytes = bytes.subspan(0UL, header_len->toUInt64()); -+ auto header = api.ParseCbor(header_bytes); -+ if (!header) { -+ return 0; -+ } -+ auto version_node = header->at("version"); -+ if (!version_node) { -+ return 0; -+ } -+ auto version = version_node->as_unsigned(); -+ if (version) { -+ bytes = bytes.subspan(header_len->toUInt64()); -+ return version.value(); -+ } -+ return 0; -+} -+std::uint64_t read_le_u64(ByteView bytes, unsigned& off) { -+ auto b = bytes.subspan(off, off + 8); -+ off += 8U; -+ auto shift_in = [](std::uint64_t i, Byte y) { -+ return (i << 8) | static_cast(y); -+ }; -+ return std::accumulate(b.rbegin(), b.rend(), 0UL, shift_in); -+} -+std::pair GetV1PayloadPos(ByteView bytes) { -+ // Following the 11 byte pragma, the CARv2 [header] is a fixed-length sequence -+ // of 40 bytes, broken into the following sections: -+ if (bytes.size() < 40) { -+ return {}; -+ } -+ -+ // Characteristics: A 128-bit (16-byte) bitfield used to describe certain -+ // features of the enclosed data. -+ auto reading_off = 16U; -+ -+ // Data offset: A 64-bit (8-byte) unsigned -+ // little-endian integer indicating the byte-offset from the beginning of the -+ // CARv2 [pragma] to the first byte of the CARv1 data payload. -+ auto data_offset = read_le_u64(bytes, reading_off); -+ -+ // Data size: A 64-bit -+ // (8-byte) unsigned little-endian integer indicating the byte-length of the -+ // CARv1 data payload. -+ auto data_size = read_le_u64(bytes, reading_off); -+ -+ // Index offset: A 64-bit (8-byte) unsigned little-endian -+ // integer indicating the byte-offset from the beginning of the CARv2 to the -+ // first byte of the index payload. This value may be 0 to indicate the -+ // absence of index data. -+ reading_off += 8; // Ignoring index and therefore index offset -+ -+ assert(reading_off == 40UL); -+ -+ return {data_offset, data_size}; -+} -+} // namespace -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/car.h b/third_party/ipfs_client/src/ipfs_client/car.h -new file mode 100644 -index 0000000000000..619ac48ed8cd3 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/car.h -@@ -0,0 +1,26 @@ -+#ifndef IPFS_CAR_H_ -+#define IPFS_CAR_H_ -+ -+#include -+#include -+ -+#include -+#include -+ -+namespace ipfs { -+class ContextApi; -+class Car { -+ public: -+ Car(ByteView, ContextApi const&); -+ struct Block { -+ Cid cid; -+ ByteView bytes; -+ }; -+ std::optional NextBlock(); -+ -+ private: -+ ByteView data_; -+}; -+} // namespace ipfs -+ -+#endif // IPFS_CAR_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/cid.cc b/third_party/ipfs_client/src/ipfs_client/cid.cc -new file mode 100644 -index 0000000000000..b20686086bca6 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/cid.cc -@@ -0,0 +1,86 @@ -+#include -+ -+#include -+#include -+ -+#include "log_macros.h" -+ -+using Self = ipfs::Cid; -+using VarInt = libp2p::multi::UVarint; -+ -+Self::Cid(ipfs::MultiCodec cdc, ipfs::MultiHash hsh) -+ : codec_{cdc}, hash_{hsh} {} -+ -+Self::Cid(ipfs::ByteView bytes) { -+ ReadStart(bytes); -+} -+ -+Self::Cid(std::string_view s) { -+ if (s.size() == 46 && s[0] == 'Q' && s[1] == 'm') { -+ auto bytes = mb::Codec::Get(mb::Code::BASE58_BTC)->decode(s); -+ auto view = ByteView{bytes}; -+ ReadStart(view); -+ } else if (auto bytes = mb::decode(s)) { -+ if (bytes->size() > 4) { -+ auto view = ByteView{bytes.value()}; -+ ReadStart(view); -+ } -+ } else { -+ LOG(WARNING) << "Failed to decode the multibase for a CID: " << s; -+ } -+} -+ -+bool Self::ReadStart(ByteView& bytes) { -+ if (bytes.size() >= 34 && bytes[0] == ipfs::Byte{0x12} && -+ bytes[1] == ipfs::Byte{0x20}) { -+ hash_ = MultiHash{bytes}; -+ codec_ = hash_.valid() ? MultiCodec::DAG_PB : MultiCodec::INVALID; -+ bytes = bytes.subspan(34); -+ return true; -+ } -+ auto version = VarInt::create(bytes); -+ if (!version) { -+ return false; -+ } -+ if (version->toUInt64() != 1U) { -+ LOG(ERROR) << "CID version " << version->toUInt64() << " not supported."; -+ return false; -+ } -+ bytes = bytes.subspan(version->size()); -+ auto codec = VarInt::create(bytes); -+ if (!codec) { -+ return false; -+ } -+ auto cdc = static_cast(codec->toUInt64()); -+ codec_ = Validate(cdc); -+ bytes = bytes.subspan(codec->size()); -+ return hash_.ReadPrefix(bytes); -+} -+ -+bool Self::valid() const { -+ return codec_ != MultiCodec::INVALID && hash_.valid(); -+} -+ -+auto Self::hash() const -> ByteView { -+ return hash_.digest(); -+} -+auto Self::hash_type() const -> HashType { -+ return multi_hash().type(); -+} -+ -+std::string Self::to_string() const { -+ std::vector binary; -+ auto append_varint = [&binary](auto x) { -+ auto i = static_cast(x); -+ VarInt v{i}; -+ auto b = v.toBytes(); -+ binary.insert(binary.end(), b.begin(), b.end()); -+ }; -+ append_varint(1); // CID version 1 -+ append_varint(codec()); -+ append_varint(hash_type()); -+ append_varint(hash().size()); -+ auto h = hash(); -+ binary.insert(binary.end(), h.begin(), h.end()); -+ return mb::encode(mb::Code::BASE32_LOWER, binary); -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/context_api.cc b/third_party/ipfs_client/src/ipfs_client/context_api.cc -new file mode 100644 -index 0000000000000..f58a062d780ab ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/context_api.cc -@@ -0,0 +1,26 @@ -+#include -+ -+#include "crypto/openssl_sha2_256.h" -+ -+using Self = ipfs::ContextApi; -+ -+Self::ContextApi() { -+#if HAS_OPENSSL_SHA -+ hashers_.emplace(HashType::SHA2_256, -+ std::make_unique()); -+#endif -+} -+ -+auto Self::Hash(HashType ht, ByteView data) -+ -> std::optional> { -+ auto it = hashers_.find(ht); -+ if (hashers_.end() == it || !(it->second)) { -+ return std::nullopt; -+ } -+ return it->second->hash(data); -+} -+ -+unsigned int Self::GetGatewayRate(std::string_view) { -+ return 120; -+} -+void Self::SetGatewayRate(std::string_view, unsigned int) {} -diff --git a/third_party/ipfs_client/src/ipfs_client/crypto/openssl_sha2_256.cc b/third_party/ipfs_client/src/ipfs_client/crypto/openssl_sha2_256.cc -new file mode 100644 -index 0000000000000..ff5c7a24d23bb ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/crypto/openssl_sha2_256.cc -@@ -0,0 +1,32 @@ -+#include "openssl_sha2_256.h" -+ -+using Self = ipfs::crypto::OpensslSha2_256; -+ -+#include "log_macros.h" -+ -+#if HAS_OPENSSL_SHA -+ -+#include -+ -+Self::~OpensslSha2_256() {} -+auto Self::hash(ipfs::ByteView data) -> std::optional> { -+ SHA256_CTX ctx; -+ if (1 != SHA256_Init(&ctx)) { -+ LOG(ERROR) << "Failed to initialize SHA256"; -+ return std::nullopt; -+ } -+ if (1 != SHA256_Update(&ctx, data.data(), data.size())) { -+ LOG(ERROR) << "Failure injesting data into SHA256."; -+ return {}; -+ } -+ std::vector rv(SHA256_DIGEST_LENGTH, Byte{}); -+ auto p = reinterpret_cast(rv.data()); -+ if (1 == SHA256_Final(p, &ctx)) { -+ return rv; -+ } else { -+ LOG(ERROR) << "Error calculating sha2-256 hash."; -+ return std::nullopt; -+ } -+} -+ -+#endif -diff --git a/third_party/ipfs_client/src/ipfs_client/crypto/openssl_sha2_256.h b/third_party/ipfs_client/src/ipfs_client/crypto/openssl_sha2_256.h -new file mode 100644 -index 0000000000000..c4e7bb975d366 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/crypto/openssl_sha2_256.h -@@ -0,0 +1,18 @@ -+#ifndef IPFS_OPENSSL_SHA2_256_H_ -+#define IPFS_OPENSSL_SHA2_256_H_ -+ -+#if __has_include() -+#define HAS_OPENSSL_SHA 1 -+#endif -+ -+#include -+ -+namespace ipfs::crypto { -+class OpensslSha2_256 final : public Hasher { -+ public: -+ ~OpensslSha2_256() noexcept override; -+ std::optional> hash(ByteView) override; -+}; -+} // namespace ipfs::crypto -+ -+#endif // IPFS_OPENSSL_SHA2_256_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/dag_cbor_value.cc b/third_party/ipfs_client/src/ipfs_client/dag_cbor_value.cc -new file mode 100644 -index 0000000000000..20a6fc713ad4e ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/dag_cbor_value.cc -@@ -0,0 +1,69 @@ -+#include -+ -+#include -+ -+#include "log_macros.h" -+ -+#include -+ -+using Self = ipfs::DagCborValue; -+ -+void Self::html(std::ostream& str) const { -+ if (auto u = as_unsigned()) { -+ str << "" << *u << "\n"; -+ } else if (auto si = as_signed()) { -+ str << "" << *si << "\n"; -+ } else if (auto fl = as_float()) { -+ str << "" << *si << "\n"; -+ } else if (auto s = as_string()) { -+ str << "

""; -+ for (auto c : *s) { -+ str << html_escape(c); -+ } -+ str << ""

\n"; -+ } else if (auto cid = as_link()) { -+ auto cs = cid.value().to_string(); -+ if (cs.size()) { -+ str << "" << cs -+ << "\n"; -+ } else { -+ str << "\n"; -+ } -+ } else if (auto bin = as_bytes()) { -+ str << "

0x"; -+ for (auto b : *bin) { -+ str << ' ' << std::hex << std::setw(2) << std::setfill('0') -+ << static_cast(b); -+ } -+ str << "

\n"; -+ } else if (is_array()) { -+ str << "
    \n"; -+ iterate_array([&str](auto& v) { -+ str << "
  1. \n"; -+ v.html(str); -+ str << "
  2. \n"; -+ }); -+ str << "
\n"; -+ } else if (is_map()) { -+ str << "\n"; -+ iterate_map([&str](auto k, auto& v) { -+ str << " \n"; -+ }); -+ str << "
" << k << "\n"; -+ v.html(str); -+ str << "
\n"; -+ } else if (auto bul = as_bool()) { -+ auto val = (bul.value() ? "True" : "False"); -+ str << " " << val << "\n"; -+ } else { -+ str << "\n"; -+ } -+} -+ -+std::string Self::html() const { -+ std::ostringstream oss; -+ oss << "DAG-CBOR Preview\n"; -+ html(oss); -+ oss << ""; -+ return oss.str(); -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/dag_json_value.cc b/third_party/ipfs_client/src/ipfs_client/dag_json_value.cc -new file mode 100644 -index 0000000000000..12a493cbd92cb ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/dag_json_value.cc -@@ -0,0 +1,22 @@ -+#include -+ -+#include -+ -+using Self = ipfs::DagJsonValue; -+ -+Self::~DagJsonValue() noexcept {} -+auto Self::get_if_link() const -> std::optional { -+ auto slash = (*this)["/"]; -+ if (!slash) { -+ return std::nullopt; -+ } -+ auto str = slash->get_if_string(); -+ if (!str) { -+ return std::nullopt; -+ } -+ auto cid = Cid(*str); -+ if (cid.valid()) { -+ return cid; -+ } -+ return std::nullopt; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/gateways.cc b/third_party/ipfs_client/src/ipfs_client/gateways.cc -new file mode 100644 -index 0000000000000..a4c3813412897 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gateways.cc -@@ -0,0 +1,121 @@ -+#include -+ -+#include -+ -+#include "log_macros.h" -+ -+#include -+#include -+#include -+ -+using namespace std::string_literals; -+ -+ipfs::Gateways::Gateways() -+ : random_engine_{std::random_device{}()}, dist_{0.01} { -+ auto gws = DefaultGateways(); -+ for (auto [k, v] : gws) { -+ known_gateways_[k] = v; -+ } -+} -+ipfs::Gateways::~Gateways() {} -+ -+auto ipfs::Gateways::GenerateList() -> GatewayList { -+ GatewayList result; -+ for (auto [k, v] : known_gateways_) { -+ result.push_back({k, v + dist_(random_engine_)}); -+ } -+ std::sort(result.begin(), result.end()); -+ return result; -+} -+ -+void ipfs::Gateways::promote(std::string const& key) { -+ auto it = known_gateways_.find(key); -+ if (known_gateways_.end() == it) { -+ LOG(ERROR) << "Can't promote (" << key -+ << ") because I don't know that one."; -+ } else { -+ auto l = known_gateways_.at(key)++; -+ if (l % (++up_log_ / 2) <= 9) { -+ LOG(INFO) << "Promote(" << key << ")"; -+ } -+ } -+} -+void ipfs::Gateways::demote(std::string const& key) { -+ auto it = known_gateways_.find(key); -+ if (known_gateways_.end() == it) { -+ VLOG(2) << "Can't demote " << key << " as I don't have that gateway."; -+ } else if (it->second) { -+ if (it->second-- % 3 == 0) { -+ LOG(INFO) << "Demote(" << key << ") to " << it->second; -+ } -+ } else { -+ LOG(INFO) << "Demoted(" << key << ") for the last time - dropping."; -+ known_gateways_.erase(it); -+ } -+} -+ -+void ipfs::Gateways::AddGateways(std::vector v) { -+ LOG(INFO) << "AddGateways(" << v.size() << ')'; -+ for (auto& ip : v) { -+ if (ip.empty()) { -+ LOG(ERROR) << "ERROR: Attempted to add empty string as gateway!"; -+ continue; -+ } -+ std::string prefix; -+ if (ip.find("://") == std::string::npos) { -+ prefix = "http://"; -+ prefix.append(ip); -+ } else { -+ prefix = ip; -+ } -+ if (prefix.back() != '/') { -+ prefix.push_back('/'); -+ } -+ if (known_gateways_.insert({prefix, 99}).second) { -+ VLOG(1) << "Adding discovered gateway " << prefix; -+ } -+ } -+} -+ -+auto ipfs::Gateways::DefaultGateways() -> GatewayList { -+ auto* ovr = std::getenv("IPFS_GATEWAY"); -+ if (ovr && *ovr) { -+ std::istringstream user_override{ovr}; -+ GatewayList result; -+ std::string gw; -+ while (user_override >> gw) { -+ if ( gw.empty() ) { -+ continue; -+ } -+ if ( gw.back() != '/' ) { -+ gw.push_back('/'); -+ } -+ result.push_back( {gw, 0} ); -+ } -+ auto N = static_cast(result.size()); -+ for (auto i = 0; i < N; ++i) { -+ auto& r = result[i]; -+ r.rate = N - i; -+ LOG(INFO) << "User-specified gateway: " << r.prefix << '=' << r.rate; -+ } -+ return result; -+ } -+ return {{"http://localhost:8080/"s, 929}, -+ {"https://jcsl.hopto.org/"s, 863}, -+ {"https://human.mypinata.cloud/"s, 798}, -+ {"https://ipfs.io/"s, 753}, -+ {"https://gateway.ipfs.io/"s, 678}, -+ {"https://dweb.link/"s, 598}, -+ {"https://gateway.pinata.cloud/"s, 519}, -+ {"https://ipfs.joaoleitao.org/"s, 434}, -+ {"https://ipfs.runfission.com/"s, 371}, -+ {"https://nftstorage.link/"s, 307}, -+ {"https://w3s.link/"s, 243}, -+ {"https://ipfs.fleek.co/"s, 203}, -+ {"https://ipfs.jpu.jp/"s, 162}, -+ {"https://permaweb.eu.org/"s, 121}, -+ {"https://jorropo.net/"s, 76}, -+ {"https://hardbin.com/"s, 39}, -+ {"https://ipfs.soul-network.com/"s, 1}, -+ {"https://storry.tv/"s, 0}}; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/generated_directory_listing.cc b/third_party/ipfs_client/src/ipfs_client/generated_directory_listing.cc -new file mode 100644 -index 0000000000000..5a7c4fce733d6 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/generated_directory_listing.cc -@@ -0,0 +1,45 @@ -+#include "generated_directory_listing.h" -+ -+#include "log_macros.h" -+ -+ipfs::GeneratedDirectoryListing::GeneratedDirectoryListing( -+ std::string_view base_path) -+ : html_("\n "), base_path_(base_path) { -+ if (base_path.empty() || base_path[0] != '/') { -+ base_path_.insert(0UL, 1UL, '/'); -+ } -+ if (base_path_.back() != '/') { -+ base_path_.push_back('/'); -+ } -+ html_.append(base_path_) -+ .append(" (directory listing)\n") -+ .append(" \n") -+ .append("
    \n"); -+ if (base_path.find_first_not_of("/") < base_path.size()) { -+ std::string_view dotdotpath{base_path_}; -+ dotdotpath.remove_suffix(1); // Remove that trailing / -+ auto last_slash = dotdotpath.find_last_of("/"); -+ dotdotpath = dotdotpath.substr(0, last_slash + 1UL); -+ AddLink("..", dotdotpath); -+ } -+} -+ -+void ipfs::GeneratedDirectoryListing::AddEntry(std::string_view name) { -+ auto path = base_path_; -+ path.append(name); -+ AddLink(name, path); -+} -+void ipfs::GeneratedDirectoryListing::AddLink(std::string_view name, -+ std::string_view path) { -+ html_.append("
  • \n") -+ .append(" ") -+ .append(name) -+ .append("\n") -+ .append("
  • \n"); -+} -+ -+std::string const& ipfs::GeneratedDirectoryListing::Finish() { -+ return html_.append("
\n").append(" \n").append("\n"); -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/generated_directory_listing.h b/third_party/ipfs_client/src/ipfs_client/generated_directory_listing.h -new file mode 100644 -index 0000000000000..8daa0ec01cb9e ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/generated_directory_listing.h -@@ -0,0 +1,41 @@ -+#ifndef IPFS_GENERATED_DIRECTORY_LISTING_H_ -+#define IPFS_GENERATED_DIRECTORY_LISTING_H_ -+ -+#include -+#include -+ -+namespace ipfs { -+ -+/*! -+ * \brief An index.html listing out a directory node's content -+ */ -+class GeneratedDirectoryListing { -+ public: -+ -+ /*! -+ * \brief Get the HTML preamble going -+ * \param base_path - The path _to_ this directory -+ */ -+ GeneratedDirectoryListing(std::string_view base_path); -+ -+ /*! -+ * \brief Add an entry to the list -+ * \param name - The directory's way of referring to that CID -+ */ -+ void AddEntry(std::string_view name); -+ -+ /*! -+ * \brief Finish up all the HTML stuff at the end. -+ * \return The generated HTML -+ */ -+ std::string const& Finish(); -+ -+ private: -+ std::string html_; -+ std::string base_path_; -+ -+ void AddLink(std::string_view name, std::string_view path); -+}; -+} // namespace ipfs -+ -+#endif // IPFS_GENERATED_DIRECTORY_LISTING_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/block_request_splitter.cc b/third_party/ipfs_client/src/ipfs_client/gw/block_request_splitter.cc -new file mode 100644 -index 0000000000000..3ded788f6bdf1 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/block_request_splitter.cc -@@ -0,0 +1,30 @@ -+#include -+ -+#include -+ -+using Self = ipfs::gw::BlockRequestSplitter; -+ -+std::string_view Self::name() const { -+ return "BlockRequestSplitter"; -+} -+auto Self::handle(ipfs::gw::RequestPtr r) -> HandleOutcome { -+ if (r->type != Type::Car) { -+ return HandleOutcome::NOT_HANDLED; -+ } -+ { -+ auto br = std::make_shared(*r); -+ br->type = Type::Block; -+ br->path.clear(); -+ forward(br); -+ } -+ /* -+ { -+ auto pr = std::make_shared(*r); -+ pr->type = Type::Providers; -+ pr->path.clear(); -+ pr->affinity.clear(); -+ forward(pr); -+ } -+ */ -+ return HandleOutcome::NOT_HANDLED; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/default_requestor.cc b/third_party/ipfs_client/src/ipfs_client/gw/default_requestor.cc -new file mode 100644 -index 0000000000000..974cf5b8539e4 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/default_requestor.cc -@@ -0,0 +1,30 @@ -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+auto ipfs::gw::default_requestor(ipfs::GatewayList /* gws */, -+ std::shared_ptr early, -+ std::shared_ptr api) -+ -> std::shared_ptr { -+ auto result = std::make_shared(); -+ result->or_else(std::make_shared()); -+ if (early) { -+ result->or_else(early); -+ early->api(api); -+ } -+ // auto pool = std::make_shared(); -+ result->or_else(std::make_shared(api)) -+ .or_else(std::make_shared()) -+ .or_else(std::make_shared()); -+ // for (auto& gw : gws) { -+ // auto gwr = std::make_shared(gw.prefix, gw.rate, -+ // api); pool->add(gwr); -+ // } -+ return result; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/dnslink_requestor.cc b/third_party/ipfs_client/src/ipfs_client/gw/dnslink_requestor.cc -new file mode 100644 -index 0000000000000..731b750ffd43a ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/dnslink_requestor.cc -@@ -0,0 +1,69 @@ -+#include -+ -+#include "ipfs_client/ipld/ipns_name.h" -+ -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include "log_macros.h" -+ -+#include -+ -+using Self = ipfs::gw::DnsLinkRequestor; -+using namespace std::literals; -+ -+Self::DnsLinkRequestor(std::shared_ptr api) { -+ api_ = api; -+} -+std::string_view Self::name() const { -+ return "DNSLink requestor"; -+} -+namespace { -+bool parse_results(ipfs::gw::RequestPtr req, -+ std::vector const& results, -+ std::shared_ptr const&); -+} -+auto Self::handle(ipfs::gw::RequestPtr req) -> HandleOutcome { -+ if (req->type != Type::DnsLink) { -+ return HandleOutcome::NOT_HANDLED; -+ } -+ // std::function requires target be copy-constructible -+ auto success = std::make_shared(); -+ *success = false; -+ auto a = api_; -+ auto res = [req, success, a](std::vector const& results) { -+ *success = *success || parse_results(req, results, a); -+ }; -+ auto don = [success, req]() { -+ LOG(INFO) << "DNSLink request completed for " << req->main_param -+ << " success=" << *success; -+ if (!*success) { -+ req->dependent->finish(ipfs::Response::HOST_NOT_FOUND); -+ } -+ }; -+ api_->SendDnsTextRequest("_dnslink." + req->main_param, res, std::move(don)); -+ return HandleOutcome::PENDING; -+} -+namespace { -+bool parse_results(ipfs::gw::RequestPtr req, -+ std::vector const& results, -+ std::shared_ptr const& api) { -+ constexpr auto prefix = "dnslink="sv; -+ LOG(INFO) << "Scanning " << results.size() << " DNS TXT records for " -+ << req->main_param << " looking for dnslink..."; -+ for (auto& result : results) { -+ if (starts_with(result, prefix)) { -+ LOG(INFO) << "DNSLink result=" << result; -+ req->RespondSuccessfully(result.substr(prefix.size()), api); -+ return true; -+ } else { -+ LOG(INFO) << "Irrelevant TXT result, ignored: " << result; -+ } -+ } -+ return false; -+} -+} // namespace -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/gateway_http_requestor.cc b/third_party/ipfs_client/src/ipfs_client/gw/gateway_http_requestor.cc -new file mode 100644 -index 0000000000000..5fd1d91ec2b9d ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/gateway_http_requestor.cc -@@ -0,0 +1,136 @@ -+#include "gateway_http_requestor.h" -+ -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include "log_macros.h" -+ -+using Self = ipfs::gw::GatewayHttpRequestor; -+using ReqTyp = ipfs::gw::Type; -+ -+std::string_view Self::name() const { -+ return "simplistic HTTP requestor"; -+} -+auto Self::handle(ipfs::gw::RequestPtr r) -> HandleOutcome { -+ DCHECK(r); -+ DCHECK(r->dependent); -+ DCHECK_GT(prefix_.size(), 0UL); -+ if (!r->is_http()) { -+ LOG(ERROR) << name() << " only handles HTTP requests"; -+ return HandleOutcome::NOT_HANDLED; -+ } -+ auto req_key = r->url_suffix().append(r->accept()); -+ if (seen_[req_key] > 0xFD) { -+ return HandleOutcome::NOT_HANDLED; -+ } -+ if (target(*r) <= r->parallel + pending_ + seen_[req_key]) { -+ return HandleOutcome::MAYBE_LATER; -+ } -+ auto desc = r->describe_http(prefix_); -+ if (!desc.has_value() || desc.value().url.empty()) { -+ LOG(ERROR) -+ << r->debug_string() -+ << " is HTTP but can't describe the HTTP request that would happen?"; -+ return HandleOutcome::NOT_HANDLED; -+ } -+ desc.value().timeout_seconds += extra_seconds_; -+ auto cb = [this, r, desc, req_key](std::int16_t status, std::string_view body, -+ ContextApi::HeaderAccess ha) { -+ if (r->parallel) { -+ r->parallel--; -+ } -+ if (pending_) { -+ pending_--; -+ } -+ if (r->type == Type::Zombie) { -+ return; -+ } else if (status == 408 || status == 504) { -+ // Timeouts -+ extra_seconds_++; -+ forward(r); -+ return; -+ } else if (status / 100 == 2) { -+ auto ct = ha("content-type"); -+ std::transform(ct.begin(), ct.end(), ct.begin(), ::tolower); -+ if (ct.empty()) { -+ LOG(ERROR) << "No content-type header?"; -+ } -+ if (ct.size() && desc->accept.size() && -+ ct.find(desc->accept) == std::string::npos) { -+ LOG(WARNING) << "Requested with Accept: " << desc->accept -+ << " but received response with content-type: " << ct; -+ LOG(INFO) << "Demote(" << prefix_ << ')'; -+ } else if (!r->RespondSuccessfully(body, api_)) { -+ LOG(ERROR) << "Got an unuseful response from " << prefix_ -+ << " forwarding request " << r->debug_string() -+ << " to next requestor."; -+ } else { -+ // Good cases -+ if (typ_good_.insert(r->type).second) { -+ VLOG(1) << prefix_ << " OK with requests of type " -+ << static_cast(r->type); -+ } else if (typ_bad_.erase(r->type)) { -+ VLOG(1) << prefix_ << " truly OK with requests of type " -+ << static_cast(r->type); -+ } -+ if (aff_good_.insert(r->affinity).second) { -+ VLOG(1) << prefix_ << " likes requests in the neighborhood of " -+ << r->affinity; -+ } else if (aff_bad_.erase(r->affinity)) { -+ VLOG(1) << prefix_ << " truly OK with affinity " << r->affinity; -+ } -+ VLOG(2) << prefix_ << " had a success on " << r->debug_string(); -+ LOG(INFO) << "Promote(" << prefix_ << ')'; -+ ++strength_; -+ return; -+ } -+ } else if (status / 100 == 4) { -+ seen_[req_key] += 9; -+ } -+ seen_[req_key] += 9; -+ LOG(INFO) << "Demote(" << prefix_ << ')'; -+ if (strength_ > 0) { -+ --strength_; -+ } -+ aff_bad_.insert(r->affinity); -+ typ_bad_.insert(r->type); -+ forward(r); -+ }; -+ DCHECK(api_); -+ api_->SendHttpRequest(desc.value(), cb); -+ seen_[req_key]++; -+ pending_++; -+ return HandleOutcome::PENDING; -+} -+ -+Self::GatewayHttpRequestor(std::string gateway_prefix, -+ int strength, -+ std::shared_ptr api) -+ : prefix_{gateway_prefix}, strength_{strength} { -+ api_ = api; -+} -+Self::~GatewayHttpRequestor() {} -+ -+int Self::target(GatewayRequest const& r) const { -+ int result = (strength_ - pending_) / 2; -+ if (!pending_) { -+ ++result; -+ } -+ if (typ_good_.count(r.type)) { -+ result += 3; -+ } -+ if (!typ_bad_.count(r.type)) { -+ result += 2; -+ } -+ if (aff_good_.count(r.affinity)) { -+ result += 5; -+ } -+ if (aff_bad_.count(r.affinity) == 0UL) { -+ result += 4; -+ } -+ return result; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/gateway_http_requestor.h b/third_party/ipfs_client/src/ipfs_client/gw/gateway_http_requestor.h -new file mode 100644 -index 0000000000000..8c61bef879db8 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/gateway_http_requestor.h -@@ -0,0 +1,34 @@ -+#ifndef IPFS_GATEWAY_HTTP_REQUESTOR_H_ -+#define IPFS_GATEWAY_HTTP_REQUESTOR_H_ -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+namespace ipfs::gw { -+class GatewayHttpRequestor final : public Requestor { -+ std::string prefix_; -+ int strength_; -+ std::unordered_map seen_; -+ std::set aff_good_, aff_bad_; -+ std::set typ_good_, typ_bad_; -+ int pending_ = 0; -+ int extra_seconds_ = 0; -+ -+ HandleOutcome handle(RequestPtr) override; -+ std::string_view name() const override; -+ int target(GatewayRequest const&) const; -+ -+ public: -+ GatewayHttpRequestor(std::string gateway_prefix, -+ int strength, -+ std::shared_ptr); -+ ~GatewayHttpRequestor() noexcept override; -+}; -+} // namespace ipfs::gw -+ -+#endif // IPFS_GATEWAY_HTTP_REQUESTOR_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/gateway_request.cc b/third_party/ipfs_client/src/ipfs_client/gw/gateway_request.cc -new file mode 100644 -index 0000000000000..0847ff7aca3eb ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/gateway_request.cc -@@ -0,0 +1,324 @@ -+#include -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "log_macros.h" -+ -+#include -+#include -+ -+using namespace std::literals; -+ -+using Self = ipfs::gw::GatewayRequest; -+ -+std::shared_ptr Self::fromIpfsPath(ipfs::SlashDelimited p) { -+ auto name_space = p.pop(); -+ auto r = std::make_shared(); -+ r->main_param = p.pop(); -+ Cid cid(r->main_param); -+ if (cid.valid()) { -+ r->cid = std::move(cid); -+ } else { -+ r->cid = std::nullopt; -+ } -+ if (name_space == "ipfs") { -+ if (!r->cid.has_value()) { -+ LOG(ERROR) << "IPFS request with invalid/unsupported CID " -+ << r->main_param; -+ return {}; -+ } -+ if (r->cid.value().hash_type() == HashType::IDENTITY) { -+ r->type = Type::Identity; -+ } else { -+ r->path = p.pop_all(); -+ r->type = r->path.empty() ? Type::Block : Type::Car; -+ } -+ } else if (name_space == "ipns") { -+ r->path = p.pop_all(); -+ if (Cid(r->main_param).valid()) { -+ r->type = Type::Ipns; -+ } else { -+ r->type = Type::DnsLink; -+ } -+ } else { -+ LOG(FATAL) << "Unsupported namespace in ipfs path: /" << name_space << '/' -+ << p.pop_all(); -+ } -+ return r; -+} -+ -+std::string Self::url_suffix() const { -+ switch (type) { -+ case Type::Block: -+ return "/ipfs/" + main_param; -+ case Type::Car: -+ return "/ipfs/" + main_param + "/" + path + "?dag-scope=entity"; -+ case Type::Ipns: -+ return "/ipns/" + main_param; -+ case Type::Providers: -+ return "/routing/v1/providers/" + main_param; -+ case Type::DnsLink: -+ LOG(FATAL) << "Don't try to use HTTP(s) for DNS TXT records."; -+ return {}; -+ case Type::Identity: -+ case Type::Zombie: -+ return {}; -+ } -+ LOG(FATAL) << "Unhandled gateway request type: " << static_cast(type); -+ return {}; -+} -+std::string_view Self::accept() const { -+ switch (type) { -+ case Type::Block: -+ return "application/vnd.ipld.raw"sv; -+ case Type::Ipns: -+ return "application/vnd.ipfs.ipns-record"sv; -+ case Type::Car: -+ return "application/vnd.ipld.car"sv; -+ case Type::Providers: -+ return "application/json"sv; -+ case Type::DnsLink: -+ // TODO : not sure this advice is 100% good, actually. -+ // If the user's system setup allows for text records to actually work, -+ // it would be good to respect their autonomy and try to follow the -+ // system's DNS setup. However, it's extremely easy to get yourself in a -+ // situation where Chromium _cannot_ access text records. If you're in -+ // that scenario, it might be better to try to use an IPFS gateway with -+ // DNSLink capability. -+ LOG(FATAL) << "Don't try to use HTTP(s) for DNS TXT records."; -+ return {}; -+ case Type::Identity: -+ case Type::Zombie: -+ return {}; -+ } -+ LOG(FATAL) << "Invalid gateway request type: " << static_cast(type); -+ return {}; -+} -+short Self::timeout_seconds() const { -+ switch (type) { -+ case Type::DnsLink: -+ return 16; -+ case Type::Block: -+ return 39; -+ case Type::Providers: -+ return 64; -+ case Type::Car: -+ return 128; -+ case Type::Ipns: -+ return 256; -+ case Type::Identity: -+ case Type::Zombie: -+ return 0; -+ } -+ LOG(FATAL) << "timeout_seconds() called for unsupported gateway request type " -+ << static_cast(type); -+ return 0; -+} -+ -+auto Self::identity_data() const -> std::string_view { -+ if (type != Type::Identity) { -+ return ""; -+ } -+ auto hash = cid.value().hash(); -+ auto d = reinterpret_cast(hash.data()); -+ return std::string_view{d, hash.size()}; -+} -+ -+bool Self::is_http() const { -+ switch (type) { -+ case Type::Ipns: -+ case Type::Car: -+ case Type::Block: -+ case Type::Providers: -+ return true; -+ case Type::Identity: -+ case Type::DnsLink: -+ case Type::Zombie: -+ return false; -+ } -+ return true; -+} -+auto Self::describe_http(std::string_view prefix) const -+ -> std::optional { -+ if (!is_http()) { -+ return {}; -+ } -+ DCHECK(!prefix.empty()); -+ auto url = url_suffix(); -+ if (url.front() == '/' && prefix.back() == '/') { -+ prefix.remove_suffix(1UL); -+ } else if (url.front() != '/' && prefix.back() != '/') { -+ url.insert(0UL, 1UL, '/'); -+ } -+ url.insert(0UL, prefix); -+ return HttpRequestDescription{url, timeout_seconds(), std::string{accept()}, max_response_size()}; -+} -+std::optional Self::max_response_size() const { -+ switch (type) { -+ case Type::Identity: -+ return 0; -+ case Type::DnsLink: -+ return std::nullopt; -+ case Type::Ipns: -+ return MAX_IPNS_PB_SERIALIZED_SIZE; -+ case Type::Block: -+ return BLOCK_RESPONSE_BUFFER_SIZE; -+ case Type::Car: { -+ // There could be an unlimited number of blocks in the CAR -+ // The _floor_ is the number of path components. -+ // But one path component could be a HAMT sharded directory that we may -+ // need to pass through several layers on. -+ // And the final path component could be a UnixFS file with an unlimited -+ // number of blocks in it. -+ return std::nullopt; -+ } -+ case Type::Zombie: -+ return 0; -+ case Type::Providers: -+ // This one's tricky. -+ // One could easily guess a pracitical limit to the size of a Peer, -+ // and the spec says it SHOULD be limited to 100 peers. -+ // But there's no guaranteed limits. A peer could have an unlimited -+ // number of multiaddrs. And they're allowed to throw in arbitrary -+ // fields I'm supposed to ignore. So in theory it could be infinitely -+ // large. -+ return std::nullopt; -+ } -+ LOG(ERROR) << "Invalid gateway request type " << static_cast(type); -+ return std::nullopt; -+} -+std::string_view ipfs::gw::name(ipfs::gw::Type t) { -+ using ipfs::gw::Type; -+ switch (t) { -+ case Type::Block: -+ return "Block"; -+ case Type::Car: -+ return "Car"; -+ case Type::Ipns: -+ return "Ipns"; -+ case Type::DnsLink: -+ return "DnsLink"; -+ case Type::Providers: -+ return "Providers"; -+ case Type::Identity: -+ return "Identity"; -+ case Type::Zombie: -+ return "CompletedRequest"; -+ } -+ static std::array buf; -+ std::sprintf(buf.data(), "InvalidType %d", static_cast(t)); -+ return buf.data(); -+} -+std::string Self::debug_string() const { -+ std::ostringstream oss; -+ oss << "Request{Type=" << type << ' ' << main_param; -+ if (!path.empty()) { -+ oss << ' ' << path; -+ } -+ if (dependent) { -+ oss << " for=" << dependent->path().to_string(); -+ } -+ oss << " plel=" << parallel << '}'; -+ return oss.str(); -+} -+bool Self::RespondSuccessfully(std::string_view bytes, -+ std::shared_ptr const& api) { -+ using namespace ipfs::ipld; -+ bool success = false; -+ switch (type) { -+ case Type::Block: { -+ DCHECK(cid.has_value()); -+ if (!cid.has_value()) { -+ LOG(ERROR) << "Your CID doesn't even have a value!"; -+ return false; -+ } -+ DCHECK(api); -+ auto node = DagNode::fromBytes(api, cid.value(), bytes); -+ success = orchestrator_->add_node(main_param, node); -+ } break; -+ case Type::Identity: -+ success = orchestrator_->add_node( -+ main_param, std::make_shared(std::string{bytes})); -+ break; -+ case Type::Ipns: -+ if (cid.has_value()) { -+ DCHECK(api); -+ auto byte_ptr = reinterpret_cast(bytes.data()); -+ auto rec = ipfs::ValidateIpnsRecord({byte_ptr, bytes.size()}, -+ cid.value(), *api); -+ if (rec.has_value()) { -+ auto node = DagNode::fromIpnsRecord(rec.value()); -+ success = orchestrator_->add_node(main_param, node); -+ } else { -+ LOG(ERROR) << "IPNS record failed to validate!"; -+ return false; -+ } -+ } -+ break; -+ case Type::DnsLink: -+ LOG(INFO) << "Resolved " << debug_string() << " to " << bytes; -+ if (orchestrator_) { -+ success = orchestrator_->add_node( -+ main_param, std::make_shared(bytes)); -+ } else { -+ LOG(FATAL) << "I have no orchestrator!!"; -+ } -+ break; -+ case Type::Car: { -+ DCHECK(api); -+ Car car(as_bytes(bytes), *api); -+ auto added = 0; -+ while (auto block = car.NextBlock()) { -+ auto cid_s = block->cid.to_string(); -+ auto n = DagNode::fromBytes(api, block->cid, block->bytes); -+ if (!n) { -+ LOG(ERROR) << "Unable to handle block from CAR: " << cid_s; -+ } else if (orchestrator_->add_node(cid_s, n)) { -+ ++added; -+ } else { -+ LOG(INFO) << "Did not add node from CAR: " << cid_s; -+ } -+ } -+ LOG(INFO) << "Added " << added << " nodes from a CAR."; -+ success = added > 0; -+ break; -+ } -+ case Type::Providers: -+ LOG(WARNING) << "TODO - handle responses to providers requests."; -+ break; -+ case Type::Zombie: -+ LOG(WARNING) << "Responding to a zombie is ill-advised."; -+ break; -+ default: -+ LOG(ERROR) << "TODO " << static_cast(type); -+ } -+ if (success) { -+ for (auto& hook : bytes_received_hooks) { -+ hook(bytes); -+ } -+ bytes_received_hooks.clear(); -+ orchestrator_->build_response(dependent); -+ } -+ return success; -+} -+void Self::Hook(std::function f) { -+ bytes_received_hooks.push_back(f); -+} -+void Self::orchestrator(std::shared_ptr const& orc) { -+ orchestrator_ = orc; -+} -+bool Self::PartiallyRedundant() const { -+ if (!orchestrator_) { -+ return false; -+ } -+ return orchestrator_->has_key(main_param); -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/inline_request_handler.cc b/third_party/ipfs_client/src/ipfs_client/gw/inline_request_handler.cc -new file mode 100644 -index 0000000000000..435142a1a74ee ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/inline_request_handler.cc -@@ -0,0 +1,23 @@ -+#include -+ -+#include -+#include -+#include -+ -+#include "log_macros.h" -+ -+using Self = ipfs::gw::InlineRequestHandler; -+ -+std::string_view Self::name() const { -+ return "InlineRequestHandler"; -+} -+auto Self::handle(ipfs::gw::RequestPtr req) -> HandleOutcome { -+ if (req->type != gw::Type::Identity) { -+ VLOG(2) << ipfs::gw::name(req->type); -+ return HandleOutcome::NOT_HANDLED; -+ } -+ std::string data{req->identity_data()}; -+ LOG(INFO) << "Responding to inline CID without using network."; -+ req->RespondSuccessfully(data, api_); -+ return HandleOutcome::DONE; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/requestor.cc b/third_party/ipfs_client/src/ipfs_client/gw/requestor.cc -new file mode 100644 -index 0000000000000..72e6746c7a807 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/requestor.cc -@@ -0,0 +1,70 @@ -+#include -+ -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+#include "log_macros.h" -+ -+using Self = ipfs::gw::Requestor; -+using ReqPtr = std::shared_ptr; -+ -+Self& Self::or_else(std::shared_ptr p) { -+ if (next_) { -+ next_->or_else(p); -+ } else { -+ VLOG(2) << name() << " is followed by " << p->name(); -+ next_ = p; -+ } -+ if (api_ && !p->api_) { -+ VLOG(1) << name() << " granting context to " << p->name(); -+ p->api_ = api_; -+ } -+ return *this; -+} -+ -+void Self::request(ReqPtr req) { -+ if (!req || req->type == Type::Zombie) { -+ return; -+ } -+ switch (handle(req)) { -+ case HandleOutcome::MAYBE_LATER: -+ // TODO -+ forward(req); -+ break; -+ case HandleOutcome::PARALLEL: -+ case HandleOutcome::NOT_HANDLED: -+ if (next_) { -+ next_->request(req); -+ } else { -+ LOG(ERROR) << "Ran out of Requestors in the chain while looking for " -+ "one that can handle " -+ << req->debug_string(); -+ definitive_failure(req); -+ } -+ break; -+ case HandleOutcome::PENDING: -+ break; -+ case HandleOutcome::DONE: -+ VLOG(2) << req->debug_string() << " finished synchronously: " << name(); -+ break; -+ } -+} -+void Self::definitive_failure(ipfs::gw::RequestPtr r) const { -+ DCHECK(r); -+ DCHECK(r->dependent); -+ r->dependent->finish(Response::PLAIN_NOT_FOUND); -+} -+ -+void Self::forward(ipfs::gw::RequestPtr req) const { -+ if (next_) { -+ next_->request(req); -+ } -+} -+void Self::api(std::shared_ptr a) { -+ api_ = a; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/requestor_pool.cc b/third_party/ipfs_client/src/ipfs_client/gw/requestor_pool.cc -new file mode 100644 -index 0000000000000..b337dda1b1529 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/requestor_pool.cc -@@ -0,0 +1,73 @@ -+#include "requestor_pool.h" -+ -+#include -+ -+#include "log_macros.h" -+ -+using Self = ipfs::gw::RequestorPool; -+ -+std::string_view Self::name() const { -+ return "requestor pool"; -+} -+Self& Self::add(std::shared_ptr r) { -+ if (api_ && !(r->api_)) { -+ r->api_ = api_; -+ } -+ pool_.push_back(r); -+ r->or_else(shared_from_this()); -+ return *this; -+} -+auto Self::handle(ipfs::gw::RequestPtr req) -> HandleOutcome { -+ auto now = std::time(nullptr); -+ for (auto i = 0UL; i * 2 < waiting_.size(); ++i) { -+ auto& t = waiting_.front().when; -+ if (t != now) { -+ auto to_pop = waiting_.front(); -+ waiting_.pop(); -+ check(to_pop); -+ } -+ } -+ return check({req, 0UL, 0L}); -+} -+auto Self::check(Waiting w) -> HandleOutcome { -+ using O = HandleOutcome; -+ auto next_retry = pool_.size(); -+ auto req = w.req; -+ if (req->PartiallyRedundant()) { -+ return O::DONE; -+ } -+ for (auto i = w.at_idx; i < pool_.size(); ++i) { -+ if (req->type == Type::Zombie) { -+ return O::DONE; -+ } -+ auto& tor = pool_[i]; -+ switch (tor->handle(req)) { -+ case O::DONE: -+ LOG(INFO) << "RequestorPool::handle returning DONE because a member of " -+ "the pool's handle returned DONE."; -+ return O::DONE; -+ case O::PENDING: -+ case O::PARALLEL: -+ req->parallel++; -+ break; -+ case O::MAYBE_LATER: -+ if (next_retry == pool_.size()) { -+ next_retry = i; -+ } -+ break; -+ case O::NOT_HANDLED: -+ break; -+ } -+ } -+ if (req->parallel > 0) { -+ return O::PENDING; -+ } -+ if (next_retry < pool_.size()) { -+ w.when = std::time(nullptr); -+ waiting_.emplace(w); -+ return O::PENDING; -+ } -+ VLOG(1) << "Have exhausted all requestors in pool looking for " -+ << req->debug_string(); -+ return O::NOT_HANDLED; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/requestor_pool.h b/third_party/ipfs_client/src/ipfs_client/gw/requestor_pool.h -new file mode 100644 -index 0000000000000..86104319a32eb ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/requestor_pool.h -@@ -0,0 +1,32 @@ -+#ifndef IPFS_REQUESTOR_POOL_H_ -+#define IPFS_REQUESTOR_POOL_H_ -+ -+#include -+ -+#include -+ -+#include -+#include -+#include -+ -+namespace ipfs::gw { -+class RequestorPool : public Requestor { -+ std::string_view name() const override; -+ HandleOutcome handle(RequestPtr) override; -+ -+ std::vector> pool_; -+ struct Waiting { -+ RequestPtr req; -+ std::size_t at_idx; -+ std::time_t when; -+ }; -+ std::queue waiting_; -+ -+ HandleOutcome check(Waiting); -+ -+ public: -+ RequestorPool& add(std::shared_ptr); -+}; -+} // namespace ipfs::gw -+ -+#endif // IPFS_REQUESTOR_POOL_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/gw/terminating_requestor.cc b/third_party/ipfs_client/src/ipfs_client/gw/terminating_requestor.cc -new file mode 100644 -index 0000000000000..791ffd849ddd4 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/gw/terminating_requestor.cc -@@ -0,0 +1,23 @@ -+#include "ipfs_client/gw/terminating_requestor.h" -+ -+#include -+ -+#include "log_macros.h" -+ -+using Self = ipfs::gw::TerminatingRequestor; -+ -+std::string_view Self::name() const { -+ return "Terminating requestor"; -+} -+auto Self::handle(ipfs::gw::RequestPtr r) -> HandleOutcome { -+ if (r->type == Type::Zombie) { -+ return HandleOutcome::DONE; -+ } else if (r->parallel) { -+ return HandleOutcome::PENDING; -+ } else { -+ VLOG(2) << "Out of options, giving up on gateway request " -+ << r->debug_string(); -+ definitive_failure(r); -+ return HandleOutcome::DONE; -+ } -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/http_request_description.cc b/third_party/ipfs_client/src/ipfs_client/http_request_description.cc -new file mode 100644 -index 0000000000000..19b29d0ccde51 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/http_request_description.cc -@@ -0,0 +1,12 @@ -+#include -+ -+using Self = ipfs::HttpRequestDescription; -+ -+bool Self::operator==(HttpRequestDescription const& r) const { -+ // The concept of identity does NOT involve feedback-looping timeout fudge -+ // Nor is the acceptable size of a response necessary to distinguish. -+ return url == r.url && accept == r.accept; -+} -+bool Self::operator<(HttpRequestDescription const& r) const { -+ return url == r.url ? accept < r.accept : url < r.url; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/identity_cid.cc b/third_party/ipfs_client/src/ipfs_client/identity_cid.cc -new file mode 100644 -index 0000000000000..9ea2421d5cdf3 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/identity_cid.cc -@@ -0,0 +1,19 @@ -+#include -+ -+#include -+ -+namespace Self = ipfs::id_cid; -+ -+auto Self::forText(std::string_view txt) -> Cid { -+ txt = txt.substr(0UL, MaximumHashLength); -+ auto p = reinterpret_cast(txt.data()); -+ auto b = ByteView{p, txt.size()}; -+ MultiHash mh(HashType::IDENTITY, b); -+ if (mh.valid()) { -+ return Cid{MultiCodec::RAW, mh}; -+ } else { -+ LOG(FATAL) -+ << "We really shouldn't be able to fail to 'hash' using identity."; -+ return forText("Unreachable"); -+ } -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/ipfs_request.cc b/third_party/ipfs_client/src/ipfs_client/ipfs_request.cc -new file mode 100644 -index 0000000000000..e4f3e1e4b47e3 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipfs_request.cc -@@ -0,0 +1,43 @@ -+#include -+ -+#include -+ -+#include "log_macros.h" -+ -+#include -+ -+using Self = ipfs::IpfsRequest; -+ -+// Self::IpfsRequest(std::string path_p) -+// : path_{path_p}, callback_([](auto&, auto&) {}) {} -+Self::IpfsRequest(std::string path_p, Finisher f) -+ : path_{path_p}, callback_{f} {} -+ -+std::shared_ptr Self::fromUrl(std::string url, ipfs::IpfsRequest::Finisher f) { -+ url.erase(4UL, 2UL ); -+ url.insert(0UL, 1UL, '/'); -+ return std::make_shared(std::move(url), std::move(f)); -+} -+ -+void Self::till_next(std::size_t w) { -+ waiting_ = w; -+} -+void Self::finish(ipfs::Response& r) { -+ VLOG(2) << "IpfsRequest::finish(" << waiting_ << ',' << r.status_ << ");"; -+ if (waiting_) { -+ if (--waiting_) { -+ return; -+ } -+ } -+ callback_(*this, r); -+ // TODO - cancel other gw req pointing into this -+ callback_ = [](auto& q, auto&) { -+ VLOG(2) << "IPFS request " << q.path().pop_all() << " satisfied multiply"; -+ }; -+} -+bool Self::ready_after() { -+ return waiting_ == 0 || 0 == --waiting_; -+} -+void Self::new_path(std::string_view sv) { -+ path_.assign(sv); -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/chunk.cc b/third_party/ipfs_client/src/ipfs_client/ipld/chunk.cc -new file mode 100644 -index 0000000000000..e5540b080e4b3 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/chunk.cc -@@ -0,0 +1,19 @@ -+#include "chunk.h" -+ -+#include "log_macros.h" -+ -+using Chunk = ipfs::ipld::Chunk; -+ -+Chunk::Chunk(std::string data) : data_{data} {} -+Chunk::~Chunk() {} -+ -+auto Chunk::resolve(ResolutionState& params) -> ResolveResult { -+ if (params.IsFinalComponent()) { -+ return Response{"", 200, data_, params.MyPath().to_string()}; -+ } else { -+ LOG(ERROR) << "Can't resolve a path (" << params.MyPath() -+ << ") inside of a file chunk!"; -+ return ProvenAbsent{}; -+ } -+} -+ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/chunk.h b/third_party/ipfs_client/src/ipfs_client/ipld/chunk.h -new file mode 100644 -index 0000000000000..b846cc5379171 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/chunk.h -@@ -0,0 +1,18 @@ -+#ifndef IPFS_CHUNK_H_ -+#define IPFS_CHUNK_H_ -+ -+#include -+ -+namespace ipfs::ipld { -+class Chunk : public DagNode { -+ std::string const data_; -+ -+ ResolveResult resolve(ResolutionState&) override; -+ -+ public: -+ explicit Chunk(std::string); -+ virtual ~Chunk() noexcept; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_CHUNK_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/dag_cbor_node.cc b/third_party/ipfs_client/src/ipfs_client/ipld/dag_cbor_node.cc -new file mode 100644 -index 0000000000000..064a89373f743 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/dag_cbor_node.cc -@@ -0,0 +1,25 @@ -+#include "dag_cbor_node.h" -+ -+#include "log_macros.h" -+ -+using Self = ipfs::ipld::DagCborNode; -+ -+auto Self::resolve(ResolutionState& params) -> ResolveResult { -+ if (auto cid = doc_->as_link()) { -+ auto cid_str = cid.value().to_string(); -+ return CallChild(params, "", cid_str); -+ } -+ if (params.IsFinalComponent()) { -+ return Response{"text/html", 200, doc_->html(), -+ params.PathToResolve().to_string()}; -+ } -+ return CallChild(params, [this](std::string_view element_name) -> NodePtr { -+ if (auto child = doc_->at(element_name)) { -+ return std::make_shared(std::move(child)); -+ } -+ return {}; -+ }); -+} -+ -+Self::DagCborNode(std::unique_ptr p) : doc_{std::move(p)} {} -+Self::~DagCborNode() {} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/dag_cbor_node.h b/third_party/ipfs_client/src/ipfs_client/ipld/dag_cbor_node.h -new file mode 100644 -index 0000000000000..c9ba53331674a ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/dag_cbor_node.h -@@ -0,0 +1,22 @@ -+#ifndef IPFS_DAG_CBOR_NODE_H_ -+#define IPFS_DAG_CBOR_NODE_H_ -+ -+#include -+ -+#include -+ -+namespace ipfs::ipld { -+class DagCborNode final : public DagNode { -+ ResolveResult resolve(ResolutionState&) override; -+ -+ public: -+ using Data = DagCborValue; -+ explicit DagCborNode(std::unique_ptr); -+ ~DagCborNode() noexcept override; -+ -+ private: -+ std::unique_ptr doc_; -+}; -+} -+ -+#endif // IPFS_DAG_CBOR_NODE_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/dag_json_node.cc b/third_party/ipfs_client/src/ipfs_client/ipld/dag_json_node.cc -new file mode 100644 -index 0000000000000..dfb3f38b0a0e1 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/dag_json_node.cc -@@ -0,0 +1,95 @@ -+#include "dag_json_node.h" -+ -+#include -+ -+#include -+ -+using Self = ipfs::ipld::DagJsonNode; -+ -+Self::DagJsonNode(std::unique_ptr j) : data_(std::move(j)) { -+ auto cid = data_->get_if_link(); -+ if (!cid) { -+ return; -+ } -+ auto cid_str = cid->to_string(); -+ if (cid_str.size()) { -+ links_.emplace_back("", Link(cid_str)); -+ } -+} -+Self::~DagJsonNode() noexcept {} -+ -+auto Self::resolve(ResolutionState& params) -> ResolveResult { -+ auto respond_as_link = CallChild(params, ""); -+ if (!std::get_if(&respond_as_link)) { -+ return respond_as_link; -+ } -+ if (params.IsFinalComponent()) { -+ return Response{"text/html", 200, html(), params.MyPath().to_string()}; -+ } -+ return CallChild(params, [this](std::string_view name) -> NodePtr { -+ auto child_data = (*data_)[name]; -+ if (child_data) { -+ return std::make_shared(std::move(child_data)); -+ } -+ return {}; -+ }); -+} -+ -+auto Self::is_link() -> Link* { -+ if (links_.size() == 1UL && links_.front().first.empty()) { -+ return &links_.front().second; -+ } else { -+ return nullptr; -+ } -+} -+namespace { -+void write_body(std::ostream& str, ipfs::DagJsonValue const& val) { -+ if (auto link = val.get_if_link()) { -+ auto cid_str = link.value().to_string(); -+ str << "" << cid_str << "\n"; -+ } else if (auto keys = val.object_keys()) { -+ str << "{\n"; -+ for (auto& key : keys.value()) { -+ str << " \n \n" -+ << " \n \n \n"; -+ } -+ str << "
  ""; -+ for (auto c : key) { -+ str << html_escape(c); -+ } -+ str << "":\n"; -+ auto child = val[key]; -+ write_body(str, *child); -+ str << ",
}\n"; -+ } else if (val.iterate_list([](auto&) {})) { -+ str << "[\n"; -+ val.iterate_list([&str](auto& child) { -+ str << " \n \n \n \n"; -+ }); -+ str << "
  \n"; -+ write_body(str, child); -+ str << "
]\n"; -+ } else { -+ auto plain = val.pretty_print(); -+ // str << "

"; -+ for (auto c : plain) { -+ if (c == '\n') { -+ str << "
\n"; -+ } else { -+ str << html_escape(c); -+ } -+ } -+ // str << "

\n"; -+ } -+} -+} // namespace -+std::string const& Self::html() { -+ if (html_.empty()) { -+ std::ostringstream html; -+ html << "Preview of DAG-JSON\n"; -+ write_body(html, *data_); -+ html << "\n"; -+ html_ = html.str(); -+ } -+ return html_; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/dag_json_node.h b/third_party/ipfs_client/src/ipfs_client/ipld/dag_json_node.h -new file mode 100644 -index 0000000000000..1d8012a007487 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/dag_json_node.h -@@ -0,0 +1,22 @@ -+#ifndef IPFS_DAG_JSON_NODE_H_ -+#define IPFS_DAG_JSON_NODE_H_ -+ -+#include -+#include -+ -+namespace ipfs::ipld { -+class DagJsonNode final : public DagNode { -+ std::unique_ptr data_; -+ std::string html_; -+ ResolveResult resolve(ResolutionState& params) override; -+ Link* is_link(); -+ std::string const& html(); -+ -+ public: -+ DagJsonNode(std::unique_ptr); -+ ~DagJsonNode() noexcept override; -+}; -+ -+} // namespace ipfs::ipld -+ -+#endif // IPFS_DAG_JSON_NODE_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/dag_node.cc b/third_party/ipfs_client/src/ipfs_client/ipld/dag_node.cc -new file mode 100644 -index 0000000000000..4d11f6f23bc78 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/dag_node.cc -@@ -0,0 +1,248 @@ -+#include -+ -+#include "chunk.h" -+#include "dag_cbor_node.h" -+#include "dag_json_node.h" -+#include "directory_shard.h" -+#include "ipns_name.h" -+#include "root.h" -+#include "small_directory.h" -+#include "symlink.h" -+#include "unixfs_file.h" -+ -+#include -+#include -+#include -+ -+#include "log_macros.h" -+ -+#include -+#include -+ -+using Node = ipfs::ipld::DagNode; -+ -+std::shared_ptr Node::fromBytes(std::shared_ptr const& api, -+ Cid const& cid, -+ std::string_view bytes) { -+ return fromBytes(api, cid, as_bytes(bytes)); -+} -+auto Node::fromBytes(std::shared_ptr const& api, -+ ipfs::Cid const& cid, -+ ipfs::ByteView bytes) -> NodePtr { -+ std::shared_ptr result = nullptr; -+ auto hash = api->Hash(cid.hash_type(), bytes); -+ if (!hash.has_value()) { -+ LOG(ERROR) << "Could not hash response for " << cid.to_string(); -+ return {}; -+ } -+ if (hash.value().size() != cid.hash().size()) { -+ return {}; -+ } -+ for (auto i = 0U; i < hash.value().size(); ++i) { -+ auto e = cid.hash()[i]; -+ auto a = hash.value().at(i); -+ if (e != a) { -+ return {}; -+ } -+ } -+ auto required = cid.hash(); -+ auto calculated = hash.value(); -+ if (!std::equal(required.begin(), required.end(), calculated.begin(), -+ calculated.end())) { -+ LOG(ERROR) << "Hash of response did not match the one in the CID " -+ << cid.to_string(); -+ return {}; -+ } -+ switch (cid.codec()) { -+ case MultiCodec::DAG_CBOR: { -+ auto p = reinterpret_cast(bytes.data()); -+ auto cbor = api->ParseCbor({p, bytes.size()}); -+ if (cbor) { -+ result = std::make_shared(std::move(cbor)); -+ } else { -+ LOG(ERROR) << "CBOR node " << cid.to_string() -+ << " does not parse as CBOR."; -+ } -+ } break; -+ case MultiCodec::DAG_JSON: { -+ auto p = reinterpret_cast(bytes.data()); -+ auto json = api->ParseJson({p, bytes.size()}); -+ if (json) { -+ result = std::make_shared(std::move(json)); -+ } else { -+ LOG(ERROR) << "JSON node " << cid.to_string() -+ << " does not parse as JSON."; -+ } -+ } break; -+ case MultiCodec::RAW: -+ case MultiCodec::DAG_PB: { -+ ipfs::PbDag b{cid, bytes}; -+ if (b.valid()) { -+ result = fromBlock(b); -+ } else { -+ std::ostringstream hex; -+ for (auto byt : bytes) { -+ hex << ' ' << std::hex -+ << static_cast(static_cast(byt)); -+ } -+ LOG(ERROR) -+ << "Have a response that did not parse as a valid block, cid: " -+ << cid.to_string() << " contents: " << bytes.size() -+ << " bytes = " << hex.str(); -+ } -+ } break; -+ case MultiCodec::INVALID: -+ case MultiCodec::IDENTITY: -+ case MultiCodec::LIBP2P_KEY: -+ default: -+ LOG(ERROR) << "Response for unhandled CID Codec: " -+ << GetName(cid.codec()); -+ } -+ if (result) { -+ result->set_api(api); -+ } -+ return result; -+} -+std::shared_ptr Node::fromBlock(ipfs::PbDag const& block) { -+ std::shared_ptr result; -+ switch (block.type()) { -+ case PbDag::Type::FileChunk: -+ return std::make_shared(block.chunk_data()); -+ case PbDag::Type::NonFs: -+ return std::make_shared(block.unparsed()); -+ case PbDag::Type::Symlink: -+ return std::make_shared(block.chunk_data()); -+ case PbDag::Type::Directory: -+ result = std::make_shared(); -+ break; -+ case PbDag::Type::File: -+ case PbDag::Type::Raw: -+ result = std::make_shared(); -+ break; -+ case PbDag::Type::HAMTShard: -+ if (block.fsdata().has_fanout()) { -+ result = std::make_shared(block.fsdata().fanout()); -+ } else { -+ result = std::make_shared(); -+ } -+ break; -+ case PbDag::Type::Metadata: -+ LOG(ERROR) << "Metadata blocks unhandled."; -+ return result; -+ case PbDag::Type::Invalid: -+ LOG(ERROR) << "Invalid block."; -+ return result; -+ default: -+ LOG(FATAL) << "TODO " << static_cast(block.type()); -+ } -+ auto add_link = [&result](auto& n, auto c) { -+ result->links_.emplace_back(n, c); -+ return true; -+ }; -+ block.List(add_link); -+ return result; -+} -+ -+auto Node::fromIpnsRecord(ipfs::ValidatedIpns const& v) -> NodePtr { -+ return std::make_shared(v.value); -+} -+ -+std::shared_ptr Node::deroot() { -+ return shared_from_this(); -+} -+std::shared_ptr Node::rooted() { -+ return std::make_shared(shared_from_this()); -+} -+auto Node::as_hamt() -> DirShard* { -+ return nullptr; -+} -+void Node::set_api(std::shared_ptr api) { -+ api_ = api; -+} -+auto Node::resolve(SlashDelimited initial_path, BlockLookup blu) -+ -> ResolveResult { -+ ResolutionState state; -+ state.resolved_path_components = ""; -+ state.unresolved_path = initial_path; -+ state.get_available_block = blu; -+ return resolve(state); -+} -+auto Node::CallChild(ipfs::ipld::ResolutionState& state) -> ResolveResult { -+ return CallChild(state, state.NextComponent(api_.get())); -+} -+auto Node::CallChild(ipfs::ipld::ResolutionState& state, -+ std::string_view link_key, -+ std::string_view block_key) -> ResolveResult { -+ auto child = FindChild(link_key); -+ if (!child) { -+ links_.emplace_back(link_key, Link{std::string{block_key}, {}}); -+ } -+ return CallChild(state, link_key); -+} -+auto Node::CallChild(ResolutionState& state, std::string_view link_key) -+ -> ResolveResult { -+ auto* child = FindChild(link_key); -+ if (!child) { -+ return ProvenAbsent{}; -+ } -+ auto& node = child->node; -+ if (!node) { -+ node = state.GetBlock(child->cid); -+ } -+ if (node) { -+ Descend(state); -+ return node->resolve(state); -+ } else { -+ std::string needed{"/ipfs/"}; -+ needed.append(child->cid); -+ auto more = state.unresolved_path.to_view(); -+ if (more.size()) { -+ if (more.front() != '/') { -+ needed.push_back('/'); -+ } -+ needed.append(more); -+ } -+ return MoreDataNeeded{needed}; -+ } -+} -+auto Node::CallChild(ResolutionState& state, -+ std::function gen_child) -+ -> ResolveResult { -+ auto link_key = state.NextComponent(api_.get()); -+ auto child = FindChild(link_key); -+ if (!child) { -+ links_.emplace_back(link_key, Link{{}, {}}); -+ child = &links_.back().second; -+ } -+ auto& node = child->node; -+ if (!node) { -+ node = gen_child(link_key); -+ if (!node) { -+ return ProvenAbsent{}; -+ } -+ } -+ Descend(state); -+ return node->resolve(state); -+} -+auto Node::FindChild(std::string_view link_key) -> Link* { -+ for (auto& [name, link] : links_) { -+ if (name == link_key) { -+ return &link; -+ } -+ } -+ return nullptr; -+} -+void Node::Descend(ResolutionState& state) { -+ auto next = state.unresolved_path.pop(); -+ if (next.empty()) { -+ return; -+ } -+ if (!state.resolved_path_components.ends_with('/')) { -+ state.resolved_path_components.push_back('/'); -+ } -+ state.resolved_path_components.append(next); -+} -+ -+std::ostream& operator<<(std::ostream& s, ipfs::ipld::PathChange const& c) { -+ return s << "PathChange{" << c.new_path << '}'; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/directory_shard.cc b/third_party/ipfs_client/src/ipfs_client/ipld/directory_shard.cc -new file mode 100644 -index 0000000000000..7839e62b66247 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/directory_shard.cc -@@ -0,0 +1,101 @@ -+#include "directory_shard.h" -+ -+#include "log_macros.h" -+ -+#include -+#include -+ -+#include -+ -+#include -+#include -+#include -+ -+using namespace std::literals; -+ -+using Self = ipfs::ipld::DirShard; -+ -+auto Self::resolve(ResolutionState& parms) -> ResolveResult { -+ if (parms.IsFinalComponent()) { -+ auto index_parm = parms.WithPath("index.html"sv); -+ auto result = resolve(index_parm); -+ // TODO generate index.html if not present -+ auto resp = std::get_if(&result); -+ if (resp) { -+ resp->mime_ = "text/html"; -+ } -+ return result; -+ } -+ std::string name{parms.NextComponent(api_.get())}; -+ auto hash = hexhash(name); -+ return resolve_internal(hash.begin(), hash.end(), name, parms); -+} -+auto Self::resolve_internal(ipfs::ipld::DirShard::HashIter hash_b, -+ ipfs::ipld::DirShard::HashIter hash_e, -+ std::string_view human_name, -+ ResolutionState& parms) -> ResolveResult { -+ auto hash_chunk = hash_b == hash_e ? std::string{} : *hash_b; -+ for (auto& [name, link] : links_) { -+ if (!starts_with(name, hash_chunk)) { -+ continue; -+ } -+ if (ends_with(name, human_name)) { -+ VLOG(2) << "Found " << human_name << ", leaving HAMT sharded directory " -+ << name << "->" << link.cid; -+ return CallChild(parms, name); -+ } -+ auto node = parms.GetBlock(link.cid); -+ if (!node) { -+ // Unfortunately we can't really append more path and do a full Car -+ // request -+ // The gateway would hash whatever we gave it and compare it to a -+ // partially-consumed hash -+ return MoreDataNeeded{{"/ipfs/" + link.cid}}; -+ } -+ auto downcast = node->as_hamt(); -+ if (downcast) { -+ if (hash_b == hash_e) { -+ LOG(ERROR) << "Ran out of hash bits."; -+ return ProvenAbsent{}; -+ } -+ VLOG(2) << "Found hash chunk, continuing to next level of HAMT sharded " -+ "directory " -+ << name << "->" << link.cid; -+ return downcast->resolve_internal(std::next(hash_b), hash_e, human_name, -+ parms); -+ } else { -+ return ProvenAbsent{}; -+ } -+ } -+ return ProvenAbsent{}; -+} -+std::vector Self::hexhash(std::string_view path_element) const { -+ auto hex_width = 0U; -+ for (auto x = fanout_; (x >>= 4); ++hex_width) -+ ; -+ std::array digest = {0U, 0U}; -+ MurmurHash3_x64_128(path_element.data(), path_element.size(), 0, -+ digest.data()); -+ std::vector result; -+ for (auto d : digest) { -+ auto hash_bits = htobe64(d); -+ while (hash_bits) { -+ // 2. Pop the log2(fanout_) lowest bits from the path component hash -+ // digest,... -+ auto popped = hash_bits % fanout_; -+ hash_bits /= fanout_; -+ std::ostringstream oss; -+ // ... then hex encode (using 0-F) using little endian those bits ... -+ oss << std::setfill('0') << std::setw(hex_width) << std::uppercase -+ << std::hex << popped; -+ result.push_back(oss.str()); -+ } -+ } -+ return result; -+} -+ -+Self::DirShard(std::uint64_t fanout) : fanout_{fanout} {} -+Self::~DirShard() {} -+Self* Self::as_hamt() { -+ return this; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/directory_shard.h b/third_party/ipfs_client/src/ipfs_client/ipld/directory_shard.h -new file mode 100644 -index 0000000000000..c1cb811355922 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/directory_shard.h -@@ -0,0 +1,26 @@ -+#ifndef IPFS_DIRECTORY_SHARD_H_ -+#define IPFS_DIRECTORY_SHARD_H_ 1 -+ -+#include -+ -+namespace ipfs::ipld { -+class DirShard : public DagNode { -+ std::uint64_t const fanout_; -+ -+ ResolveResult resolve(ResolutionState&) override; -+ DirShard* as_hamt() override; -+ -+ std::vector hexhash(std::string_view path_element) const; -+ using HashIter = std::vector::const_iterator; -+ ResolveResult resolve_internal(HashIter, -+ HashIter, -+ std::string_view, -+ ResolutionState&); -+ -+ public: -+ explicit DirShard(std::uint64_t fanout = 256UL); -+ virtual ~DirShard() noexcept; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_DIRECTORY_SHARD_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/ipns_name.cc b/third_party/ipfs_client/src/ipfs_client/ipld/ipns_name.cc -new file mode 100644 -index 0000000000000..f38200923f49f ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/ipns_name.cc -@@ -0,0 +1,25 @@ -+#include "ipns_name.h" -+ -+#include "log_macros.h" -+ -+using Self = ipfs::ipld::IpnsName; -+ -+Self::IpnsName(std::string_view target_abs_path) -+ : target_path_{target_abs_path} {} -+ -+auto Self::resolve(ResolutionState& params) -> ResolveResult { -+ // Can't use PathChange, as the target is truly absolute (rootless) -+ SlashDelimited t{target_path_}; -+ t.pop(); // Discard namespace, though realistically it's going to be ipfs -+ // basically all the time -+ auto name = t.pop(); -+ if (t) { -+ LOG(WARNING) << "Odd case: name points at /ns/root/MORE/PATH (" -+ << target_path_ << "): " << params.MyPath(); -+ auto path = t.to_string() + "/" + params.PathToResolve().to_string(); -+ auto altered = params.WithPath(path); -+ return CallChild(altered, "", name); -+ } else { -+ return CallChild(params, "", name); -+ } -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/ipns_name.h b/third_party/ipfs_client/src/ipfs_client/ipld/ipns_name.h -new file mode 100644 -index 0000000000000..8b50d6e86e397 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/ipns_name.h -@@ -0,0 +1,18 @@ -+#ifndef IPFS_IPLD_IPNS_NAME_H_ -+#define IPFS_IPLD_IPNS_NAME_H_ -+ -+#include "ipfs_client/ipld/dag_node.h" -+ -+namespace ipfs::ipld { -+class IpnsName : public DagNode { -+ std::string const target_path_; -+ -+ ResolveResult resolve(ResolutionState& params) override; -+ -+ public: -+ IpnsName(std::string_view target_abs_path); -+ virtual ~IpnsName() noexcept {} -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_IPLD_IPNS_NAME_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/link.cc b/third_party/ipfs_client/src/ipfs_client/ipld/link.cc -new file mode 100644 -index 0000000000000..f9dad98e58840 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/link.cc -@@ -0,0 +1,6 @@ -+#include "ipfs_client/ipld/link.h" -+ -+using Self = ipfs::ipld::Link; -+ -+Self::Link(std::string cid_s) : cid{cid_s} {} -+Self::Link(std::string s, std::shared_ptr n) : cid{s}, node{n} {} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/resolution_state.cc b/third_party/ipfs_client/src/ipfs_client/ipld/resolution_state.cc -new file mode 100644 -index 0000000000000..7b51513d83d6f ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/resolution_state.cc -@@ -0,0 +1,36 @@ -+#include -+ -+#include -+ -+using Self = ipfs::ipld::ResolutionState; -+ -+bool Self::IsFinalComponent() const { -+ return !unresolved_path; -+} -+auto Self::PathToResolve() const -> SlashDelimited { -+ return unresolved_path; -+} -+auto Self::MyPath() const -> SlashDelimited { -+ return SlashDelimited{resolved_path_components}; -+} -+std::string Self::NextComponent(ContextApi const* api) const { -+ auto copy = unresolved_path; -+ if (api) { -+ return api->UnescapeUrlComponent(copy.pop()); -+ } else { -+ return std::string{copy.pop()}; -+ } -+} -+auto Self::GetBlock(std::string const& block_key) const -> NodePtr { -+ return get_available_block(block_key); -+} -+Self Self::WithPath(std::string_view p) const { -+ auto rv = *this; -+ rv.unresolved_path = SlashDelimited{p}; -+ return rv; -+} -+auto Self::RestartResolvedPath() const -> ResolutionState { -+ auto rv = *this; -+ rv.resolved_path_components.clear(); -+ return rv; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/root.cc b/third_party/ipfs_client/src/ipfs_client/ipld/root.cc -new file mode 100644 -index 0000000000000..fd5af1b2891b2 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/root.cc -@@ -0,0 +1,90 @@ -+#include "root.h" -+ -+#include "log_macros.h" -+ -+using namespace std::literals; -+ -+using Self = ipfs::ipld::Root; -+using Ptr = std::shared_ptr; -+ -+Self::Root(std::shared_ptr under) { -+ links_.push_back({{}, Link{{}, under}}); -+} -+Self::~Root() {} -+ -+Ptr Self::deroot() { -+ return links_.at(0).second.node; -+} -+Ptr Self::rooted() { -+ return shared_from_this(); -+} -+ -+auto Self::resolve(ResolutionState& params) -> ResolveResult { -+ auto location = params.PathToResolve().to_string(); -+ auto result = deroot()->resolve(params); -+ if (auto pc = std::get_if(&result)) { -+ auto lower = params.WithPath(pc->new_path); -+ result = resolve(lower); -+ location.assign(lower.MyPath().to_view()); -+ } else if (std::get_if(&result)) { -+ if (params.NextComponent(api_.get()) == "_redirects") { -+ return result; -+ } -+ if (!redirects_.has_value()) { -+ auto redirects_path = params.WithPath("_redirects"); -+ result = resolve(redirects_path); -+ auto redirect_resp = std::get_if(&result); -+ if (redirect_resp && redirect_resp->status_ == 200) { -+ redirects_ = redirects::File(redirect_resp->body_); -+ } else { -+ // Either this is ProvenAbsent, in which case this will be interpreted -+ // as the original ProvenAbsent Or it's MoreDataNeeded but for -+ // _redirects, which is what we need now -+ return result; -+ } -+ } -+ if (redirects_.has_value() && redirects_.value().valid()) { -+ Response* resp = nullptr; -+ auto status = redirects_.value().rewrite(location); -+ if (location.find("://") < location.size()) { -+ LOG(INFO) << "_redirects file sent us to a whole URL, scheme-and-all: " -+ << location << " status=" << status; -+ return Response{"", status, "", location}; -+ } -+ auto lower_parm = params.WithPath(location).RestartResolvedPath(); -+ switch (status / 100) { -+ case 0: // no rewrites available -+ break; -+ case 2: -+ result = deroot()->resolve(lower_parm); -+ location.assign(lower_parm.MyPath().to_view()); -+ break; -+ case 3: -+ // Let the redirect happen -+ return Response{"", status, "", location}; -+ case 4: { -+ result = deroot()->resolve(lower_parm); -+ location.assign(lower_parm.MyPath().to_view()); -+ if (std::get_if(&result)) { -+ return Response{"", 500, "", location}; -+ } -+ resp = std::get_if(&result); -+ if (resp) { -+ resp->status_ = status; -+ return *resp; -+ } -+ break; // MoreDataNeeded to fetch e.g. custom 404 page -+ } -+ default: -+ LOG(ERROR) << "Unsupported status came back from _redirects file: " -+ << status; -+ return ProvenAbsent{}; -+ } -+ } -+ } -+ auto resp = std::get_if(&result); -+ if (resp && resp->location_.empty()) { -+ resp->location_ = location; -+ } -+ return result; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/root.h b/third_party/ipfs_client/src/ipfs_client/ipld/root.h -new file mode 100644 -index 0000000000000..b57951b42f7f1 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/root.h -@@ -0,0 +1,23 @@ -+#ifndef IPFS_ROOT_H_ -+#define IPFS_ROOT_H_ -+ -+#include -+#include -+ -+#include -+ -+namespace ipfs::ipld { -+class Root : public DagNode { -+ std::optional redirects_; -+ -+ ResolveResult resolve(ResolutionState& params) override; -+ std::shared_ptr rooted() override; -+ std::shared_ptr deroot() override; -+ -+ public: -+ Root(std::shared_ptr); -+ virtual ~Root() noexcept; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_ROOT_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/small_directory.cc b/third_party/ipfs_client/src/ipfs_client/ipld/small_directory.cc -new file mode 100644 -index 0000000000000..b8613663932ca ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/small_directory.cc -@@ -0,0 +1,35 @@ -+#include "small_directory.h" -+ -+#include -+#include "ipfs_client/generated_directory_listing.h" -+#include "ipfs_client/path2url.h" -+ -+#include "log_macros.h" -+ -+#include -+ -+using namespace std::literals; -+ -+using Self = ipfs::ipld::SmallDirectory; -+ -+auto Self::resolve(ResolutionState& params) -> ResolveResult { -+ if (params.IsFinalComponent()) { -+ LOG(INFO) << "Directory listing requested for " << params.MyPath(); -+ auto result = CallChild(params, "index.html"); -+ if (auto resp = std::get_if(&result)) { -+ resp->mime_ = "text/html"; -+ } -+ if (!std::get_if(&result)) { -+ return result; -+ } -+ auto dir_path = params.MyPath().to_view(); -+ GeneratedDirectoryListing index_html{dir_path}; -+ for (auto& [name, link] : links_) { -+ index_html.AddEntry(name); -+ } -+ return Response{"text/html", 200, index_html.Finish(), ""}; -+ } -+ return CallChild(params); -+} -+ -+Self::~SmallDirectory() {} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/small_directory.h b/third_party/ipfs_client/src/ipfs_client/ipld/small_directory.h -new file mode 100644 -index 0000000000000..a076122c5041f ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/small_directory.h -@@ -0,0 +1,17 @@ -+#ifndef IPFS_UNIXFS_DIRECTORY_H_ -+#define IPFS_UNIXFS_DIRECTORY_H_ -+ -+#include "ipfs_client/ipld/link.h" -+ -+#include -+ -+namespace ipfs::ipld { -+class SmallDirectory : public DagNode { -+ ResolveResult resolve(ResolutionState&) override; -+ -+ public: -+ virtual ~SmallDirectory() noexcept; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_UNIXFS_DIRECTORY_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/symlink.cc b/third_party/ipfs_client/src/ipfs_client/ipld/symlink.cc -new file mode 100644 -index 0000000000000..b35725ad7f703 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/symlink.cc -@@ -0,0 +1,37 @@ -+#include "symlink.h" -+ -+#include "log_macros.h" -+ -+using Self = ipfs::ipld::Symlink; -+ -+Self::Symlink(std::string target) : target_{target} {} -+ -+Self::~Symlink() {} -+ -+auto Self::resolve(ResolutionState& params) -> ResolveResult { -+ std::string result; -+ if (!is_absolute()) { -+ auto left_path = params.MyPath(); -+ left_path.pop_n(2); // Returning a path relative to content root. -+ left_path.pop_back(); // Because the final component refers to this -+ // symlink, which is getting replaced with target -+ result.assign(left_path.to_view()); -+ } -+ result.append("/").append(target_); -+ if (!params.IsFinalComponent()) { -+ result.append("/").append(params.PathToResolve().to_string()); -+ } -+ std::size_t i; -+ while ((i = result.find("//")) != std::string::npos) { -+ result.erase(i, 1); -+ } -+ if (result.ends_with('/')) { -+ result.resize(result.size() - 1); -+ } -+ LOG(INFO) << "symlink: '" << params.MyPath() << "' -> '" << result << "'."; -+ return PathChange{result}; -+} -+ -+bool Self::is_absolute() const { -+ return target_.at(0) == '/'; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/symlink.h b/third_party/ipfs_client/src/ipfs_client/ipld/symlink.h -new file mode 100644 -index 0000000000000..937f0d248c25d ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/symlink.h -@@ -0,0 +1,20 @@ -+#ifndef IPFS_SYMLINK_H_ -+#define IPFS_SYMLINK_H_ -+ -+#include -+ -+namespace ipfs::ipld { -+class Symlink : public DagNode { -+ std::string const target_; -+ -+ ResolveResult resolve(ResolutionState& params) override; -+ -+ bool is_absolute() const; -+ -+ public: -+ Symlink(std::string target); -+ ~Symlink() noexcept override; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_SYMLINK_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/unixfs_file.cc b/third_party/ipfs_client/src/ipfs_client/ipld/unixfs_file.cc -new file mode 100644 -index 0000000000000..784a1df367152 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/unixfs_file.cc -@@ -0,0 +1,50 @@ -+#include "unixfs_file.h" -+ -+#include "log_macros.h" -+ -+using namespace std::literals; -+ -+using Self = ipfs::ipld::UnixfsFile; -+ -+auto Self::resolve(ResolutionState& params) -> ResolveResult { -+ if (!params.IsFinalComponent()) { -+ LOG(ERROR) << "Can't path through a file, (at " << params.MyPath() -+ << ") but given the path " << params.PathToResolve(); -+ return ProvenAbsent{}; -+ } -+ std::vector missing; -+ std::string body; -+ for (auto& child : links_) { -+ auto& link = child.second; -+ if (!link.node) { -+ link.node = params.GetBlock(link.cid); -+ } -+ if (link.node) { -+ auto recurse = link.node->resolve(params); -+ auto mdn = std::get_if(&recurse); -+ if (mdn) { -+ missing.insert(missing.end(), mdn->ipfs_abs_paths_.begin(), -+ mdn->ipfs_abs_paths_.end()); -+ continue; -+ } -+ if (missing.empty()) { -+ body.append(std::get(recurse).body_); -+ } -+ } else { -+ missing.push_back("/ipfs/" + link.cid); -+ } -+ } -+ if (missing.empty()) { -+ return Response{ -+ "", -+ 200, -+ body, -+ params.MyPath().to_string(), -+ }; -+ } -+ auto result = MoreDataNeeded{missing}; -+ result.insist_on_car = true; -+ return result; -+} -+ -+Self::~UnixfsFile() {} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipld/unixfs_file.h b/third_party/ipfs_client/src/ipfs_client/ipld/unixfs_file.h -new file mode 100644 -index 0000000000000..3447e949d330e ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipld/unixfs_file.h -@@ -0,0 +1,15 @@ -+#ifndef IPFS_UNIXFS_FILE_H_ -+#define IPFS_UNIXFS_FILE_H_ -+ -+#include -+ -+namespace ipfs::ipld { -+class UnixfsFile : public DagNode { -+ ResolveResult resolve(ResolutionState&) override; -+ -+ public: -+ virtual ~UnixfsFile() noexcept; -+}; -+} // namespace ipfs::ipld -+ -+#endif // IPFS_UNIXFS_FILE_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/ipns_names.cc b/third_party/ipfs_client/src/ipfs_client/ipns_names.cc -new file mode 100644 -index 0000000000000..6eccfaa7dc51b ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipns_names.cc -@@ -0,0 +1,101 @@ -+#include -+ -+#include -+ -+#include "log_macros.h" -+ -+using Self = ipfs::IpnsNames; -+ -+void Self::NoSuchName(std::string const& name) { -+ names_[name]; // If it already exists, leave it. -+} -+void Self::AssignName(std::string const& name, ValidatedIpns entry) { -+ auto& res = entry.value; -+ if (res.size() && res.front() == '/') { -+ res.erase(0, 1); -+ } -+ auto endofcid = res.find_first_of("/?#", 6); -+ using namespace libp2p::multi; -+ auto cid_str = res.substr(5, endofcid); -+ LOG(INFO) << "IPNS points to CID " << cid_str; -+ auto cid = Cid(cid_str); -+ if (cid.valid()) { -+ auto desensitized = res.substr(0, 5); -+ desensitized.append(cid_str); -+ if (endofcid < res.size()) { -+ auto extra = res.substr(endofcid); -+ LOG(INFO) << name << " resolution contains oddity '" << extra; -+ desensitized.append(extra); -+ } -+ LOG(INFO) << name << " now resolves to (desensitized)" << desensitized; -+ entry.value = desensitized; -+ } else { -+ LOG(INFO) << name << " now resolves to (extra level)" << res; -+ } -+ auto it = names_.find(name); -+ if (it == names_.end()) { -+ names_.emplace(name, std::move(entry)); -+ } else if (it->second.sequence < entry.sequence) { -+ LOG(INFO) << "Updating IPNS record for " << name << " from sequence " -+ << it->second.sequence << " where it pointed to " -+ << it->second.value << " to sequence " << entry.sequence -+ << " where it points to " << entry.value; -+ it->second = std::move(entry); -+ } else { -+ LOG(INFO) << "Discarding redundant IPNS record for " << name; -+ } -+} -+void Self::AssignDnsLink(std::string const& name, std::string_view target) { -+ ValidatedIpns v; -+ v.value.assign(target); -+ auto t = std::time(nullptr); -+ v.use_until = v.cache_until = t + 300; -+ AssignName(name, std::move(v)); -+} -+ -+std::string_view Self::NameResolvedTo(std::string_view original_name) const { -+ std::string name{original_name}; -+ std::string_view prev = ""; -+ auto trailer = names_.end(); -+ auto trail_step = false; -+ auto now = std::time(nullptr); -+ while (true) { -+ auto it = names_.find(name); -+ if (names_.end() == it) { -+ LOG(INFO) << "Host not in immediate access map: " << name << " (" -+ << std::string{original_name} << ')'; -+ return prev; -+ } else if (it == trailer) { -+ LOG(ERROR) << "Host cycle found in IPNS: " << std::string{original_name} -+ << ' ' << name; -+ return ""; -+ } -+ auto& target = it->second.value; -+ if (target.empty()) { -+ return kNoSuchName; -+ } -+ if (target.at(2) == 'f') { -+ return target; -+ } -+ if (it->second.use_until < now) { -+ return prev; -+ } -+ if (trail_step) { -+ if (trailer == names_.end()) { -+ trailer = names_.find(name); -+ } else { -+ trailer = names_.find(trailer->second.value.substr(5)); -+ } -+ } -+ trail_step = !trail_step; -+ prev = it->second.value; -+ name.assign(prev, 5); -+ } -+} -+auto Self::Entry(std::string const& name) -> ValidatedIpns const* { -+ auto it = names_.find(name); -+ return it == names_.end() ? nullptr : &(it->second); -+} -+ -+Self::IpnsNames() {} -+Self::~IpnsNames() {} -diff --git a/third_party/ipfs_client/src/ipfs_client/ipns_record.cc b/third_party/ipfs_client/src/ipfs_client/ipns_record.cc -new file mode 100644 -index 0000000000000..86be780f9066f ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/ipns_record.cc -@@ -0,0 +1,244 @@ -+#include -+ -+#include -+#include -+#include -+ -+#include "log_macros.h" -+ -+#include -+#include -+ -+#if __has_include() -+#include -+#else -+#include "ipfs_client/ipns_record.pb.h" -+#endif -+ -+namespace { -+bool matches(ipfs::MultiHash const& hash, -+ ipfs::ByteView pubkey_bytes, -+ ipfs::ContextApi& api) { -+ auto result = api.Hash(hash.type(), pubkey_bytes); -+ if (!result.has_value()) { -+ return false; -+ } -+ return std::equal(result->begin(), result->end(), hash.digest().begin(), -+ hash.digest().end()); -+} -+} // namespace -+ -+namespace { -+void assign(std::string& out, -+ ipfs::DagCborValue& top, -+ std::string_view key) { -+ auto p = top.at(key); -+ if (!p) { -+ out.assign("Key '").append(key).append("' not present in IPNS CBOR!"); -+ } else { -+ // YEP! as_bytes() . There are only 2 string values here, they are logically -+ // text, but they are defined in the spec to be bytes. -+ auto o = p->as_bytes(); -+ if (o.has_value()) { -+ auto chars = reinterpret_cast(o.value().data()); -+ out.assign(chars, o.value().size()); -+ } else { -+ out.assign("Key '").append(key).append( -+ "' was not a string in IPNS CBOR!"); -+ } -+ } -+} -+void assign(std::uint64_t& out, -+ ipfs::DagCborValue& top, -+ std::string_view key) { -+ auto p = top.at(key); -+ if (!p) { -+ LOG(ERROR) << "Key '" << key << "' is not present in IPNS CBOR!"; -+ out = std::numeric_limits::max(); -+ } else { -+ auto o = p->as_unsigned(); -+ if (o.has_value()) { -+ out = o.value(); -+ } else { -+ LOG(ERROR) << "Key '" << key -+ << "' is not an unsigned integer in IPNS CBOR!"; -+ out = std::numeric_limits::max(); -+ } -+ } -+} -+} // namespace -+ -+auto ipfs::ValidateIpnsRecord(ipfs::ByteView top_level_bytes, -+ Cid const& name, -+ ContextApi& api) -> std::optional { -+ DCHECK_EQ(name.codec(), MultiCodec::LIBP2P_KEY); -+ if (name.codec() != MultiCodec::LIBP2P_KEY) { -+ return {}; -+ } -+ // https://github.com/ipfs/specs/blob/main/ipns/IPNS.md#record-verification -+ -+ // Before parsing the protobuf, confirm that the serialized IpnsEntry bytes -+ // sum to less than or equal to the size limit. -+ if (top_level_bytes.size() > MAX_IPNS_PB_SERIALIZED_SIZE) { -+ LOG(ERROR) << "IPNS record too large: " << top_level_bytes.size(); -+ return {}; -+ } -+ -+ ipfs::ipns::IpnsEntry entry; -+ if (!entry.ParseFromArray(top_level_bytes.data(), top_level_bytes.size())) { -+ LOG(ERROR) << "Failed to parse top-level bytes as a protobuf"; -+ return {}; -+ } -+ -+ // Confirm IpnsEntry.signatureV2 and IpnsEntry.data are present and are not -+ // empty -+ if (!entry.has_signaturev2()) { -+ LOG(ERROR) << "IPNS record contains no .signatureV2!"; -+ return {}; -+ } -+ if (!entry.has_data() || entry.data().empty()) { -+ LOG(ERROR) << "IPNS record has no .data"; -+ return {}; -+ } -+ -+ // The only supported value is 0, which indicates the validity field contains -+ // the expiration date after which the IPNS record becomes invalid. -+ DCHECK_EQ(entry.validitytype(), 0); -+ -+ auto parsed = -+ api.ParseCbor({reinterpret_cast(entry.data().data()), -+ entry.data().size()}); -+ if (!parsed) { -+ LOG(ERROR) << "CBOR parsing failed."; -+ return {}; -+ } -+ IpnsCborEntry result; -+ assign(result.value, *parsed, "Value"); -+ if (entry.has_value() && result.value != entry.value()) { -+ LOG(ERROR) << "Mismatch on Value field in IPNS record... CBOR(v2): '" -+ << result.value << "' but PB(v1): '" << entry.value() -+ << "' : " << parsed->html(); -+ return {}; -+ } -+ ipfs::ByteView public_key; -+ if (entry.has_pubkey()) { -+ public_key = ipfs::ByteView{ -+ reinterpret_cast(entry.pubkey().data()), -+ entry.pubkey().size()}; -+ if (!matches(name.multi_hash(), public_key, api)) { -+ LOG(ERROR) << "Given IPNS record contains a pubkey that does not match " -+ "the hash from the IPNS name that fetched it!"; -+ return {}; -+ } -+ } else if (name.hash_type() == HashType::IDENTITY) { -+ public_key = name.hash(); -+ } else { -+ LOG(ERROR) << "IPNS record contains no public key, and the IPNS name " -+ << name.to_string() -+ << " is a true hash, not identity. Validation impossible."; -+ return {}; -+ } -+ ipfs::ipns::PublicKey pk; -+ auto* pkbp = reinterpret_cast(public_key.data()); -+ if (!pk.ParseFromArray(pkbp, public_key.size())) { -+ LOG(ERROR) << "Failed to parse public key bytes"; -+ return {}; -+ } -+ LOG(INFO) << "Record contains a public key of type " << pk.type() -+ << " and points to " << entry.value(); -+ auto& signature_str = entry.signaturev2(); -+ ByteView signature{reinterpret_cast(signature_str.data()), -+ signature_str.size()}; -+ // https://specs.ipfs.tech/ipns/ipns-record/#record-verification -+ // Create bytes for signature verification by concatenating -+ // ipto_hex(ns-signature:// prefix (bytes in hex: -+ // 69706e732d7369676e61747572653a) with raw CBOR bytes from IpnsEntry.data -+ auto bytes_str = entry.data(); -+ bytes_str.insert( -+ 0, "\x69\x70\x6e\x73\x2d\x73\x69\x67\x6e\x61\x74\x75\x72\x65\x3a"); -+ ByteView bytes{reinterpret_cast(bytes_str.data()), -+ bytes_str.size()}; -+ ByteView key_bytes{reinterpret_cast(pk.data().data()), -+ pk.data().size()}; -+ if (!api.VerifyKeySignature(static_cast(pk.type()), signature, -+ bytes, key_bytes)) { -+ LOG(ERROR) << "Verification failed!!"; -+ return {}; -+ } -+ // TODO check expiration date -+ if (entry.has_value() && entry.value() != result.value) { -+ LOG(ERROR) << "IPNS " << name.to_string() << " has different values for V1(" -+ << entry.value() << ") and V2(" << result.value << ')'; -+ return {}; -+ } -+ assign(result.validity, *parsed, "Validity"); -+ if (entry.has_validity() && entry.validity() != result.validity) { -+ LOG(ERROR) << "IPNS " << name.to_string() -+ << " has different validity for V1(" << entry.validity() -+ << ") and V2(" << result.validity << ')'; -+ return {}; -+ } -+ assign(result.validityType, *parsed, "ValidityType"); -+ if (entry.has_validitytype() && -+ entry.validitytype() != static_cast(result.validityType)) { -+ LOG(ERROR) << "IPNS " << name.to_string() -+ << " has different validity types for V1(" -+ << entry.validitytype() << ") and V2(" << result.validityType -+ << ')'; -+ return {}; -+ } -+ assign(result.sequence, *parsed, "Sequence"); -+ if (entry.has_sequence() && entry.sequence() != result.sequence) { -+ LOG(ERROR) << "IPNS " << name.to_string() -+ << " has different validity types for V1(" << entry.sequence() -+ << ") and V2(" << result.sequence << ')'; -+ return {}; -+ } -+ assign(result.ttl, *parsed, "TTL"); -+ if (entry.has_ttl() && entry.ttl() != result.ttl) { -+ LOG(ERROR) << "IPNS " << name.to_string() -+ << " has different validity types for V1(" << entry.ttl() -+ << ") and V2(" << result.ttl << ')'; -+ return {}; -+ } -+ LOG(INFO) << "IPNS record verification passes for " << name.to_string() -+ << " sequence: " << result.sequence << " points at " -+ << result.value; -+ return result; -+} -+ -+ipfs::ValidatedIpns::ValidatedIpns() = default; -+ipfs::ValidatedIpns::ValidatedIpns(ValidatedIpns&&) = default; -+ipfs::ValidatedIpns::ValidatedIpns(ValidatedIpns const&) = default; -+auto ipfs::ValidatedIpns::operator=(ValidatedIpns const&) -+ -> ValidatedIpns& = default; -+ipfs::ValidatedIpns::ValidatedIpns(IpnsCborEntry const& e) -+ : value{e.value}, sequence{e.sequence} { -+ std::istringstream ss{e.validity}; -+ std::tm t = {}; -+ ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%S"); -+ long ttl = (e.ttl / 1'000'000'000UL) + 1; -+#ifdef _MSC_VER -+ use_until = _mkgmtime(&t); -+#else -+ use_until = timegm(&t); -+#endif -+ cache_until = std::time(nullptr) + ttl; -+} -+ -+std::string ipfs::ValidatedIpns::Serialize() const { -+ DCHECK_EQ(value.find(' '), std::string::npos); -+ DCHECK_EQ(gateway_source.find(' '), std::string::npos); -+ std::ostringstream ss; -+ ss << std::hex << sequence << ' ' << use_until << ' ' << cache_until << ' ' -+ << fetch_time << ' ' << resolution_ms << ' ' << value << ' ' -+ << gateway_source; -+ return ss.str(); -+} -+auto ipfs::ValidatedIpns::Deserialize(std::string s) -> ValidatedIpns { -+ std::istringstream ss(s); -+ ValidatedIpns e; -+ ss >> std::hex >> e.sequence >> e.use_until >> e.cache_until >> -+ e.fetch_time >> e.resolution_ms >> e.value >> e.gateway_source; -+ return e; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/logger.cc b/third_party/ipfs_client/src/ipfs_client/logger.cc -new file mode 100644 -index 0000000000000..8c093bfca89aa ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/logger.cc -@@ -0,0 +1,76 @@ -+#include -+ -+#include -+ -+#include -+ -+namespace lg = ipfs::log; -+ -+namespace { -+lg::Level current_level = lg::Level::WARN; -+lg::Handler current_handler = nullptr; -+ -+void CheckLevel(google::protobuf::LogLevel lv, -+ char const* f, -+ int l, -+ std::string const& m) { -+ auto lev = static_cast(lv); -+ if (lev < static_cast(current_level)) { -+ return; -+ } -+ if (!current_handler) { -+ return; -+ } -+ current_handler(m, f, l, static_cast(lev)); -+} -+} // namespace -+ -+void lg::SetLevel(Level lev) { -+ IsInitialized(); -+ current_level = lev; -+} -+ -+void lg::SetHandler(Handler h) { -+ current_handler = h; -+ google::protobuf::SetLogHandler(&CheckLevel); -+} -+ -+void lg::DefaultHandler(std::string const& message, -+ char const* source_file, -+ int source_line, -+ Level lev) { -+ std::clog << source_file << ':' << source_line << ": " << LevelDescriptor(lev) -+ << ": " << message << '\n'; -+ if (lev == Level::FATAL) { -+ std::abort(); -+ } -+} -+ -+std::string_view lg::LevelDescriptor(Level l) { -+ switch (l) { -+ case Level::TRACE: -+ return "trace"; -+ case Level::DEBUG: -+ return "debug"; -+ case Level::INFO: -+ return "note"; // The next 3 are gcc- & clang-inspired -+ case Level::WARN: -+ return "warning"; -+ case Level::ERROR: -+ return "error"; -+ case Level::FATAL: -+ return " ### FATAL ERROR ### "; -+ case Level::OFF: -+ return "off"; -+ default: -+ return "Unknown log level used: possible corruption?"; -+ } -+} -+ -+bool lg::IsInitialized() { -+ if (current_handler) { -+ return true; -+ } -+ SetHandler(&DefaultHandler); -+ return false; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/multi_base.cc b/third_party/ipfs_client/src/ipfs_client/multi_base.cc -new file mode 100644 -index 0000000000000..58c9a0f18d100 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/multi_base.cc -@@ -0,0 +1,133 @@ -+#include "ipfs_client/multi_base.h" -+ -+#include "bases/b16_upper.h" -+#include "bases/b32.h" -+ -+#include -+ -+#include "log_macros.h" -+ -+using namespace std::literals; -+ -+namespace imb = ipfs::mb; -+namespace { -+constexpr std::string_view UnsupportedMultibase = "unsupported-multibase"; -+ -+template -+std::string encode_adapt(ipfs::ByteView bytes) { -+ auto p = reinterpret_cast(bytes.data()); -+ typename Target::encoder target; -+ return target.process({p, bytes.size()}); -+} -+enum class EncodedCase { lower, UPPER, Sensitive }; -+template -+std::vector decode_adapt(std::string_view encoded_sv) { -+ typename Target::decoder target; -+ std::string encoded_s{encoded_sv}; -+ switch (ec) { -+ case EncodedCase::lower: -+ for (auto& c : encoded_s) { -+ if (c >= 'A' && c <= 'Z') { -+ c = c - 'A' + 'a'; -+ } -+ } -+ break; -+ case EncodedCase::UPPER: -+ for (auto& c : encoded_s) { -+ if (c >= 'a' && c <= 'z') { -+ c = c - 'a' + 'A'; -+ } -+ } -+ break; -+ case EncodedCase::Sensitive: -+ break; -+ } -+ auto s = target.process(encoded_s); -+ auto b = reinterpret_cast(s.data()); -+ auto e = b + s.size(); -+ return std::vector(b, e); -+} -+template -+constexpr imb::Codec adapt(std::string_view name) { -+ return imb::Codec{&decode_adapt, -+ &encode_adapt, name}; -+} -+} // namespace -+ -+auto imb::Codec::Get(Code c) -> Codec const* { -+ switch (c) { -+ case Code::IDENTITY: -+ return nullptr; -+ case Code::UNSUPPORTED: -+ return nullptr; -+ case Code::BASE16_LOWER: { -+ static auto b16 = -+ adapt("base16"sv); -+ return &b16; -+ } -+ case Code::BASE16_UPPER: { -+ static auto b16u = -+ adapt("base16upper"sv); -+ return &b16u; -+ } -+ case Code::BASE32_LOWER: { -+ static auto b32 = adapt("base32"sv); -+ return &b32; -+ } -+ case Code::BASE32_UPPER: { -+ static auto b32u = -+ adapt("base32upper"sv); -+ return &b32u; -+ } -+ case Code::BASE36_LOWER: { -+ static auto b36 = -+ adapt("base36"sv); -+ return &b36; -+ } -+ case Code::BASE36_UPPER: -+ return nullptr; -+ case Code::BASE58_BTC: { -+ static auto b58 = -+ adapt("base58btc"sv); -+ return &b58; -+ } -+ case Code::BASE64: -+ return nullptr; -+ } -+ return nullptr; -+} -+std::string_view imb::GetName(Code c) { -+ if (auto codec = Codec::Get(c)) { -+ return codec->name; -+ } -+ return UnsupportedMultibase; -+} -+auto imb::CodeFromPrefix(char ch) -> Code { -+ auto c = static_cast(ch); -+ return Codec::Get(c) ? Code::UNSUPPORTED : c; -+} -+auto imb::decode(std::string_view mb_str) -> std::optional> { -+ if (mb_str.empty()) { -+ return std::nullopt; -+ } -+ if (auto* codec = Codec::Get(static_cast(mb_str[0]))) { -+ return codec->decode(mb_str.substr(1)); -+ } else { -+ return std::nullopt; -+ } -+} -+std::string imb::encode(Code c, ByteView bs) { -+ if (auto codec = Codec::Get(c)) { -+ auto rv = codec->encode(bs); -+ if (rv.size() >= bs.size()) { -+ rv.insert(0UL, 1UL, static_cast(c)); -+ return rv; -+ } else { -+ LOG(ERROR) << "Error encoding into base " << codec->name; -+ } -+ } else { -+ LOG(ERROR) << "Can't encode to multibase " << static_cast(c) -+ << " because I can't find a codec??"; -+ } -+ return {}; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/multi_hash.cc b/third_party/ipfs_client/src/ipfs_client/multi_hash.cc -new file mode 100644 -index 0000000000000..20cf5b19a16c8 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/multi_hash.cc -@@ -0,0 +1,56 @@ -+#include -+ -+#include -+ -+using Self = ipfs::MultiHash; -+using VarInt = libp2p::multi::UVarint; -+ -+Self::MultiHash(ipfs::HashType t, ipfs::ByteView digest) -+ : type_{t}, hash_(digest.begin(), digest.end()) {} -+ -+Self::MultiHash(ipfs::ByteView bytes) { -+ ReadPrefix(bytes); -+} -+bool Self::ReadPrefix(ipfs::ByteView& bytes) { -+ auto i = VarInt::create(bytes); -+ if (!i) { -+ return false; -+ } -+ bytes = bytes.subspan(i->size()); -+ auto type = Validate(static_cast(i->toUInt64())); -+ i = VarInt::create(bytes); -+ if (!i) { -+ return false; -+ } -+ auto length = i->toUInt64(); -+ if (length > bytes.size()) { -+ return false; -+ } -+ bytes = bytes.subspan(i->size()); -+ hash_.assign(bytes.begin(), std::next(bytes.begin(), length)); -+ bytes = bytes.subspan(length); -+ type_ = type; -+ return true; -+} -+bool Self::valid() const { -+ return type_ != HashType::INVALID && hash_.size() > 0UL; -+} -+namespace { -+constexpr std::string_view InvalidHashTypeName; -+} -+std::string_view ipfs::GetName(HashType t) { -+ switch (t) { -+ case HashType::INVALID: -+ return InvalidHashTypeName; -+ case HashType::IDENTITY: -+ return "identity"; -+ case HashType::SHA2_256: -+ return "sha2-256"; -+ } -+ // Don't use default: -> let it fall through. We want compiler warnings about -+ // unhandled cases. -+ return InvalidHashTypeName; -+} -+auto ipfs::Validate(HashType t) -> HashType { -+ return GetName(t) == InvalidHashTypeName ? HashType::INVALID : t; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/multicodec.cc b/third_party/ipfs_client/src/ipfs_client/multicodec.cc -new file mode 100644 -index 0000000000000..68cad03ea5862 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/multicodec.cc -@@ -0,0 +1,33 @@ -+#include -+ -+using Cdc = ipfs::MultiCodec; -+ -+namespace { -+constexpr std::string_view InvalidMulticodecLabel{"invalid-multicodec"}; -+} -+ -+std::string_view ipfs::GetName(Cdc c) { -+ switch (c) { -+ case Cdc::INVALID: -+ return InvalidMulticodecLabel; -+ case Cdc::IDENTITY: -+ return "identity"; -+ case Cdc::RAW: -+ return "raw"; -+ case Cdc::DAG_PB: -+ return "dag-pb"; -+ case Cdc::DAG_CBOR: -+ return "dag-cbor"; -+ case Cdc::LIBP2P_KEY: -+ return "libp2p-key"; -+ case Cdc::DAG_JSON: -+ return "dag-json"; -+ } -+ return InvalidMulticodecLabel; -+} -+Cdc ipfs::Validate(Cdc c) { -+ if (GetName(c) == InvalidMulticodecLabel) { -+ return Cdc::INVALID; -+ } -+ return c; -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/orchestrator.cc b/third_party/ipfs_client/src/ipfs_client/orchestrator.cc -new file mode 100644 -index 0000000000000..3392ae1126e39 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/orchestrator.cc -@@ -0,0 +1,146 @@ -+#include "ipfs_client/orchestrator.h" -+ -+#include -+#include -+#include -+ -+#include "log_macros.h" -+#include "path2url.h" -+ -+using namespace std::literals; -+ -+using Self = ipfs::Orchestrator; -+ -+Self::Orchestrator(std::shared_ptr requestor, -+ std::shared_ptr api) -+ // : gw_requestor_{ga}, api_{api}, requestor_{requestor} { -+ : api_{api}, requestor_{requestor} { -+ DCHECK(requestor); -+} -+ -+void Self::build_response(std::shared_ptr req) { -+ if (!req || !req->ready_after()) { -+ return; -+ } -+ auto req_path = req->path(); -+ VLOG(2) << "build_response(" << req_path.to_string() << ')'; -+ req_path.pop(); // namespace -+ std::string affinity{req_path.pop()}; -+ auto it = dags_.find(affinity); -+ if (dags_.end() == it) { -+ if (gw_request(req, req->path(), affinity)) { -+ build_response(req); -+ } -+ } else { -+ VLOG(2) << "Requesting root " << affinity << " resolve path " -+ << req_path.to_string(); -+ auto root = it->second->rooted(); -+ if (root != it->second) { -+ it->second = root; -+ } -+ from_tree(req, root, req_path, affinity); -+ } -+} -+void Self::from_tree(std::shared_ptr req, -+ ipfs::ipld::NodePtr& node, -+ SlashDelimited relative_path, -+ std::string const& affinity) { -+ auto root = node->rooted(); -+ auto block_look_up = [this](auto& k) { -+ auto i = dags_.find(k); -+ return i == dags_.end() ? ipld::NodePtr{} : i->second; -+ }; -+ auto start = std::string{req->path().pop_n(2)}; -+ auto result = root->resolve(relative_path, block_look_up); -+ auto response = std::get_if(&result); -+ if (response) { -+ VLOG(2) << "Tree gave us a response: status=" << response->status_ -+ << " mime=" << response->mime_ -+ << " location=" << response->location_ << " body is " -+ << response->body_.size() << " bytes."; -+ if (response->mime_.empty() && !response->body_.empty()) { -+ if (response->location_.empty()) { -+ LOG(INFO) << "Request for " << req->path() -+ << " returned no location, so sniffing from request path and " -+ "body of " -+ << response->body_.size() << "B."; -+ response->mime_ = sniff(req->path(), response->body_); -+ } else { -+ std::string hit_path{req->path().pop_n(2)}; -+ if (!hit_path.ends_with('/') && -+ !(response->location_.starts_with('/'))) { -+ hit_path.push_back('/'); -+ } -+ hit_path.append(response->location_); -+ LOG(INFO) << "Request for " << req->path() << " returned a location of " -+ << response->location_ << " and a body of " -+ << response->body_.size() << " bytes, sniffing mime from " -+ << hit_path; -+ response->mime_ = sniff(SlashDelimited{hit_path}, response->body_); -+ } -+ } -+ req->finish(*response); -+ } else if (std::holds_alternative(result)) { -+ auto& np = std::get(result); -+ LOG(INFO) << "Symlink converts request to " << req->path().to_string() -+ << " into " << np.new_path -+ << ". TODO - check for infinite loops."; -+ req->new_path(np.new_path); -+ build_response(req); -+ } else if (std::get_if(&result)) { -+ req->finish(Response::IMMUTABLY_GONE); -+ } else { -+ auto& mps = std::get(result).ipfs_abs_paths_; -+ req->till_next(mps.size()); -+ if (std::any_of(mps.begin(), mps.end(), [this, &req, &affinity](auto& p) { -+ return gw_request(req, SlashDelimited{p}, affinity); -+ })) { -+ from_tree(req, node, relative_path, affinity); -+ } -+ } -+} -+bool Self::gw_request(std::shared_ptr ir, -+ ipfs::SlashDelimited path, -+ std::string const& aff) { -+ VLOG(1) << "Seeking " << path.to_string(); -+ auto req = gw::GatewayRequest::fromIpfsPath(path); -+ if (req) { -+ req->dependent = ir; -+ req->orchestrator(shared_from_this()); -+ req->affinity = aff; -+ requestor_->request(req); -+ } else { -+ LOG(ERROR) << "Failed to create a request for " << path.to_string(); -+ } -+ return false; -+} -+ -+bool Self::add_node(std::string key, ipfs::ipld::NodePtr p) { -+ if (p) { -+ if (dags_.insert({key, p}).second) { -+ p->set_api(api_); -+ } -+ return true; -+ } else { -+ LOG(INFO) << "NULL block attempted to be added for " << key; -+ } -+ return false; -+} -+ -+std::string Self::sniff(ipfs::SlashDelimited p, std::string const& body) const { -+ auto fake_url = path2url(p.to_string()); -+ auto file_name = p.peek_back(); -+ auto dot = file_name.find_last_of('.'); -+ std::string ext = ""; -+ if (dot < file_name.size()) { -+ ext.assign(file_name, dot + 1); -+ } -+ auto result = api_->MimeType(ext, body, fake_url); -+ LOG(INFO) << "Deduced mime from (ext=" << ext << " body of " << body.size() -+ << " bytes, 'url'=" << fake_url << ")=" << result; -+ return result; -+} -+ -+bool Self::has_key(std::string const& k) const { -+ return dags_.count(k); -+} -\ No newline at end of file -diff --git a/third_party/ipfs_client/src/ipfs_client/path2url.cc b/third_party/ipfs_client/src/ipfs_client/path2url.cc -new file mode 100644 -index 0000000000000..0d7cf305a47b4 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/path2url.cc -@@ -0,0 +1,16 @@ -+#include "path2url.h" -+ -+#include "log_macros.h" -+ -+std::string ipfs::path2url(std::string p) { -+ while (!p.empty() && p[0] == '/') { -+ p.erase(0UL, 1UL); -+ } -+ DCHECK_EQ(p.at(0), 'i'); -+ DCHECK_EQ(p.at(1), 'p'); -+ DCHECK(p.at(2) == 'f' || p.at(2) == 'n'); -+ DCHECK_EQ(p.at(3), 's'); -+ DCHECK_EQ(p.at(4), '/'); -+ p.insert(4, ":/"); -+ return p; -+} -diff --git a/third_party/ipfs_client/src/ipfs_client/path2url.h b/third_party/ipfs_client/src/ipfs_client/path2url.h -new file mode 100644 -index 0000000000000..683e92d759b4e ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/path2url.h -@@ -0,0 +1,10 @@ -+#ifndef IPFS_PATH2URL_H_ -+#define IPFS_PATH2URL_H_ -+ -+#include -+ -+namespace ipfs { -+std::string path2url(std::string path_as_string); -+} -+ -+#endif // IPFS_PATH2URL_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/redirects.cc b/third_party/ipfs_client/src/ipfs_client/redirects.cc -new file mode 100644 -index 0000000000000..b2395dcae75c0 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/redirects.cc -@@ -0,0 +1,259 @@ -+#include "redirects.h" -+ -+#include "log_macros.h" -+ -+#include -+ -+#include -+#include -+ -+namespace r = ipfs::redirects; -+using namespace std::literals; -+ -+namespace { -+// 2.4.4 Max File Size -+// The file size must not exceed 64 KiB. -+constexpr std::size_t MAX_SIZE = 64UL * 1024UL * 1024UL; -+ -+// Not including \n which terminates lines -+constexpr std::string_view WHITESPACE = " \t\f\r\v\n"; -+ -+// https://specs.ipfs.tech/http-gateways/web-redirects-file/#status -+constexpr int DEFAULT_STATUS = 301; -+// https://specs.ipfs.tech/http-gateways/web-redirects-file/#error-handling -+constexpr int PARSE_ERROR_STATUS = 500; -+} // namespace -+ -+r::Directive::Directive(std::string_view from, std::string_view to, int status) -+ : to_{to}, status_{status} { -+ SlashDelimited comp_str_s{from}; -+ std::unordered_set placeholders; -+ while (comp_str_s) { -+ auto comp_str = comp_str_s.pop(); -+ if (comp_str.empty()) { -+ LOG(ERROR) << "Got empty slash-delimited component. Should not have."; -+ return; -+ } else if (comp_str == "*") { -+ components_.emplace_back(ComponentType::SPLAT, comp_str); -+ } else if (comp_str[0] == ':') { -+ if (placeholders.insert(comp_str).second) { -+ components_.emplace_back(ComponentType::PLACEHOLDER, comp_str); -+ } else { -+ to_.assign("ERROR: Duplicate placeholder ").append(comp_str); -+ return; -+ } -+ } else { -+ components_.emplace_back(ComponentType::LITERAL, comp_str); -+ } -+ } -+} -+std::uint16_t r::Directive::rewrite(std::string& path) const { -+ auto input = SlashDelimited{path}; -+ auto result = to_; -+ auto replace = [&result](std::string_view ph, std::string_view val) { -+ std::size_t pos; -+ while ((pos = result.find(ph)) < result.size()) { -+ result.replace(pos, ph.size(), val); -+ } -+ }; -+ for (auto [type, comp_str] : components_) { -+ if (!input) { -+ VLOG(2) << "Ran out of input in [" << path -+ << "] before running out of pattern components to match against " -+ "(was looking for [" -+ << comp_str << "]. Not a match."; -+ return 0; -+ } -+ if (type == ComponentType::LITERAL) { -+ if (comp_str != input.pop()) { -+ return 0; -+ } -+ } else if (type == ComponentType::PLACEHOLDER) { -+ replace(comp_str, input.pop()); -+ } else { -+ replace(":splat"sv, input.pop_all()); -+ } -+ } -+ if (input) { -+ return 0; -+ } else { -+ path = result; -+ return status_; -+ } -+} -+std::string r::Directive::error() const { -+ if (starts_with(to_, "ERROR: ")) { -+ return to_; -+ } -+ if (status_ < 200 || status_ > 451) { -+ return "UNSUPPORTED STATUS " + std::to_string(status_); -+ } -+ if (components_.empty()) { -+ return "Empty directive pattern"; -+ } -+ if (to_.empty()) { -+ return "Empty redirect target location"; -+ } -+ if (to_.at(0) != '/' && to_.find("://") == std::string::npos) { -+ return "Location must begin with / or be a URL"; -+ } -+ return {}; -+} -+ -+std::uint16_t r::File::rewrite(std::string& missing_path) const { -+ for (auto& directive : directives_) { -+ auto status = directive.rewrite(missing_path); -+ if (status) { -+ return status; -+ } -+ } -+ return 0; -+} -+r::File::File(std::string_view to_parse) { -+ if (to_parse.size() > MAX_SIZE) { -+ error_ = "INPUT FILE TOO LARGE " + std::to_string(to_parse.size()); -+ return; -+ } -+ for (auto line_number = 1; valid() && to_parse.size(); ++line_number) { -+ auto line_end = to_parse.find('\n'); -+ auto line = to_parse.substr(0UL, line_end); -+ if (!parse_line(line, line_number)) { -+ LOG(INFO) << "Line #" << line_number << " ignored: [" << line << ']'; -+ } else if (directives_.empty()) { -+ LOG(ERROR) << "Expected to have a directive after parsing line #" -+ << line_number << ": " << line; -+ } else if (directives_.back().valid()) { -+ VLOG(1) << "Line #" << line_number << " parsed. " << line; -+ } else { -+ error_ = "FAILURE PARSING LINE # " + std::to_string(line_number); -+ error_.append(": ") -+ .append(directives_.back().error()) -+ .append(" [") -+ .append(line) -+ .push_back(']'); -+ LOG(ERROR) << error_; -+ return; -+ } -+ if (line_end < to_parse.size()) { -+ to_parse.remove_prefix(line_end + 1); -+ } else { -+ break; -+ } -+ } -+ if (directives_.empty()) { -+ error_ = "No redirection directives in _redirects"; -+ LOG(ERROR) << error_; -+ } -+} -+ -+namespace { -+std::pair parse_status(std::string_view line, -+ std::size_t col); -+} -+bool r::File::parse_line(std::string_view line, int line_number) { -+ if (line.empty()) { -+ // empty line is not a directive -+ return false; -+ } -+ auto bpos = line.find_first_not_of(WHITESPACE); -+ if (bpos == std::string_view::npos) { -+ // effectively empty line -+ return false; -+ } else if (line[bpos] == '#') { -+ // https://specs.ipfs.tech/http-gateways/web-redirects-file/#comments -+ return false; -+ } -+ auto epos = line.find_first_of(WHITESPACE, bpos); -+ if (epos == std::string_view::npos) { -+ error_ = "Parsing _redirects file: line # " + std::to_string(line_number); -+ error_ -+ .append(" , expected at least 2 tokens (from and to) for directive: [") -+ .append(line) -+ .append("], but didn't even get whitespace to end from"); -+ return false; -+ } -+ auto from = line.substr(bpos, epos - bpos); -+ bpos = line.find_first_not_of(WHITESPACE, epos); -+ if (bpos == std::string_view::npos) { -+ error_ = "Parsing _redirects file: line # " + std::to_string(line_number); -+ error_ -+ .append(" , expected at least 2 tokens (from and to) for directive: [") -+ .append(line) -+ .append("], but didn't get a to"); -+ return false; -+ } -+ epos = line.find_first_of(WHITESPACE, bpos); -+ auto to = line.substr(bpos, epos - bpos); -+ auto [status, err] = parse_status(line, epos); -+ if (err.empty()) { -+ directives_.emplace_back(from, to, status); -+ return true; -+ } else { -+ error_ = err; -+ LOG(ERROR) << "Error parsing status on line #" << line_number << " [" -+ << line << "]."; -+ return false; -+ } -+} -+ -+namespace { -+ -+std::pair parse_status(std::string_view line, -+ std::size_t col) { -+ if (col >= line.size()) { -+ VLOG(2) << " No status specified, using default."; -+ return {DEFAULT_STATUS, ""}; -+ } -+ auto b = line.find_first_not_of(WHITESPACE, col); -+ if (b >= line.size()) { -+ VLOG(2) -+ << " No status specified (line ended in whitespace), using default."; -+ return {DEFAULT_STATUS, ""}; -+ } -+ auto status_str = line.substr(b); -+ if (status_str.size() < 3) { -+ return {PARSE_ERROR_STATUS, -+ " Not enough characters for a valid status string: [" + -+ std::string{status_str} + "]."}; -+ } -+ auto good = [](int i) { return std::make_pair(i, ""s); }; -+ auto unsupported = [status_str]() { -+ return std::make_pair( -+ PARSE_ERROR_STATUS, -+ "Unsupported status specified in directive:" + std::string{status_str}); -+ }; -+ /* -+ * 200 - OK treated as a rewrite, without changing the URL in the browser. -+ * 301 - Permanent Redirect (default) -+ * 302 - Found (commonly used for Temporary Redirect) -+ * 303 - See Other (replacing PUT and POST with GET) -+ * 307 - Temporary Redirect (explicitly preserving body and HTTP method) -+ * 308 - Permanent Redirect (preserving body & method of original request) -+ * 404 - Not Found (Useful for a pretty 404 page) -+ * 410 - Gone -+ * 451 - Unavailable For Legal Reasons -+ */ -+ switch (status_str[0]) { -+ case '2': -+ return status_str == "200" ? good(200) : unsupported(); -+ case '3': -+ if (status_str[1] != '0') { -+ return unsupported(); -+ } -+ return good(300 + status_str[2] - '0'); -+ case '4': -+ switch (status_str[1]) { -+ case '0': -+ return status_str[2] == '4' ? good(404) : unsupported(); -+ case '1': -+ return status_str[2] == '0' ? good(410) : unsupported(); -+ case '5': -+ return status_str[2] == '1' ? good(451) : unsupported(); -+ default: -+ return unsupported(); -+ } -+ default: -+ return unsupported(); -+ } -+} -+} // namespace -diff --git a/third_party/ipfs_client/src/ipfs_client/redirects.h b/third_party/ipfs_client/src/ipfs_client/redirects.h -new file mode 100644 -index 0000000000000..e0b333f1de2f1 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/redirects.h -@@ -0,0 +1,41 @@ -+#ifndef IPFS_REDIRECTS_H_ -+#define IPFS_REDIRECTS_H_ -+ -+#include -+ -+#include -+#include -+#include -+ -+namespace ipfs { -+namespace redirects { -+class Directive { -+ enum class ComponentType { LITERAL, PLACEHOLDER, SPLAT }; -+ std::vector> components_; -+ std::string to_; -+ int const status_; -+ -+ public: -+ Directive(std::string_view, std::string_view, int); -+ std::uint16_t rewrite(std::string&) const; -+ std::string error() const; -+ bool valid() const { return error().empty(); } -+}; -+class File { -+ std::vector directives_; -+ std::string error_; -+ -+ public: -+ File(std::string_view to_parse); -+ -+ bool valid() const { return error().empty(); } -+ std::string const& error() const { return error_; } -+ std::uint16_t rewrite(std::string& missing_path) const; -+ -+ private: -+ bool parse_line(std::string_view, int); -+}; -+} // namespace redirects -+} // namespace ipfs -+ -+#endif // IPFS_REDIRECTS_H_ -diff --git a/third_party/ipfs_client/src/ipfs_client/response.cc b/third_party/ipfs_client/src/ipfs_client/response.cc -new file mode 100644 -index 0000000000000..411d87d1354e4 ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/response.cc -@@ -0,0 +1,16 @@ -+#include "ipfs_client/response.h" -+ -+using Self = ipfs::Response; -+ -+Self Self::PLAIN_NOT_FOUND{"text/html", static_cast(404), -+ std::string{}, std::string{}}; -+Self Self::IMMUTABLY_GONE{"text/plain", 410, -+ "Using immutable data it has been proven the " -+ "resource does not exist anywhere.", -+ std::string{}}; -+ -+Self Self::HOST_NOT_FOUND{ -+ "text/plain", Self::HOST_NOT_FOUND_STATUS, -+ "either a hostname didn't resolve a DNS TXT records for dnslink=, or we " -+ "can't find a gateway with the necessary IPNS record", -+ std::string{}}; -diff --git a/third_party/ipfs_client/src/ipfs_client/signing_key_type.cc b/third_party/ipfs_client/src/ipfs_client/signing_key_type.cc -new file mode 100644 -index 0000000000000..b6489a47b130f ---- /dev/null -+++ b/third_party/ipfs_client/src/ipfs_client/signing_key_type.cc -@@ -0,0 +1,15 @@ -+#include -+ -+#include -+ -+using T = ipfs::SigningKeyType; -+namespace n = ipfs::ipns; -+ -+// It is critically important that these 2 enumerations remain in-synch. -+// However, some headers that reference SigningKeyType need to be able to -+// compile without access to protobuf. -+static_assert(static_cast(T::RSA) == n::RSA); -+static_assert(static_cast(T::Ed25519) == n::Ed25519); -+static_assert(static_cast(T::Secp256k1) == n::Secp256k1); -+static_assert(static_cast(T::ECDSA) == n::ECDSA); -+static_assert(static_cast(T::KeyTypeCount) == n::KeyType_ARRAYSIZE); -diff --git a/third_party/ipfs_client/src/libp2p/crypto/protobuf_key.hpp b/third_party/ipfs_client/src/libp2p/crypto/protobuf_key.hpp -new file mode 100644 -index 0000000000000..459426f8c58a2 ---- /dev/null -+++ b/third_party/ipfs_client/src/libp2p/crypto/protobuf_key.hpp -@@ -0,0 +1,29 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#ifndef KAGOME_PROTOBUF_KEY_HPP -+#define KAGOME_PROTOBUF_KEY_HPP -+ -+#include -+#include -+ -+#include -+ -+namespace libp2p::crypto { -+ /** -+ * Strict type for key, which is encoded into Protobuf format -+ */ -+ struct ProtobufKey : public boost::equality_comparable { -+ explicit ProtobufKey(std::vector key) : key{std::move(key)} {} -+ -+ std::vector key; -+ -+ bool operator==(const ProtobufKey &other) const { -+ return key == other.key; -+ } -+ }; -+} // namespace libp2p::crypto -+ -+#endif // KAGOME_PROTOBUF_KEY_HPP -diff --git a/third_party/ipfs_client/src/libp2p/multi/multibase_codec/codecs/base16.cc b/third_party/ipfs_client/src/libp2p/multi/multibase_codec/codecs/base16.cc -new file mode 100644 -index 0000000000000..b032cccbbdc1c ---- /dev/null -+++ b/third_party/ipfs_client/src/libp2p/multi/multibase_codec/codecs/base16.cc -@@ -0,0 +1,104 @@ -+#include -+ -+namespace b16 = ipfs::base16; -+ -+namespace { -+std::uint8_t to_i(char c); -+template -+char to_c(std::uint8_t n) { -+ if (n < 10) { -+ return n + '0'; -+ } else { -+ return n - 10 + a; -+ } -+} -+template -+std::string encode(ipfs::ByteView bytes) { -+ std::string result; -+ result.reserve(bytes.size() * 2); -+ for (auto b : bytes) { -+ auto i = to_integer(b); -+ result.push_back(to_c(i >> 4)); -+ result.push_back(to_c(i & 0xF)); -+ } -+ return result; -+} -+} // namespace -+ -+std::string b16::encodeLower(ByteView bytes) { -+ return encode<'a'>(bytes); -+} -+std::string b16::encodeUpper(ByteView bytes) { -+ return encode<'A'>(bytes); -+} -+auto b16::decode(std::string_view s) -> Decoded { -+ ByteArray result(s.size() / 2, ipfs::Byte{}); -+ for (auto i = 0U; i + 1U < s.size(); i += 2U) { -+ auto a = to_i(s[i]); -+ auto b = to_i(s[i + 1]); -+ if (a > 0xF || b > 0xF) { -+ return ipfs::unexpected{BaseError::INVALID_BASE16_INPUT}; -+ } -+ result[i / 2] = ipfs::Byte{static_cast((a << 4) | b)}; -+ } -+ if (s.size() % 2) { -+ auto a = to_i(s.back()); -+ if (a <= 0xF) { -+ result.push_back(ipfs::Byte{a}); -+ } -+ } -+ return result; -+} -+ -+namespace { -+std::uint8_t to_i(char c) { -+ switch (c) { -+ case '0': -+ return 0; -+ case '1': -+ return 1; -+ case '2': -+ return 2; -+ case '3': -+ return 3; -+ case '4': -+ return 4; -+ case '5': -+ return 5; -+ case '6': -+ return 6; -+ case '7': -+ return 7; -+ case '8': -+ return 8; -+ case '9': -+ return 9; -+ case 'a': -+ return 10; -+ case 'b': -+ return 11; -+ case 'c': -+ return 12; -+ case 'd': -+ return 13; -+ case 'e': -+ return 14; -+ case 'f': -+ return 15; -+ case 'A': -+ return 10; -+ case 'B': -+ return 11; -+ case 'C': -+ return 12; -+ case 'D': -+ return 13; -+ case 'E': -+ return 14; -+ case 'F': -+ return 15; -+ default: -+ return 0xFF; -+ } -+} -+} // namespace -diff --git a/third_party/ipfs_client/src/libp2p/multi/uvarint.cc b/third_party/ipfs_client/src/libp2p/multi/uvarint.cc -new file mode 100644 -index 0000000000000..2e6ed6eb0bada ---- /dev/null -+++ b/third_party/ipfs_client/src/libp2p/multi/uvarint.cc -@@ -0,0 +1,107 @@ -+/** -+ * Copyright Soramitsu Co., Ltd. All Rights Reserved. -+ * SPDX-License-Identifier: Apache-2.0 -+ */ -+ -+#include -+ -+namespace libp2p::multi { -+ -+UVarint::UVarint(UVarint const& rhs) : bytes_(rhs.bytes_) {} -+UVarint::UVarint(uint64_t number) { -+ do { -+ auto byte = static_cast(number) & ipfs::Byte{0x7f}; -+ number >>= 7; -+ if (number != 0) { -+ byte |= ipfs::Byte{0x80}; -+ } -+ bytes_.push_back(byte); -+ } while (number != 0); -+} -+ -+UVarint::UVarint(ipfs::ByteView varint_bytes) { -+ auto size = calculateSize(varint_bytes); -+ if (size <= varint_bytes.size()) { -+ bytes_.assign(varint_bytes.begin(), varint_bytes.begin() + size); -+ } -+} -+ -+UVarint::UVarint(ipfs::ByteView varint_bytes, size_t varint_size) -+ : bytes_(varint_bytes.begin(), varint_bytes.begin() + varint_size) {} -+ -+std::optional UVarint::create(ipfs::ByteView varint_bytes) { -+ size_t size = calculateSize(varint_bytes); -+ if (size > 0 && size <= varint_bytes.size()) { -+ return UVarint{varint_bytes, size}; -+ } -+ return {}; -+} -+ -+uint64_t UVarint::toUInt64() const { -+ uint64_t res = 0; -+ size_t index = 0; -+ for (const auto& byte : bytes_) { -+ res += static_cast((byte & ipfs::Byte{0x7f})) << index; -+ index += 7; -+ } -+ return res; -+} -+ -+ipfs::ByteView UVarint::toBytes() const { -+ return ipfs::ByteView{bytes_.data(), bytes_.size()}; -+} -+ -+std::vector const& UVarint::toVector() const { -+ return bytes_; -+} -+ -+size_t UVarint::size() const { -+ return bytes_.size(); -+} -+ -+UVarint& UVarint::operator=(UVarint const& rhs) { -+ bytes_ = rhs.bytes_; // actually OK even if &rhs == this -+ return *this; -+} -+UVarint& UVarint::operator=(uint64_t n) { -+ *this = UVarint(n); -+ return *this; -+} -+ -+bool UVarint::operator==(const UVarint& r) const { -+ return std::equal(bytes_.begin(), bytes_.end(), r.bytes_.begin(), -+ r.bytes_.end()); -+} -+ -+bool UVarint::operator!=(const UVarint& r) const { -+ return !(*this == r); -+} -+ -+bool UVarint::operator<(const UVarint& r) const { -+ return toUInt64() < r.toUInt64(); -+} -+ -+size_t UVarint::calculateSize(ipfs::ByteView varint_bytes) { -+ size_t size = 0; -+ size_t shift = 0; -+ constexpr size_t capacity = sizeof(uint64_t) * 8; -+ bool last_byte_found = false; -+ for (const auto& byte : varint_bytes) { -+ ++size; -+ std::uint_least64_t slice = to_integer(byte) & 0x7f; -+ if (shift >= capacity || ((slice << shift) >> shift) != slice) { -+ size = 0; -+ break; -+ } -+ if ((byte & ipfs::Byte{0x80}) == ipfs::Byte{0}) { -+ last_byte_found = true; -+ break; -+ } -+ shift += 7; -+ } -+ return last_byte_found ? size : 0; -+} -+ -+UVarint::~UVarint() noexcept {} -+ -+} // namespace libp2p::multi -diff --git a/third_party/ipfs_client/src/log_macros.h b/third_party/ipfs_client/src/log_macros.h -new file mode 100644 -index 0000000000000..e406429d0f280 ---- /dev/null -+++ b/third_party/ipfs_client/src/log_macros.h -@@ -0,0 +1,53 @@ -+#ifndef IPFS_LOG_MACROS_H_ -+#define IPFS_LOG_MACROS_H_ -+ -+#include -+ -+#if __has_include("base/logging.h") //In Chromium -+ -+#include "base/logging.h" -+#include "base/check_op.h" -+ -+#else // Not in Chromium -+ -+#include -+ -+#include -+ -+#define DCHECK_EQ GOOGLE_DCHECK_EQ -+#define DCHECK_GT GOOGLE_DCHECK_GT -+#define DCHECK GOOGLE_DCHECK -+#define LOG GOOGLE_LOG -+ -+#define VLOG(X) \ -+ ::google::protobuf::internal::LogFinisher() = \ -+ ::google::protobuf::internal::LogMessage( \ -+ static_cast<::google::protobuf::LogLevel>( \ -+ ::google::protobuf::LOGLEVEL_INFO - X), \ -+ __FILE__, __LINE__) -+ -+#pragma GCC diagnostic push -+#pragma GCC diagnostic ignored "-Wunused-variable" -+namespace { -+static bool is_logging_initialized = ::ipfs::log::IsInitialized(); -+} -+#pragma GCC diagnostic pop -+ -+#endif //Chromium in-tree check -+ -+#define L_VAR(X) LOG(INFO) << "VAR " << #X << "='" << (X) << '\''; -+ -+inline bool starts_with(std::string_view full_text, std::string_view prefix) { -+ if (prefix.size() > full_text.size()) { -+ return false; -+ } -+ return full_text.substr(0UL, prefix.size()) == prefix; -+} -+inline bool ends_with(std::string_view full_text, std::string_view suffix) { -+ if (suffix.size() > full_text.size()) { -+ return false; -+ } -+ return full_text.substr(full_text.size() - suffix.size()) == suffix; -+} -+ -+#endif // IPFS_LOG_MACROS_H_ -diff --git a/third_party/ipfs_client/src/smhasher/MurmurHash3.cc b/third_party/ipfs_client/src/smhasher/MurmurHash3.cc -new file mode 100644 -index 0000000000000..677aedf1d7a55 ---- /dev/null -+++ b/third_party/ipfs_client/src/smhasher/MurmurHash3.cc -@@ -0,0 +1,424 @@ -+//----------------------------------------------------------------------------- -+// MurmurHash3 was written by Austin Appleby, and is placed in the public -+// domain. The author hereby disclaims copyright to this source code. -+ -+// Note - The x86 and x64 versions do _not_ produce the same results, as the -+// algorithms are optimized for their respective platforms. You can still -+// compile and run any of them on any platform, but your performance with the -+// non-native version will be less than optimal. -+ -+#include "smhasher/MurmurHash3.h" -+#ifdef __GNUG__ -+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -+#endif -+ -+#ifdef __clang__ -+#pragma clang diagnostic ignored "-Wimplicit-fallthrough" -+#endif -+//----------------------------------------------------------------------------- -+// Platform-specific functions and macros -+ -+// Microsoft Visual Studio -+ -+#if defined(_MSC_VER) -+ -+#define FORCE_INLINE __forceinline -+ -+#include -+ -+#define ROTL32(x, y) _rotl(x, y) -+#define ROTL64(x, y) _rotl64(x, y) -+ -+#define BIG_CONSTANT(x) (x) -+ -+// Other compilers -+ -+#else // defined(_MSC_VER) -+ -+#define FORCE_INLINE inline __attribute__((always_inline)) -+ -+inline uint32_t rotl32(uint32_t x, int8_t r) { -+ return (x << r) | (x >> (32 - r)); -+} -+ -+inline uint64_t rotl64(uint64_t x, int8_t r) { -+ return (x << r) | (x >> (64 - r)); -+} -+ -+#define ROTL32(x, y) rotl32(x, y) -+#define ROTL64(x, y) rotl64(x, y) -+ -+#define BIG_CONSTANT(x) (x##LLU) -+ -+#endif // !defined(_MSC_VER) -+ -+//----------------------------------------------------------------------------- -+// Block read - if your platform needs to do endian-swapping or can only -+// handle aligned reads, do the conversion here -+ -+FORCE_INLINE uint32_t getblock32(const uint32_t* p, int i) { -+ return p[i]; -+} -+ -+FORCE_INLINE uint64_t getblock64(const uint64_t* p, int i) { -+ return p[i]; -+} -+ -+//----------------------------------------------------------------------------- -+// Finalization mix - force all bits of a hash block to avalanche -+ -+FORCE_INLINE uint32_t fmix32(uint32_t h) { -+ h ^= h >> 16; -+ h *= 0x85ebca6b; -+ h ^= h >> 13; -+ h *= 0xc2b2ae35; -+ h ^= h >> 16; -+ -+ return h; -+} -+ -+//---------- -+ -+FORCE_INLINE uint64_t fmix64(uint64_t k) { -+ k ^= k >> 33; -+ k *= BIG_CONSTANT(0xff51afd7ed558ccd); -+ k ^= k >> 33; -+ k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); -+ k ^= k >> 33; -+ -+ return k; -+} -+ -+//----------------------------------------------------------------------------- -+ -+void MurmurHash3_x86_32(const void* key, int len, uint32_t seed, void* out) { -+ const uint8_t* data = (const uint8_t*)key; -+ const int nblocks = len / 4; -+ -+ uint32_t h1 = seed; -+ -+ const uint32_t c1 = 0xcc9e2d51; -+ const uint32_t c2 = 0x1b873593; -+ -+ //---------- -+ // body -+ -+ const uint32_t* blocks = (const uint32_t*)(data + nblocks * 4); -+ -+ for (int i = -nblocks; i; i++) { -+ uint32_t k1 = getblock32(blocks, i); -+ -+ k1 *= c1; -+ k1 = ROTL32(k1, 15); -+ k1 *= c2; -+ -+ h1 ^= k1; -+ h1 = ROTL32(h1, 13); -+ h1 = h1 * 5 + 0xe6546b64; -+ } -+ -+ //---------- -+ // tail -+ -+ const uint8_t* tail = (const uint8_t*)(data + nblocks * 4); -+ -+ uint32_t k1 = 0; -+ -+ switch (len & 3) { -+ case 3: -+ k1 ^= tail[2] << 16; -+ case 2: -+ k1 ^= tail[1] << 8; -+ case 1: -+ k1 ^= tail[0]; -+ k1 *= c1; -+ k1 = ROTL32(k1, 15); -+ k1 *= c2; -+ h1 ^= k1; -+ }; -+ -+ //---------- -+ // finalization -+ -+ h1 ^= len; -+ -+ h1 = fmix32(h1); -+ -+ *(uint32_t*)out = h1; -+} -+ -+//----------------------------------------------------------------------------- -+ -+void MurmurHash3_x86_128(const void* key, -+ const int len, -+ uint32_t seed, -+ void* out) { -+ const uint8_t* data = (const uint8_t*)key; -+ const int nblocks = len / 16; -+ -+ uint32_t h1 = seed; -+ uint32_t h2 = seed; -+ uint32_t h3 = seed; -+ uint32_t h4 = seed; -+ -+ const uint32_t c1 = 0x239b961b; -+ const uint32_t c2 = 0xab0e9789; -+ const uint32_t c3 = 0x38b34ae5; -+ const uint32_t c4 = 0xa1e38b93; -+ -+ //---------- -+ // body -+ -+ const uint32_t* blocks = (const uint32_t*)(data + nblocks * 16); -+ -+ for (int i = -nblocks; i; i++) { -+ uint32_t k1 = getblock32(blocks, i * 4 + 0); -+ uint32_t k2 = getblock32(blocks, i * 4 + 1); -+ uint32_t k3 = getblock32(blocks, i * 4 + 2); -+ uint32_t k4 = getblock32(blocks, i * 4 + 3); -+ -+ k1 *= c1; -+ k1 = ROTL32(k1, 15); -+ k1 *= c2; -+ h1 ^= k1; -+ -+ h1 = ROTL32(h1, 19); -+ h1 += h2; -+ h1 = h1 * 5 + 0x561ccd1b; -+ -+ k2 *= c2; -+ k2 = ROTL32(k2, 16); -+ k2 *= c3; -+ h2 ^= k2; -+ -+ h2 = ROTL32(h2, 17); -+ h2 += h3; -+ h2 = h2 * 5 + 0x0bcaa747; -+ -+ k3 *= c3; -+ k3 = ROTL32(k3, 17); -+ k3 *= c4; -+ h3 ^= k3; -+ -+ h3 = ROTL32(h3, 15); -+ h3 += h4; -+ h3 = h3 * 5 + 0x96cd1c35; -+ -+ k4 *= c4; -+ k4 = ROTL32(k4, 18); -+ k4 *= c1; -+ h4 ^= k4; -+ -+ h4 = ROTL32(h4, 13); -+ h4 += h1; -+ h4 = h4 * 5 + 0x32ac3b17; -+ } -+ -+ //---------- -+ // tail -+ -+ const uint8_t* tail = (const uint8_t*)(data + nblocks * 16); -+ -+ uint32_t k1 = 0; -+ uint32_t k2 = 0; -+ uint32_t k3 = 0; -+ uint32_t k4 = 0; -+ -+ switch (len & 15) { -+ case 15: -+ k4 ^= tail[14] << 16; -+ case 14: -+ k4 ^= tail[13] << 8; -+ case 13: -+ k4 ^= tail[12] << 0; -+ k4 *= c4; -+ k4 = ROTL32(k4, 18); -+ k4 *= c1; -+ h4 ^= k4; -+ -+ case 12: -+ k3 ^= tail[11] << 24; -+ case 11: -+ k3 ^= tail[10] << 16; -+ case 10: -+ k3 ^= tail[9] << 8; -+ case 9: -+ k3 ^= tail[8] << 0; -+ k3 *= c3; -+ k3 = ROTL32(k3, 17); -+ k3 *= c4; -+ h3 ^= k3; -+ -+ case 8: -+ k2 ^= tail[7] << 24; -+ case 7: -+ k2 ^= tail[6] << 16; -+ case 6: -+ k2 ^= tail[5] << 8; -+ case 5: -+ k2 ^= tail[4] << 0; -+ k2 *= c2; -+ k2 = ROTL32(k2, 16); -+ k2 *= c3; -+ h2 ^= k2; -+ -+ case 4: -+ k1 ^= tail[3] << 24; -+ case 3: -+ k1 ^= tail[2] << 16; -+ case 2: -+ k1 ^= tail[1] << 8; -+ case 1: -+ k1 ^= tail[0] << 0; -+ k1 *= c1; -+ k1 = ROTL32(k1, 15); -+ k1 *= c2; -+ h1 ^= k1; -+ }; -+ -+ //---------- -+ // finalization -+ -+ h1 ^= len; -+ h2 ^= len; -+ h3 ^= len; -+ h4 ^= len; -+ -+ h1 += h2; -+ h1 += h3; -+ h1 += h4; -+ h2 += h1; -+ h3 += h1; -+ h4 += h1; -+ -+ h1 = fmix32(h1); -+ h2 = fmix32(h2); -+ h3 = fmix32(h3); -+ h4 = fmix32(h4); -+ -+ h1 += h2; -+ h1 += h3; -+ h1 += h4; -+ h2 += h1; -+ h3 += h1; -+ h4 += h1; -+ -+ ((uint32_t*)out)[0] = h1; -+ ((uint32_t*)out)[1] = h2; -+ ((uint32_t*)out)[2] = h3; -+ ((uint32_t*)out)[3] = h4; -+} -+ -+//----------------------------------------------------------------------------- -+ -+void MurmurHash3_x64_128(const void* key, -+ const int len, -+ const uint32_t seed, -+ void* out) { -+ const uint8_t* data = (const uint8_t*)key; -+ const int nblocks = len / 16; -+ -+ uint64_t h1 = seed; -+ uint64_t h2 = seed; -+ -+ const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); -+ const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); -+ -+ //---------- -+ // body -+ -+ const uint64_t* blocks = (const uint64_t*)(data); -+ -+ for (int i = 0; i < nblocks; i++) { -+ uint64_t k1 = getblock64(blocks, i * 2 + 0); -+ uint64_t k2 = getblock64(blocks, i * 2 + 1); -+ -+ k1 *= c1; -+ k1 = ROTL64(k1, 31); -+ k1 *= c2; -+ h1 ^= k1; -+ -+ h1 = ROTL64(h1, 27); -+ h1 += h2; -+ h1 = h1 * 5 + 0x52dce729; -+ -+ k2 *= c2; -+ k2 = ROTL64(k2, 33); -+ k2 *= c1; -+ h2 ^= k2; -+ -+ h2 = ROTL64(h2, 31); -+ h2 += h1; -+ h2 = h2 * 5 + 0x38495ab5; -+ } -+ -+ //---------- -+ // tail -+ -+ const uint8_t* tail = (const uint8_t*)(data + nblocks * 16); -+ -+ uint64_t k1 = 0; -+ uint64_t k2 = 0; -+ -+ switch (len & 15) { -+ case 15: -+ k2 ^= ((uint64_t)tail[14]) << 48; -+ case 14: -+ k2 ^= ((uint64_t)tail[13]) << 40; -+ case 13: -+ k2 ^= ((uint64_t)tail[12]) << 32; -+ case 12: -+ k2 ^= ((uint64_t)tail[11]) << 24; -+ case 11: -+ k2 ^= ((uint64_t)tail[10]) << 16; -+ case 10: -+ k2 ^= ((uint64_t)tail[9]) << 8; -+ case 9: -+ k2 ^= ((uint64_t)tail[8]) << 0; -+ k2 *= c2; -+ k2 = ROTL64(k2, 33); -+ k2 *= c1; -+ h2 ^= k2; -+ -+ case 8: -+ k1 ^= ((uint64_t)tail[7]) << 56; -+ case 7: -+ k1 ^= ((uint64_t)tail[6]) << 48; -+ case 6: -+ k1 ^= ((uint64_t)tail[5]) << 40; -+ case 5: -+ k1 ^= ((uint64_t)tail[4]) << 32; -+ case 4: -+ k1 ^= ((uint64_t)tail[3]) << 24; -+ case 3: -+ k1 ^= ((uint64_t)tail[2]) << 16; -+ case 2: -+ k1 ^= ((uint64_t)tail[1]) << 8; -+ case 1: -+ k1 ^= ((uint64_t)tail[0]) << 0; -+ k1 *= c1; -+ k1 = ROTL64(k1, 31); -+ k1 *= c2; -+ h1 ^= k1; -+ }; -+ -+ //---------- -+ // finalization -+ -+ h1 ^= len; -+ h2 ^= len; -+ -+ h1 += h2; -+ h2 += h1; -+ -+ h1 = fmix64(h1); -+ h2 = fmix64(h2); -+ -+ h1 += h2; -+ h2 += h1; -+ -+ ((uint64_t*)out)[0] = h1; -+ ((uint64_t*)out)[1] = h2; -+} -+ -+//----------------------------------------------------------------------------- -diff --git a/third_party/ipfs_client/src/vocab/byte_view.cc b/third_party/ipfs_client/src/vocab/byte_view.cc -new file mode 100644 -index 0000000000000..f71dcaa0181f1 ---- /dev/null -+++ b/third_party/ipfs_client/src/vocab/byte_view.cc -@@ -0,0 +1,2 @@ -+#include "vocab/byte_view.h" -+ -diff --git a/third_party/ipfs_client/src/vocab/slash_delimited.cc b/third_party/ipfs_client/src/vocab/slash_delimited.cc -new file mode 100644 -index 0000000000000..c81ae5823c867 ---- /dev/null -+++ b/third_party/ipfs_client/src/vocab/slash_delimited.cc -@@ -0,0 +1,117 @@ -+#include -+ -+#include "log_macros.h" -+ -+#include -+ -+#if __has_include() -+#include -+#define HAS_STRINGPIECE 1 -+#endif -+ -+using Self = ipfs::SlashDelimited; -+ -+Self::SlashDelimited(std::string_view unowned) : remainder_{unowned} {} -+ -+Self::operator bool() const { -+ return remainder_.find_first_not_of("/") < remainder_.size(); -+} -+std::string_view Self::pop() { -+ if (remainder_.empty()) { -+ return remainder_; -+ } -+ auto slash = remainder_.find('/'); -+ if (slash == std::string_view::npos) { -+ return pop_all(); -+ } -+ auto result = remainder_.substr(0UL, slash); -+ remainder_.remove_prefix(slash + 1); -+ if (result.empty()) { -+ return pop(); -+ } else { -+ return result; -+ } -+} -+std::string_view Self::pop_all() { -+ auto result = remainder_; -+ remainder_ = ""; -+ return result; -+} -+std::string_view Self::pop_n(std::size_t n) { -+ std::size_t a = 0UL; -+ while (n) { -+ auto slash = remainder_.find('/', a); -+ auto non_slash = remainder_.find_first_not_of("/", a); -+ if (slash == std::string_view::npos) { -+ auto result = remainder_; -+ remainder_ = ""; -+ return result; -+ } -+ if (non_slash < slash) { -+ --n; -+ } -+ a = slash + 1UL; -+ } -+ auto result = remainder_.substr(0UL, a - 1); -+ remainder_.remove_prefix(a); -+ return result; -+} -+std::string_view Self::peek_back() const { -+ auto s = remainder_; -+ while (!s.empty() && s.back() == '/') { -+ s.remove_suffix(1); -+ } -+ if (s.empty()) { -+ return s; -+ } -+ auto last_slash = s.find_last_of('/'); -+ if (last_slash < remainder_.size()) { -+ return remainder_.substr(last_slash + 1); -+ } else { -+ return s; -+ } -+} -+std::string Self::pop_back() { -+ auto non_slash = remainder_.find_last_not_of('/'); -+ if (non_slash == std::string_view::npos) { -+ return ""; -+ } -+ auto slash = remainder_.find_last_of('/', non_slash); -+ std::string rv; -+ if (slash == std::string_view::npos) { -+ rv = remainder_; -+ remainder_ = ""; -+ } else { -+ rv = remainder_.substr(slash + 1, non_slash - slash); -+ remainder_ = remainder_.substr(0UL, slash); -+ } -+ return rv; -+} -+ -+std::ostream& operator<<(std::ostream& str, ipfs::SlashDelimited const& sd) { -+ return str << sd.to_view(); -+} -+ -+#if __has_include() -+#include -+ -+using namespace google::protobuf::internal; -+using namespace google::protobuf; -+ -+#if PROTOBUF_VERSION >= 3020000 -+#include -+LogMessage& operator<<(LogMessage& str, ipfs::SlashDelimited const& sd) { -+ return str << sd.to_view(); -+} -+#elif __has_include() -+#include -+LogMessage& operator<<(LogMessage& str, ipfs::SlashDelimited const& sd) { -+ return str << StringPiece{sd.to_view()}; -+} -+#else -+LogMessage& operator<<(LogMessage& str, ipfs::SlashDelimited const& sd) { -+ return str << std::string{sd.to_view()}; -+} -+#endif -+ -+#endif -diff --git a/third_party/ipfs_client/unix_fs.proto b/third_party/ipfs_client/unix_fs.proto -new file mode 100644 -index 0000000000000..9d117a4d66bdf ---- /dev/null -+++ b/third_party/ipfs_client/unix_fs.proto -@@ -0,0 +1,32 @@ -+syntax = "proto2"; -+option optimize_for = LITE_RUNTIME; -+package ipfs.unix_fs; -+ -+message Data { -+ enum DataType { -+ Raw = 0; -+ Directory = 1; -+ File = 2; -+ Metadata = 3; -+ Symlink = 4; -+ HAMTShard = 5; -+ } -+ -+ required DataType Type = 1; -+ optional bytes Data = 2; -+ optional uint64 filesize = 3; -+ repeated uint64 blocksizes = 4; -+ optional uint64 hashType = 5; -+ optional uint64 fanout = 6; -+ optional uint32 mode = 7; -+ optional UnixTime mtime = 8; -+} -+ -+message Metadata { -+ optional string MimeType = 1; -+} -+ -+message UnixTime { -+ required int64 Seconds = 1; -+ optional fixed32 FractionalNanoseconds = 2; -+} -diff --git a/url/BUILD.gn b/url/BUILD.gn -index c525c166979d6..ce2b1ae43c0a7 100644 ---- a/url/BUILD.gn -+++ b/url/BUILD.gn -@@ -5,6 +5,7 @@ - import("//build/buildflag_header.gni") - import("//testing/libfuzzer/fuzzer_test.gni") - import("//testing/test.gni") -+import("//third_party/ipfs_client/args.gni") - import("features.gni") - - import("//build/config/cronet/config.gni") -@@ -67,6 +68,7 @@ component("url") { - public_deps = [ - "//base", - "//build:robolectric_buildflags", -+ "//third_party/ipfs_client:ipfs_buildflags", - ] - - configs += [ "//build/config/compiler:wexit_time_destructors" ] -@@ -89,6 +91,11 @@ component("url") { - public_configs = [ "//third_party/jdk" ] - } - -+ if (enable_ipfs) { -+ sources += [ "url_canon_ipfs.cc" ] -+ deps += [ "//third_party/ipfs_client:ipfs_client" ] -+ } -+ - if (is_win) { - # Don't conflict with Windows' "url.dll". - output_name = "url_lib" -diff --git a/url/url_canon.h b/url/url_canon.h -index 913b3685c6fec..3c3c55e580564 100644 ---- a/url/url_canon.h -+++ b/url/url_canon.h -@@ -792,6 +792,23 @@ bool CanonicalizeMailtoURL(const char16_t* spec, - CanonOutput* output, - Parsed* new_parsed); - -+COMPONENT_EXPORT(URL) -+bool CanonicalizeIpfsURL(const char* spec, -+ int spec_len, -+ const Parsed& parsed, -+ SchemeType scheme_type, -+ CharsetConverter* query_converter, -+ CanonOutput* output, -+ Parsed* new_parsed); -+COMPONENT_EXPORT(URL) -+bool CanonicalizeIpfsURL(const char16_t* spec, -+ int spec_len, -+ const Parsed& parsed, -+ SchemeType scheme_type, -+ CharsetConverter* query_converter, -+ CanonOutput* output, -+ Parsed* new_parsed); -+ - // Part replacer -------------------------------------------------------------- - - // Internal structure used for storing separate strings for each component. -diff --git a/url/url_canon_ipfs.cc b/url/url_canon_ipfs.cc -new file mode 100644 -index 0000000000000..9511e3f5e6f5c ---- /dev/null -+++ b/url/url_canon_ipfs.cc -@@ -0,0 +1,55 @@ -+#include "url_canon_internal.h" -+ -+#include -+#include -+ -+#include -+ -+bool url::CanonicalizeIpfsURL(const char* spec, -+ int spec_len, -+ const Parsed& parsed, -+ SchemeType scheme_type, -+ CharsetConverter* charset_converter, -+ CanonOutput* output, -+ Parsed* output_parsed) { -+ if ( spec_len < 1 || !spec ) { -+ return false; -+ } -+ if ( parsed.host.len < 1 ) { -+ return false; -+ } -+ std::string_view cid_str{ spec + parsed.host.begin, static_cast(parsed.host.len) }; -+ auto cid = ipfs::Cid(cid_str); -+ if ( !cid.valid() ) { -+ cid = ipfs::id_cid::forText( std::string{cid_str} + " is not a valid CID." ); -+ } -+ auto as_str = cid.to_string(); -+ if ( as_str.empty() ) { -+ return false; -+ } -+ std::string stdurl{ spec, static_cast(parsed.host.begin) }; -+ stdurl.append( as_str ); -+ stdurl.append( spec + parsed.host.end(), spec_len - parsed.host.end() ); -+ spec = stdurl.data(); -+ spec_len = static_cast(stdurl.size()); -+ Parsed parsed_input; -+ ParseStandardURL(spec, spec_len, &parsed_input); -+ return CanonicalizeStandardURL( -+ spec, spec_len, -+ parsed_input, -+ scheme_type, -+ charset_converter, -+ output, output_parsed -+ ); -+} -+bool url::CanonicalizeIpfsURL(const char16_t* spec, -+ int spec_len, -+ const Parsed& parsed, -+ SchemeType scheme_type, -+ CharsetConverter* query_converter, -+ CanonOutput* output, -+ Parsed* new_parsed) { -+ RawCanonOutput<2048> as8; -+ ConvertUTF16ToUTF8(spec, spec_len, &as8); -+ return CanonicalizeIpfsURL(as8.data(), as8.length(), parsed, scheme_type, query_converter, output, new_parsed); -+} -diff --git a/url/url_util.cc b/url/url_util.cc -index 9258cfcfada47..daf10e4c3b741 100644 ---- a/url/url_util.cc -+++ b/url/url_util.cc -@@ -277,6 +277,12 @@ bool DoCanonicalize(const CHAR* spec, - charset_converter, output, - output_parsed); - -+ } else if (DoCompareSchemeComponent(spec, scheme, "ipfs")) { -+ // Switch multibase away from case-sensitive ones before continuing canonicalization. -+ ParseStandardURL(spec, spec_len, &parsed_input); -+ success = CanonicalizeIpfsURL(spec, spec_len, parsed_input, scheme_type, -+ charset_converter, output, output_parsed); -+ - } else if (DoIsStandard(spec, scheme, &scheme_type)) { - // All "normal" URLs. - ParseStandardURL(spec, spec_len, &parsed_input); - diff --git a/library/include/ipfs_client/context_api.h b/library/include/ipfs_client/context_api.h index f269f6bd..b87520c4 100644 --- a/library/include/ipfs_client/context_api.h +++ b/library/include/ipfs_client/context_api.h @@ -2,12 +2,13 @@ #define IPFS_CONTEXT_API_H_ #include "crypto/hasher.h" +#include "crypto/signature_verifier.h" +#include "crypto/signing_key_type.h" #include "dag_cbor_value.h" #include "gateway_spec.h" #include "http_request_description.h" #include "ipns_cbor_entry.h" #include "multi_hash.h" -#include "signing_key_type.h" #include @@ -69,12 +70,12 @@ class ContextApi : public std::enable_shared_from_this { using IpnsCborEntry = ::ipfs::IpnsCborEntry; - using SigningKeyType = ::ipfs::SigningKeyType; + using SigningKeyType = ::ipfs::crypto::SigningKeyType; using ByteView = ::ipfs::ByteView; virtual bool VerifyKeySignature(SigningKeyType, ByteView signature, ByteView data, - ByteView key_bytes) const = 0; + ByteView key_bytes) const; std::optional> Hash(HashType, ByteView data); @@ -85,6 +86,8 @@ class ContextApi : public std::enable_shared_from_this { protected: std::unordered_map> hashers_; + std::unordered_map> + verifiers_; }; } // namespace ipfs diff --git a/library/include/ipfs_client/crypto/signature_verifier.h b/library/include/ipfs_client/crypto/signature_verifier.h new file mode 100644 index 00000000..515db62f --- /dev/null +++ b/library/include/ipfs_client/crypto/signature_verifier.h @@ -0,0 +1,17 @@ +#ifndef IPFS_CHROMIUM_SIGNATURE_VERIFIER_H +#define IPFS_CHROMIUM_SIGNATURE_VERIFIER_H + +#include + +namespace ipfs::crypto { +class SignatureVerifier { + public: + virtual ~SignatureVerifier() noexcept {} + + virtual bool VerifySignature(ByteView signature, + ByteView data, + ByteView key) = 0; +}; +} // namespace ipfs::crypto + +#endif // IPFS_CHROMIUM_SIGNATURE_VERIFIER_H diff --git a/library/include/ipfs_client/signing_key_type.h b/library/include/ipfs_client/crypto/signing_key_type.h similarity index 88% rename from library/include/ipfs_client/signing_key_type.h rename to library/include/ipfs_client/crypto/signing_key_type.h index 4a74ad0f..e72efa31 100644 --- a/library/include/ipfs_client/signing_key_type.h +++ b/library/include/ipfs_client/crypto/signing_key_type.h @@ -1,7 +1,7 @@ #ifndef IPFS_SIGNING_KEY_TYPE_H_ #define IPFS_SIGNING_KEY_TYPE_H_ -namespace ipfs { +namespace ipfs::crypto { enum class SigningKeyType : int { RSA, Ed25519, diff --git a/library/include/ipfs_client/test_context.h b/library/include/ipfs_client/test_context.h index e7f58a91..de58bdab 100644 --- a/library/include/ipfs_client/test_context.h +++ b/library/include/ipfs_client/test_context.h @@ -114,13 +114,6 @@ class TestContext final : public ContextApi { << " bytes of JSON string and got " << oss.str(); return std::make_unique(data); } - bool VerifyKeySignature(SigningKeyType, - ByteView, - ByteView, - ByteView) const override { - GOOGLE_LOG(ERROR) << "TODO\n"; - return true; - } std::vector gateways_; boost::asio::io_context& io_; diff --git a/library/src/ipfs_client/context_api.cc b/library/src/ipfs_client/context_api.cc index b9fc82ae..07e09d66 100644 --- a/library/src/ipfs_client/context_api.cc +++ b/library/src/ipfs_client/context_api.cc @@ -1,6 +1,9 @@ #include #include "crypto/openssl_sha2_256.h" +#include "crypto/openssl_signature_verifier.h" + +#include "log_macros.h" using Self = ipfs::ContextApi; @@ -9,6 +12,12 @@ Self::ContextApi() { hashers_.emplace(HashType::SHA2_256, std::make_unique()); #endif +#if HAS_OPENSSL_EVP + verifiers_[SigningKeyType::RSA] = + std::make_unique(EVP_PKEY_RSA); + verifiers_[SigningKeyType::Ed25519] = + std::make_unique(EVP_PKEY_ED25519); +#endif } auto Self::Hash(HashType ht, ByteView data) @@ -19,6 +28,19 @@ auto Self::Hash(HashType ht, ByteView data) } return it->second->hash(data); } +bool Self::VerifyKeySignature(SigningKeyType typ, + ByteView signature, + ByteView data, + ByteView key_bytes) const { + auto it = verifiers_.find(typ); + if (verifiers_.end() == it || !(it->second)) { + LOG(ERROR) + << "Don't have an implementation to verify signature with key of type " + << static_cast(typ); + return false; + } + return it->second->VerifySignature(signature, data, key_bytes); +} void Self::SetGatewayRate(std::string_view, unsigned int) {} void Self::AddGateway(std::string_view gw) { diff --git a/library/src/ipfs_client/crypto/openssl_signature_verifier.cc b/library/src/ipfs_client/crypto/openssl_signature_verifier.cc new file mode 100644 index 00000000..9624a28c --- /dev/null +++ b/library/src/ipfs_client/crypto/openssl_signature_verifier.cc @@ -0,0 +1,35 @@ +#include "openssl_signature_verifier.h" + +#include + +#include "log_macros.h" + +#if HAS_OPENSSL_EVP + +using Self = ipfs::crypto::OpensslSignatureVerifier; + +Self::OpensslSignatureVerifier(int key_type) : openssl_key_type_{key_type} {} +Self::~OpensslSignatureVerifier() {} +bool Self::VerifySignature(ipfs::ByteView signature, + ipfs::ByteView data, + ipfs::ByteView key_bytes) { + auto* key_p = reinterpret_cast(key_bytes.data()); + auto* data_p = reinterpret_cast(data.data()); + auto* sig_p = reinterpret_cast(signature.data()); + auto kt = openssl_key_type_; + std::unique_ptr ctx{EVP_MD_CTX_new(), + &EVP_MD_CTX_free}; + std::unique_ptr key{ + EVP_PKEY_new_raw_public_key(kt, nullptr, key_p, key_bytes.size()), + &EVP_PKEY_free}; + if (!EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, key.get())) { + LOG(ERROR) << "EVP_DigestVerifyInit failed"; + return false; + } + auto result = + EVP_DigestVerify(ctx.get(), sig_p, signature.size(), data_p, data.size()); + VLOG(1) << "EVP_DigestVerify returned " << result; + return result == 1; +} + +#endif diff --git a/library/src/ipfs_client/crypto/openssl_signature_verifier.h b/library/src/ipfs_client/crypto/openssl_signature_verifier.h new file mode 100644 index 00000000..3f9b0e47 --- /dev/null +++ b/library/src/ipfs_client/crypto/openssl_signature_verifier.h @@ -0,0 +1,31 @@ +#ifndef IPFS_CHROMIUM_OPENSSL_SIGNATURE_VERIFIER_H +#define IPFS_CHROMIUM_OPENSSL_SIGNATURE_VERIFIER_H + +#include + +#if __has_include() +#include +#define HAS_OPENSSL_EVP 1 +#endif + +namespace ipfs::crypto { + +#if HAS_OPENSSL_EVP + +class OpensslSignatureVerifier : public SignatureVerifier { + int const openssl_key_type_; + + public: + OpensslSignatureVerifier(int); + ~OpensslSignatureVerifier() noexcept override; + + bool VerifySignature(ByteView signature, + ByteView data, + ByteView key) override; +}; + +#endif + +} // namespace ipfs::crypto + +#endif // IPFS_CHROMIUM_OPENSSL_SIGNATURE_VERIFIER_H diff --git a/library/src/ipfs_client/signing_key_type.cc b/library/src/ipfs_client/crypto/signing_key_type.cc similarity index 86% rename from library/src/ipfs_client/signing_key_type.cc rename to library/src/ipfs_client/crypto/signing_key_type.cc index b6489a47..ca0b797d 100644 --- a/library/src/ipfs_client/signing_key_type.cc +++ b/library/src/ipfs_client/crypto/signing_key_type.cc @@ -1,8 +1,8 @@ -#include +#include "ipfs_client/crypto/signing_key_type.h" #include -using T = ipfs::SigningKeyType; +using T = ipfs::crypto::SigningKeyType; namespace n = ipfs::ipns; // It is critically important that these 2 enumerations remain in-synch. diff --git a/library/src/ipfs_client/gw/gateway_request.cc b/library/src/ipfs_client/gw/gateway_request.cc index f5b1a839..2f23edb3 100644 --- a/library/src/ipfs_client/gw/gateway_request.cc +++ b/library/src/ipfs_client/gw/gateway_request.cc @@ -69,6 +69,9 @@ std::string Self::url_suffix() const { return "/routing/v1/providers/" + main_param; case Type::DnsLink: LOG(FATAL) << "Don't try to use HTTP(s) for DNS TXT records."; +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunreachable-code-return" +#endif return {}; case Type::Identity: case Type::Zombie: diff --git a/library/src/ipfs_client/identity_cid.cc b/library/src/ipfs_client/identity_cid.cc index 9ea2421d..0964f8d8 100644 --- a/library/src/ipfs_client/identity_cid.cc +++ b/library/src/ipfs_client/identity_cid.cc @@ -14,6 +14,9 @@ auto Self::forText(std::string_view txt) -> Cid { } else { LOG(FATAL) << "We really shouldn't be able to fail to 'hash' using identity."; +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wunreachable-code-return" +#endif return forText("Unreachable"); } } \ No newline at end of file diff --git a/library/src/ipfs_client/ipns_record.cc b/library/src/ipfs_client/ipns_record.cc index 81e52065..32a441b6 100644 --- a/library/src/ipfs_client/ipns_record.cc +++ b/library/src/ipfs_client/ipns_record.cc @@ -160,8 +160,8 @@ auto ipfs::ValidateIpnsRecord(ipfs::ByteView top_level_bytes, bytes_str.size()}; ByteView key_bytes{reinterpret_cast(pk.data().data()), pk.data().size()}; - if (!api.VerifyKeySignature(static_cast(pk.type()), signature, - bytes, key_bytes)) { + if (!api.VerifyKeySignature(static_cast(pk.type()), + signature, bytes, key_bytes)) { LOG(ERROR) << "Verification failed!!"; return {}; } diff --git a/library/src/ipfs_client/ipns_record_unittest.cc b/library/src/ipfs_client/ipns_record_unittest.cc index f8135196..dc0ee366 100644 --- a/library/src/ipfs_client/ipns_record_unittest.cc +++ b/library/src/ipfs_client/ipns_record_unittest.cc @@ -46,7 +46,7 @@ struct Api final : public i::ContextApi { cbors.pop_front(); return std::move(r); } - bool verify_key_signature(ipfs::SigningKeyType, + bool verify_key_signature(ipfs::crypto::SigningKeyType, ipfs::ByteView signature, ipfs::ByteView data, ipfs::ByteView key_bytes) {