From b003bea318e4bb725add79a410559145a823b007 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Fri, 19 Jan 2024 16:25:48 -0500 Subject: [PATCH] 122.0.6253.3 --- .../chrome/browser/BUILD.gn.patch | 27 + .../chrome/browser/about_flags.cc.patch | 38 + ...me_autocomplete_scheme_classifier.cc.patch | 50 + .../chrome_content_browser_client.cc.patch | 78 + .../chrome/browser/flag-metadata.json.patch | 16 + .../chrome/browser/flag_descriptions.cc.patch | 16 + .../chrome/browser/flag_descriptions.h.patch | 24 + .../chrome/browser/ipfs_extra_parts.cc | 10 + .../chrome/browser/ipfs_extra_parts.h | 10 + .../browser/prefs/browser_prefs.cc.patch | 36 + .../common/chrome_content_client.cc.patch | 17 + .../components/cbor/reader.cc.patch | 44 + .../components/cbor/reader.h.patch | 26 + .../components/cbor/reader_unittest.cc.patch | 47 + .../components/cbor/values.cc.patch | 145 + .../components/cbor/values.h.patch | 79 + .../components/cbor/writer.cc.patch | 14 + .../components/cbor/writer_unittest.cc.patch | 36 + .../clipboard_recent_content_generic.cc.patch | 13 + .../net/dns/dns_config_service_linux.cc.patch | 18 + .../weborigin/scheme_registry.cc.patch | 13 + .../122.0.6253.3/url/BUILD.gn.patch | 32 + .../122.0.6253.3/url/url_canon.h.patch | 28 + .../122.0.6253.3/url/url_canon_ipfs.cc | 55 + .../122.0.6253.3/url/url_util.cc.patch | 22 + cmake/chromium.py | 7 + component/CMakeLists.txt | 12 +- component/patches/122.0.6182.0.patch | 11249 ---------------- library/src/ipfs_client/gw/gateway_request.cc | 3 + library/src/ipfs_client/identity_cid.cc | 3 + ...pXqJ2GXKi8NEcefpobtk7DhM1HvJpU1HcyCipbYMeM | 2 - ...W7kUoTDuoVUY6dky8pdeF4mV5yZoxA1g9S23E2MJ2s | 2 - ...plrgl3hvwu2wrbnfgoj2eau5eqjzjglsmwq2ewxpyy | 1 - 33 files changed, 918 insertions(+), 11255 deletions(-) create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/BUILD.gn.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/about_flags.cc.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/chrome_content_browser_client.cc.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/flag-metadata.json.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.cc.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.h.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.cc create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.h create mode 100644 chromium_edits/122.0.6253.3/chrome/browser/prefs/browser_prefs.cc.patch create mode 100644 chromium_edits/122.0.6253.3/chrome/common/chrome_content_client.cc.patch create mode 100644 chromium_edits/122.0.6253.3/components/cbor/reader.cc.patch create mode 100644 chromium_edits/122.0.6253.3/components/cbor/reader.h.patch create mode 100644 chromium_edits/122.0.6253.3/components/cbor/reader_unittest.cc.patch create mode 100644 chromium_edits/122.0.6253.3/components/cbor/values.cc.patch create mode 100644 chromium_edits/122.0.6253.3/components/cbor/values.h.patch create mode 100644 chromium_edits/122.0.6253.3/components/cbor/writer.cc.patch create mode 100644 chromium_edits/122.0.6253.3/components/cbor/writer_unittest.cc.patch create mode 100644 chromium_edits/122.0.6253.3/components/open_from_clipboard/clipboard_recent_content_generic.cc.patch create mode 100644 chromium_edits/122.0.6253.3/net/dns/dns_config_service_linux.cc.patch create mode 100644 chromium_edits/122.0.6253.3/third_party/blink/renderer/platform/weborigin/scheme_registry.cc.patch create mode 100644 chromium_edits/122.0.6253.3/url/BUILD.gn.patch create mode 100644 chromium_edits/122.0.6253.3/url/url_canon.h.patch create mode 100644 chromium_edits/122.0.6253.3/url/url_canon_ipfs.cc create mode 100644 chromium_edits/122.0.6253.3/url/url_util.cc.patch delete mode 100644 component/patches/122.0.6182.0.patch delete mode 100644 test_data/blocks/QmTopXqJ2GXKi8NEcefpobtk7DhM1HvJpU1HcyCipbYMeM delete mode 100644 test_data/blocks/QmTtW7kUoTDuoVUY6dky8pdeF4mV5yZoxA1g9S23E2MJ2s delete mode 100644 test_data/blocks/bafkreibopuwahkkqplrgl3hvwu2wrbnfgoj2eau5eqjzjglsmwq2ewxpyy diff --git a/chromium_edits/122.0.6253.3/chrome/browser/BUILD.gn.patch b/chromium_edits/122.0.6253.3/chrome/browser/BUILD.gn.patch new file mode 100644 index 00000000..528486f6 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/BUILD.gn.patch @@ -0,0 +1,27 @@ +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +index 265908862de90..d1c9b9729b4ee 100644 +--- a/chrome/browser/BUILD.gn ++++ b/chrome/browser/BUILD.gn +@@ -39,6 +39,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") +@@ -2627,6 +2628,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/chromium_edits/122.0.6253.3/chrome/browser/about_flags.cc.patch b/chromium_edits/122.0.6253.3/chrome/browser/about_flags.cc.patch new file mode 100644 index 00000000..3b6e9316 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/about_flags.cc.patch @@ -0,0 +1,38 @@ +diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc +index 887fbf40830b6..90887fa54aed1 100644 +--- a/chrome/browser/about_flags.cc ++++ b/chrome/browser/about_flags.cc +@@ -214,6 +214,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/ozone_buildflags.h" +@@ -310,6 +311,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 +@@ -9554,6 +9559,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/chromium_edits/122.0.6253.3/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc.patch b/chromium_edits/122.0.6253.3/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc.patch new file mode 100644 index 00000000..4d172311 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.cc.patch @@ -0,0 +1,50 @@ +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/chromium_edits/122.0.6253.3/chrome/browser/chrome_content_browser_client.cc.patch b/chromium_edits/122.0.6253.3/chrome/browser/chrome_content_browser_client.cc.patch new file mode 100644 index 00000000..cc9a0a53 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/chrome_content_browser_client.cc.patch @@ -0,0 +1,78 @@ +diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc +index 9e48a38fe3576..7bb51d58e7ca0 100644 +--- a/chrome/browser/chrome_content_browser_client.cc ++++ b/chrome/browser/chrome_content_browser_client.cc +@@ -374,6 +374,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" +@@ -495,6 +496,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" +@@ -1676,6 +1684,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; + } + +@@ -5990,12 +6003,25 @@ void ChromeContentBrowserClient:: + const std::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) { +@@ -6137,6 +6163,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/chromium_edits/122.0.6253.3/chrome/browser/flag-metadata.json.patch b/chromium_edits/122.0.6253.3/chrome/browser/flag-metadata.json.patch new file mode 100644 index 00000000..416cff54 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/flag-metadata.json.patch @@ -0,0 +1,16 @@ +diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json +index 78b276113ee84..1518999f1a44f 100644 +--- a/chrome/browser/flag-metadata.json ++++ b/chrome/browser/flag-metadata.json +@@ -2991,6 +2991,11 @@ + "owners": [ "adamta@google.com", "chrome-feed-fundamentals@google.com" ], + "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/chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.cc.patch b/chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.cc.patch new file mode 100644 index 00000000..f233dd3d --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.cc.patch @@ -0,0 +1,16 @@ +diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc +index 8d79fd20d9812..a27a7530018f2 100644 +--- a/chrome/browser/flag_descriptions.cc ++++ b/chrome/browser/flag_descriptions.cc +@@ -292,6 +292,11 @@ const char kEnableBenchmarkingChoiceDefaultFeatureStates[] = + const char kEnableBenchmarkingChoiceMatchFieldTrialTestingConfig[] = + "Match Field Trial Testing Config"; + ++#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/chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.h.patch b/chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.h.patch new file mode 100644 index 00000000..c0417db5 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/flag_descriptions.h.patch @@ -0,0 +1,24 @@ +diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h +index 4b282cb875813..256395ccb420f 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 kEnableBenchmarkingChoiceDisabled[]; + extern const char kEnableBenchmarkingChoiceDefaultFeatureStates[]; + extern const char kEnableBenchmarkingChoiceMatchFieldTrialTestingConfig[]; + ++#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/chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.cc b/chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.cc new file mode 100644 index 00000000..90d2596f --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.cc @@ -0,0 +1,10 @@ +#include "ipfs_extra_parts.h" + +#include "profiles/profile.h" + +#include + +void IpfsExtraParts::PostProfileInit(Profile* profile, bool /* is_initial_profile */ ) { + DCHECK(profile); + ipfs::InterRequestState::CreateForBrowserContext(profile, profile->GetPrefs()); +} diff --git a/chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.h b/chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.h new file mode 100644 index 00000000..2059c437 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/ipfs_extra_parts.h @@ -0,0 +1,10 @@ +#ifndef IPFS_EXTRA_PART_H_ +#define IPFS_EXTRA_PART_H_ + +#include + +class IpfsExtraParts : public ChromeBrowserMainExtraParts { + void PostProfileInit(Profile* profile, bool is_initial_profile) override; +}; + +#endif // IPFS_EXTRA_PART_H_ diff --git a/chromium_edits/122.0.6253.3/chrome/browser/prefs/browser_prefs.cc.patch b/chromium_edits/122.0.6253.3/chrome/browser/prefs/browser_prefs.cc.patch new file mode 100644 index 00000000..f61c9ebb --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/browser/prefs/browser_prefs.cc.patch @@ -0,0 +1,36 @@ +diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc +index 23aeba22f7f83..00d8fcee6538f 100644 +--- a/chrome/browser/prefs/browser_prefs.cc ++++ b/chrome/browser/prefs/browser_prefs.cc +@@ -189,6 +189,7 @@ + #include "ppapi/buildflags/buildflags.h" + #include "printing/buildflags/buildflags.h" + #include "rlz/buildflags/buildflags.h" ++#include "third_party/ipfs_client/ipfs_buildflags.h" + + #if BUILDFLAG(ENABLE_BACKGROUND_MODE) + #include "chrome/browser/background/background_mode_manager.h" +@@ -233,6 +234,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 +@@ -1682,6 +1688,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/chromium_edits/122.0.6253.3/chrome/common/chrome_content_client.cc.patch b/chromium_edits/122.0.6253.3/chrome/common/chrome_content_client.cc.patch new file mode 100644 index 00000000..5bd09f16 --- /dev/null +++ b/chromium_edits/122.0.6253.3/chrome/common/chrome_content_client.cc.patch @@ -0,0 +1,17 @@ +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/chromium_edits/122.0.6253.3/components/cbor/reader.cc.patch b/chromium_edits/122.0.6253.3/components/cbor/reader.cc.patch new file mode 100644 index 00000000..aed86452 --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/cbor/reader.cc.patch @@ -0,0 +1,44 @@ +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/chromium_edits/122.0.6253.3/components/cbor/reader.h.patch b/chromium_edits/122.0.6253.3/components/cbor/reader.h.patch new file mode 100644 index 00000000..fb821165 --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/cbor/reader.h.patch @@ -0,0 +1,26 @@ +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/chromium_edits/122.0.6253.3/components/cbor/reader_unittest.cc.patch b/chromium_edits/122.0.6253.3/components/cbor/reader_unittest.cc.patch new file mode 100644 index 00000000..3f657dc3 --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/cbor/reader_unittest.cc.patch @@ -0,0 +1,47 @@ +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/chromium_edits/122.0.6253.3/components/cbor/values.cc.patch b/chromium_edits/122.0.6253.3/components/cbor/values.cc.patch new file mode 100644 index 00000000..ddbab2b3 --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/cbor/values.cc.patch @@ -0,0 +1,145 @@ +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/chromium_edits/122.0.6253.3/components/cbor/values.h.patch b/chromium_edits/122.0.6253.3/components/cbor/values.h.patch new file mode 100644 index 00000000..ca39df01 --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/cbor/values.h.patch @@ -0,0 +1,79 @@ +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/chromium_edits/122.0.6253.3/components/cbor/writer.cc.patch b/chromium_edits/122.0.6253.3/components/cbor/writer.cc.patch new file mode 100644 index 00000000..21fe28ce --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/cbor/writer.cc.patch @@ -0,0 +1,14 @@ +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/chromium_edits/122.0.6253.3/components/cbor/writer_unittest.cc.patch b/chromium_edits/122.0.6253.3/components/cbor/writer_unittest.cc.patch new file mode 100644 index 00000000..240fee83 --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/cbor/writer_unittest.cc.patch @@ -0,0 +1,36 @@ +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/chromium_edits/122.0.6253.3/components/open_from_clipboard/clipboard_recent_content_generic.cc.patch b/chromium_edits/122.0.6253.3/components/open_from_clipboard/clipboard_recent_content_generic.cc.patch new file mode 100644 index 00000000..891b53df --- /dev/null +++ b/chromium_edits/122.0.6253.3/components/open_from_clipboard/clipboard_recent_content_generic.cc.patch @@ -0,0 +1,13 @@ +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/chromium_edits/122.0.6253.3/net/dns/dns_config_service_linux.cc.patch b/chromium_edits/122.0.6253.3/net/dns/dns_config_service_linux.cc.patch new file mode 100644 index 00000000..1d694639 --- /dev/null +++ b/chromium_edits/122.0.6253.3/net/dns/dns_config_service_linux.cc.patch @@ -0,0 +1,18 @@ +diff --git a/net/dns/dns_config_service_linux.cc b/net/dns/dns_config_service_linux.cc +index 3c01e724ff069..24788851ff39f 100644 +--- a/net/dns/dns_config_service_linux.cc ++++ b/net/dns/dns_config_service_linux.cc +@@ -271,11 +271,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/chromium_edits/122.0.6253.3/third_party/blink/renderer/platform/weborigin/scheme_registry.cc.patch b/chromium_edits/122.0.6253.3/third_party/blink/renderer/platform/weborigin/scheme_registry.cc.patch new file mode 100644 index 00000000..119d72b2 --- /dev/null +++ b/chromium_edits/122.0.6253.3/third_party/blink/renderer/platform/weborigin/scheme_registry.cc.patch @@ -0,0 +1,13 @@ +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/chromium_edits/122.0.6253.3/url/BUILD.gn.patch b/chromium_edits/122.0.6253.3/url/BUILD.gn.patch new file mode 100644 index 00000000..cc358ad4 --- /dev/null +++ b/chromium_edits/122.0.6253.3/url/BUILD.gn.patch @@ -0,0 +1,32 @@ +diff --git a/url/BUILD.gn b/url/BUILD.gn +index b5edb89f7698f..d299856674d7d 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") +@@ -68,6 +69,7 @@ component("url") { + public_deps = [ + "//base", + "//build:robolectric_buildflags", ++ "//third_party/ipfs_client:ipfs_buildflags", + ] + + configs += [ "//build/config/compiler:wexit_time_destructors" ] +@@ -90,6 +92,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/chromium_edits/122.0.6253.3/url/url_canon.h.patch b/chromium_edits/122.0.6253.3/url/url_canon.h.patch new file mode 100644 index 00000000..7ffd3a2f --- /dev/null +++ b/chromium_edits/122.0.6253.3/url/url_canon.h.patch @@ -0,0 +1,28 @@ +diff --git a/url/url_canon.h b/url/url_canon.h +index 8c48f9825d8cf..b9ad961e1b123 100644 +--- a/url/url_canon.h ++++ b/url/url_canon.h +@@ -804,6 +804,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/chromium_edits/122.0.6253.3/url/url_canon_ipfs.cc b/chromium_edits/122.0.6253.3/url/url_canon_ipfs.cc new file mode 100644 index 00000000..d7c9fdc7 --- /dev/null +++ b/chromium_edits/122.0.6253.3/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, + 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/chromium_edits/122.0.6253.3/url/url_util.cc.patch b/chromium_edits/122.0.6253.3/url/url_util.cc.patch new file mode 100644 index 00000000..ee9cb33b --- /dev/null +++ b/chromium_edits/122.0.6253.3/url/url_util.cc.patch @@ -0,0 +1,22 @@ +diff --git a/url/url_util.cc b/url/url_util.cc +index c57c19ba2773a..7951b1e1145c9 100644 +--- a/url/url_util.cc ++++ b/url/url_util.cc +@@ -274,8 +274,15 @@ bool DoCanonicalize(const CHAR* spec, + } else if (DoCompareSchemeComponent(spec, scheme, url::kFileSystemScheme)) { + // Filesystem URLs are special. + ParseFileSystemURL(spec, spec_len, &parsed_input); +- success = CanonicalizeFileSystemURL(spec, parsed_input, charset_converter, +- output, output_parsed); ++ success = CanonicalizeFileSystemURL(spec, parsed_input, ++ 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. diff --git a/cmake/chromium.py b/cmake/chromium.py index f3dfc84e..cb1b3d7e 100755 --- a/cmake/chromium.py +++ b/cmake/chromium.py @@ -27,6 +27,13 @@ patcher = Patcher(src, git_binary, build_type) UPDATED = 'chromium_source_updated' +prof_gn = profile.lower() +electron_args_file = join(src, 'electron', 'build', 'args', prof_gn + '.gn') +if isfile(electron_args_file): + toks = gnargs.split() + #electron defines is_debug by profile convention, and unfortunately they disagree with me + toks = filter(lambda x: not x.startswith('is_debug'), toks) + gnargs = ' '.join(toks) + f' import(\"//electron/build/args/testing.gn\") ' def run(args, fail_ok=False): if isdir(src): diff --git a/component/CMakeLists.txt b/component/CMakeLists.txt index 7cd3e38f..2be3f358 100644 --- a/component/CMakeLists.txt +++ b/component/CMakeLists.txt @@ -104,6 +104,11 @@ add_custom_target(build_browser COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/cmake/chromium.py" "${CMAKE_BINARY_DIR}" chrome DEPENDS chromium_ordering_issue ) +add_custom_target(build_electron + COMMENT "Building 'electron' in Chromium source tree, output in out/${CHROMIUM_PROFILE}" + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/cmake/chromium.py" "${CMAKE_BINARY_DIR}" electron + DEPENDS chromium_ordering_issue +) if(CHROMIUM_PROFILE STREQUAL win) set(CHROMIUM_PACKAGE_TARGET mini_installer) @@ -124,13 +129,18 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release") ) endif() -#Symlink for convenience +#Symlinks for convenience add_custom_command( TARGET build_browser POST_BUILD COMMAND "${CMAKE_COMMAND}" -E create_symlink "${CHROMIUM_SOURCE_TREE}/out/${CHROMIUM_PROFILE}/chrome" "${CMAKE_BINARY_DIR}/browser" COMMENT "Symlinking... your browser can be found here: ${CMAKE_BINARY_DIR}/browser or ${CHROMIUM_SOURCE_TREE}/out/${CHROMIUM_PROFILE}/chrome" ) +add_custom_command( + TARGET build_electron + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E create_symlink "${CHROMIUM_SOURCE_TREE}/out/${CHROMIUM_PROFILE}/electron" "${CMAKE_BINARY_DIR}/electron" +) add_custom_target(inc_link ALL 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/src/ipfs_client/gw/gateway_request.cc b/library/src/ipfs_client/gw/gateway_request.cc index 39759943..8edb5b2f 100644 --- a/library/src/ipfs_client/gw/gateway_request.cc +++ b/library/src/ipfs_client/gw/gateway_request.cc @@ -70,6 +70,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/test_data/blocks/QmTopXqJ2GXKi8NEcefpobtk7DhM1HvJpU1HcyCipbYMeM b/test_data/blocks/QmTopXqJ2GXKi8NEcefpobtk7DhM1HvJpU1HcyCipbYMeM deleted file mode 100644 index 61b345ae..00000000 --- a/test_data/blocks/QmTopXqJ2GXKi8NEcefpobtk7DhM1HvJpU1HcyCipbYMeM +++ /dev/null @@ -1,2 +0,0 @@ - -˜Xd2 Xd3 Xd4 Xd5 Xd6 Xd7 Xd8 Xd9 Xe0 Xe1 Xe2 Xe3 Xe4 Xe5 Xe6 Xe7 Xe8 Xe9 Xf0 Xf1 Xf2 Xf3 Xf4 Xf5 Xf6 Xf7 Xf8 Xf9 Xg0 Xg1 Xg2 Xg3 Xg4 Xg5 Xg6 Xg7  \ No newline at end of file diff --git a/test_data/blocks/QmTtW7kUoTDuoVUY6dky8pdeF4mV5yZoxA1g9S23E2MJ2s b/test_data/blocks/QmTtW7kUoTDuoVUY6dky8pdeF4mV5yZoxA1g9S23E2MJ2s deleted file mode 100644 index 8264caab..00000000 --- a/test_data/blocks/QmTtW7kUoTDuoVUY6dky8pdeF4mV5yZoxA1g9S23E2MJ2s +++ /dev/null @@ -1,2 +0,0 @@ - -˜Cx6 Cx7 Cx8 Cx9 Cy0 Cy1 Cy2 Cy3 Cy4 Cy5 Cy6 Cy7 Cy8 Cy9 Cz0 Cz1 Cz2 Cz3 Cz4 Cz5 Cz6 Cz7 Cz8 Cz9 Da0 Da1 Da2 Da3 Da4 Da5 Da6 Da7 Da8 Da9 Db0 Db1  \ No newline at end of file diff --git a/test_data/blocks/bafkreibopuwahkkqplrgl3hvwu2wrbnfgoj2eau5eqjzjglsmwq2ewxpyy b/test_data/blocks/bafkreibopuwahkkqplrgl3hvwu2wrbnfgoj2eau5eqjzjglsmwq2ewxpyy deleted file mode 100644 index 3410062b..00000000 --- a/test_data/blocks/bafkreibopuwahkkqplrgl3hvwu2wrbnfgoj2eau5eqjzjglsmwq2ewxpyy +++ /dev/null @@ -1 +0,0 @@ -c \ No newline at end of file