diff --git a/cmake/patch.py b/cmake/patch.py index e880c9f4..e6a8db2c 100755 --- a/cmake/patch.py +++ b/cmake/patch.py @@ -151,7 +151,7 @@ def electron_version(self, branch='main'): def unavailable(self): avail = list(map(as_int, self.available())) version_set = {} - fuzz = 115 + fuzz = 59876 def check(version, version_set, s): i = as_int(version) by = (fuzz,0) diff --git a/component/block_http_request.cc b/component/block_http_request.cc new file mode 100644 index 00000000..4fe91b1e --- /dev/null +++ b/component/block_http_request.cc @@ -0,0 +1,63 @@ +#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, + raw_ptr loader_factory) + : callback_{cb} { + auto req = std::make_unique(); + req->url = GURL{req_inf.url}; + req->priority = net::HIGHEST; // TODO + if (!req_inf.accept.empty()) { + req->headers.SetHeader("Accept", req_inf.accept); + } + using L = network::SimpleURLLoader; + loader_ = L::Create(std::move(req), kTrafficAnnotation, FROM_HERE); + loader_->SetTimeoutDuration(base::Seconds(req_inf.timeout_seconds)); + loader_->SetAllowHttpErrorResults(true); + auto bound = base::BindOnce(&Self::OnResponse, base::Unretained(this)); + DCHECK(loader_factory); + loader_->DownloadToString(loader_factory, std::move(bound), + gw::BLOCK_RESPONSE_BUFFER_SIZE); +} +Self::~BlockHttpRequest() noexcept {} +void Self::OnResponse(std::unique_ptr body) { + auto const* head = loader_->ResponseInfo(); + auto status_text = head->headers->GetStatusText(); + LOG(INFO) << "Handling body of size " << body->size() << " and status of " + << status_text; + int status = std::atoi(status_text.c_str()); + auto hdrs = [head](std::string_view k) { + std::string val; + head->headers->EnumerateHeader(nullptr, k, &val); + return val; + }; + callback_(status, *body, hdrs); +} +void Self::self_ownership(std::unique_ptr& p) { + self_.swap(p); +} diff --git a/component/block_http_request.h b/component/block_http_request.h new file mode 100644 index 00000000..3f664a32 --- /dev/null +++ b/component/block_http_request.h @@ -0,0 +1,38 @@ +#ifndef IPFS_BLOCK_HTTP_REQUEST_H_ +#define IPFS_BLOCK_HTTP_REQUEST_H_ + +#include + +#include + +namespace network { +struct ResourceRequest; +class SimpleURLLoader; +} // namespace network +namespace network::mojom { +class URLLoaderFactory; +} + +namespace ipfs { +class BlockHttpRequest { + // TODO ween oneself off of SimpleURLLoader + // std::array buffer_; + std::unique_ptr self_; + std::unique_ptr loader_; + + public: + using HttpCompleteCallback = ipfs::ContextApi::HttpCompleteCallback; + BlockHttpRequest(ipfs::HttpRequestDescription, + HttpCompleteCallback, + raw_ptr); + ~BlockHttpRequest() noexcept; + void self_ownership(std::unique_ptr&); + + private: + HttpCompleteCallback callback_; + + void OnResponse(std::unique_ptr body); +}; +} // namespace ipfs + +#endif // IPFS_BLOCK_HTTP_REQUEST_H_ diff --git a/component/cache_requestor.cc b/component/cache_requestor.cc index 19426a97..ed0d27ba 100644 --- a/component/cache_requestor.cc +++ b/component/cache_requestor.cc @@ -1,6 +1,6 @@ #include "cache_requestor.h" -#include "gateway_requests.h" +#include "chromium_ipfs_context.h" #include "inter_request_state.h" #include diff --git a/component/gateway_requests.cc b/component/chromium_ipfs_context.cc similarity index 82% rename from component/gateway_requests.cc rename to component/chromium_ipfs_context.cc index 7e609028..13df2118 100644 --- a/component/gateway_requests.cc +++ b/component/chromium_ipfs_context.cc @@ -1,18 +1,20 @@ -#include "gateway_requests.h" +#include "chromium_ipfs_context.h" +#include "block_http_request.h" #include "crypto_api.h" #include "inter_request_state.h" #include "ipns_cbor.h" +#include #include "base/strings/escape.h" #include "net/base/mime_sniffer.h" #include "net/base/mime_util.h" #include "services/network/public/cpp/resource_request.h" -#include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_response_head.mojom.h" #include "url/gurl.h" #include +#include #include #include @@ -20,7 +22,7 @@ #include -using Self = ipfs::GatewayRequests; +using Self = ipfs::ChromiumIpfsContext; constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation = net::DefineNetworkTrafficAnnotation("ipfs_gateway_request", R"( @@ -207,17 +209,19 @@ bool Self::ProcessResponse(BusyGateway& gw, auto duration = (end_time - start_time).InMillisecondsRoundedUp(); if (cid.value().content_type == libp2p::multi::MulticodecType::Code::LIBP2P_KEY) { - auto as_peer = libp2p::peer::PeerId::fromHash(cid.value().content_address); - if (!as_peer.has_value()) { - LOG(INFO) << cid_str - << " has the codec of being a libp2p key, like one would " - "expect of a Peer ID, but it does not parse as a Peer ID."; - return false; - } + // auto as_peer = + // libp2p::peer::PeerId::fromHash(cid.value().content_address); if + // (!as_peer.has_value()) { + // LOG(INFO) << cid_str + // << " has the codec of being a libp2p key, like one would " + // "expect of a Peer ID, but it does not parse as a Peer + // ID."; + // return false; + // } auto* bytes = reinterpret_cast(body->data()); - auto record = - ValidateIpnsRecord({bytes, body->size()}, as_peer.value(), - crypto_api::VerifySignature, ParseCborIpns); + // auto record = ValidateIpnsRecord({bytes, body->size()}, + // as_peer.value(), crypto_api::VerifySignature, ParseCborIpns); + auto record = ValidateIpnsRecord({bytes, body->size()}, cid.value(), *this); if (!record.has_value()) { LOG(ERROR) << "IPNS record failed to validate! From: " << gw.url(); return false; @@ -247,7 +251,9 @@ bool Self::ProcessResponse(BusyGateway& gw, auto& orc = state_->orchestrator(); orc.add_node(cid_str, ipld::DagNode::fromBlock(block)); if (gw.srcreq) { - orc.build_response(gw.srcreq->dependent); + if (gw.srcreq->dependent->ready_after()) { + orc.build_response(gw.srcreq->dependent); + } } else { LOG(ERROR) << "This BusyGateway with response has no top-level " "IpfsRequest associated with it " @@ -291,13 +297,14 @@ std::string Self::MimeType(std::string extension, } else { result.clear(); } - if (net::SniffMimeType({content.data(), content.size()}, GURL{url}, result, + auto head_size = std::min(content.size(), 999'999UL); + if (net::SniffMimeType({content.data(), head_size}, GURL{url}, result, net::ForceSniffFileUrlsForHtml::kDisabled, &result)) { VLOG(1) << "Got " << result << " from content of " << url; } if (result.empty() || result == "application/octet-stream") { // C'mon, man - net::SniffMimeTypeFromLocalData({content.data(), content.size()}, &result); + net::SniffMimeTypeFromLocalData({content.data(), head_size}, &result); LOG(INFO) << "Falling all the way back to content type " << result; } return result; @@ -319,14 +326,45 @@ void Self::RequestByCid(std::string cid, prio, {}); sched_.IssueRequests(me); } +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); + }; + dns_reqs_[host] = std::make_unique(host, res, don_wrap, + network_context_.get()); +} +void Self::SendHttpRequest(HttpRequestDescription req_inf, + HttpCompleteCallback cb) const { + auto ptr = std::make_unique(req_inf, cb, loader_factory_); + ptr->self_ownership(ptr); +} +auto Self::deserialize_cbor(ByteView bytes) const -> IpnsCborEntry { + return ParseCborIpns(bytes); +} +bool Self::verify_key_signature(SigningKeyType t, + ByteView signature, + ByteView data, + ByteView key_bytes) const { + return crypto_api::VerifySignature(static_cast(t), signature, + data, key_bytes); +} -Self::GatewayRequests(InterRequestState& state) +Self::ChromiumIpfsContext(InterRequestState& state) : state_{state}, sched_([this]() { return state_->gateways().GenerateList(); }) {} -Self::~GatewayRequests() { +Self::~ChromiumIpfsContext() { LOG(WARNING) << "API dtor - are all URIs loaded?"; } Self::GatewayUrlLoader::GatewayUrlLoader(BusyGateway&& bg) : GatewayRequest(std::move(bg)) {} Self::GatewayUrlLoader::~GatewayUrlLoader() noexcept {} + diff --git a/component/gateway_requests.h b/component/chromium_ipfs_context.h similarity index 67% rename from component/gateway_requests.h rename to component/chromium_ipfs_context.h index 44f0588e..7cafbbbc 100644 --- a/component/gateway_requests.h +++ b/component/chromium_ipfs_context.h @@ -1,5 +1,7 @@ -#ifndef IPFS_GATEWAY_REQUESTS_H_ -#define IPFS_GATEWAY_REQUESTS_H_ +#ifndef IPFS_CHROMIUM_IPFS_CONTEXT_H_ +#define IPFS_CHROMIUM_IPFS_CONTEXT_H_ + +#include "dns_txt_request.h" #include #include @@ -24,7 +26,7 @@ class InterRequestState; class IpfsRequest; class NetworkRequestor; -class GatewayRequests final : public ContextApi { +class ChromiumIpfsContext final : public ContextApi { struct GatewayUrlLoader : public ipfs::GatewayRequest { GatewayUrlLoader(BusyGateway&&); GatewayUrlLoader(GatewayRequest&&); @@ -33,9 +35,11 @@ class GatewayRequests final : public ContextApi { }; raw_ptr loader_factory_ = nullptr; + raw_ptr network_context_; // TODO initialize raw_ref state_; Scheduler sched_; std::function)> disc_cb_; + std::map> dns_reqs_; void Request(std::string task, std::shared_ptr, Priority); std::shared_ptr InitiateGatewayRequest(BusyGateway) override; @@ -43,6 +47,16 @@ class GatewayRequests final : public ContextApi { 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; + IpnsCborEntry deserialize_cbor(ByteView) const override; + bool verify_key_signature(SigningKeyType, + ByteView signature, + ByteView data, + ByteView key_bytes) const override; void OnResponse(std::shared_ptr, std::shared_ptr, @@ -59,8 +73,8 @@ class GatewayRequests final : public ContextApi { Priority); public: - GatewayRequests(InterRequestState&); - ~GatewayRequests(); + ChromiumIpfsContext(InterRequestState&); + ~ChromiumIpfsContext(); void SetLoaderFactory(network::mojom::URLLoaderFactory&); Scheduler& scheduler(); void Discover(std::function)>) override; @@ -68,4 +82,4 @@ class GatewayRequests final : public ContextApi { } // namespace ipfs -#endif // IPFS_GATEWAY_REQUESTS_H_ +#endif // IPFS_CHROMIUM_IPFS_CONTEXT_H_ diff --git a/component/dns_txt_request.cc b/component/dns_txt_request.cc new file mode 100644 index 00000000..7be445b6 --- /dev/null +++ b/component/dns_txt_request.cc @@ -0,0 +1,37 @@ +#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) { + results_callback_(results); +} +void Self::OnComplete(int32_t, + const ::net::ResolveErrorInfo&, + const absl::optional<::net::AddressList>&, + const absl::optional&) { + completion_callback_(); +} diff --git a/component/dns_txt_request.h b/component/dns_txt_request.h new file mode 100644 index 00000000..a6c72e46 --- /dev/null +++ b/component/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/component/inter_request_state.cc b/component/inter_request_state.cc index ee1e2041..02b72b9c 100644 --- a/component/inter_request_state.cc +++ b/component/inter_request_state.cc @@ -1,6 +1,6 @@ #include "inter_request_state.h" -#include "gateway_requests.h" +#include "chromium_ipfs_context.h" #include "network_requestor.h" #include "base/logging.h" @@ -59,12 +59,12 @@ auto Self::requestor() -> BlockRequestor& { } return requestor_; } -std::shared_ptr Self::api() { +std::shared_ptr Self::api() { auto existing = api_.lock(); if (existing) { return existing; } - auto created = std::make_shared(*this); + auto created = std::make_shared(*this); api_ = created; auto t = std::time(nullptr); if (t - last_discovery_ > 300) { @@ -90,7 +90,7 @@ void send_gateway_request(Self* me, struct DagListenerAdapter final : public ipfs::DagListener { std::shared_ptr gw_req; std::string bytes; - std::shared_ptr api; + std::shared_ptr api; void ReceiveBlockBytes(std::string_view b) override { LOG(INFO) << "DagListenerAdapter::ReceiveBlockBytes(" << b.size() << "B)"; bytes.assign(b); @@ -119,22 +119,12 @@ void send_gateway_request(Self* me, 9, req); sched.IssueRequests(me->api()); } -std::string detect_mime(Self* me, - std::string a, - std::string_view b, - std::string const& c) { - auto api = me->api(); - return static_cast(api.get())->MimeType(a, b, c); -} } // namespace auto Self::orchestrator() -> Orchestrator& { if (!orc_) { auto gwreq = [this](auto p) { send_gateway_request(this, p); }; - auto mimer = [this](auto a, auto b, auto& c) { - return detect_mime(this, a, b, c); - }; - orc_ = std::make_shared(gwreq, mimer); + orc_ = std::make_shared(gwreq, api()); } return *orc_; } diff --git a/component/inter_request_state.h b/component/inter_request_state.h index 6b5f1354..32ac0c66 100644 --- a/component/inter_request_state.h +++ b/component/inter_request_state.h @@ -17,13 +17,13 @@ class BrowserContext; namespace ipfs { class Scheduler; -class GatewayRequests; +class ChromiumIpfsContext; class InterRequestState : public base::SupportsUserData::Data { Gateways gws_; BlockStorage storage_; ChainedRequestors requestor_; IpnsNames names_; - std::weak_ptr api_; + std::weak_ptr api_; std::time_t last_discovery_ = 0; std::shared_ptr mem_, dsk_; base::FilePath const disk_path_; @@ -38,7 +38,7 @@ class InterRequestState : public base::SupportsUserData::Data { BlockRequestor& requestor(); IpnsNames& names() { return names_; } Scheduler& scheduler(); - std::shared_ptr api(); + std::shared_ptr api(); std::array,2> serialized_caches(); Orchestrator& orchestrator(); diff --git a/component/ipfs_url_loader.cc b/component/ipfs_url_loader.cc index a433b362..768c8925 100644 --- a/component/ipfs_url_loader.cc +++ b/component/ipfs_url_loader.cc @@ -1,6 +1,6 @@ #include "ipfs_url_loader.h" -#include "gateway_requests.h" +#include "chromium_ipfs_context.h" #include "inter_request_state.h" #include "summarize_headers.h" diff --git a/component/ipfs_url_loader.h b/component/ipfs_url_loader.h index f0634b3c..796822f8 100644 --- a/component/ipfs_url_loader.h +++ b/component/ipfs_url_loader.h @@ -15,7 +15,7 @@ #include namespace ipfs { -class GatewayRequests; +class ChromiumIpfsContext; class UnixFsPathResolver; } // namespace ipfs @@ -72,7 +72,7 @@ class IpfsUrlLoader final : public network::mojom::URLLoader, mojo::ScopedDataPipeProducerHandle pipe_prod_ = {}; mojo::ScopedDataPipeConsumerHandle pipe_cons_ = {}; bool complete_ = false; - std::shared_ptr api_; + std::shared_ptr api_; std::string original_url_; std::shared_ptr resolver_; std::string partial_block_; diff --git a/component/ipns_url_loader.cc b/component/ipns_url_loader.cc index 24dfe322..f56c56d6 100644 --- a/component/ipns_url_loader.cc +++ b/component/ipns_url_loader.cc @@ -1,6 +1,6 @@ #include "ipns_url_loader.h" -#include "gateway_requests.h" +#include "chromium_ipfs_context.h" #include "inter_request_state.h" #include "net/base/net_errors.h" diff --git a/component/ipns_url_loader.h b/component/ipns_url_loader.h index f1b9c101..6531ce38 100644 --- a/component/ipns_url_loader.h +++ b/component/ipns_url_loader.h @@ -39,7 +39,7 @@ class IpnsUrlLoader : public network::ResolveHostClientBase, mojo::PendingRemote client_remote_; std::shared_ptr ipfs_loader_; raw_ptr network_context_; - std::shared_ptr api_; + std::shared_ptr api_; raw_ref http_loader_; public: diff --git a/component/network_requestor.cc b/component/network_requestor.cc index a05ac6a1..1e306b12 100644 --- a/component/network_requestor.cc +++ b/component/network_requestor.cc @@ -1,6 +1,6 @@ #include "network_requestor.h" -#include "gateway_requests.h" +#include "chromium_ipfs_context.h" #include "inter_request_state.h" using Self = ipfs::NetworkRequestor; diff --git a/component/patches/121.0.6103.3.patch b/component/patches/121.0.6103.3.patch new file mode 100644 index 00000000..398887d3 --- /dev/null +++ b/component/patches/121.0.6103.3.patch @@ -0,0 +1,439 @@ +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +index 9feb694b4e410..fe16faf7a9fa3 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") +@@ -2657,6 +2658,10 @@ static_library("browser") { + ] + } + ++ if (enable_ipfs) { ++ 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 aba92a8ce8422..e5b409742ae49 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" +@@ -314,6 +315,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 +@@ -9787,6 +9792,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 cac5aa93883dc..c8beecf246eb2 100644 +--- a/chrome/browser/chrome_content_browser_client.cc ++++ b/chrome/browser/chrome_content_browser_client.cc +@@ -229,6 +229,8 @@ + #include "components/error_page/common/localized_error.h" + #include "components/error_page/content/browser/net_error_auto_reloader.h" + #include "components/google/core/common/google_switches.h" ++#include "components/ipfs/interceptor.h" ++#include "components/ipfs/url_loader_factory.h" + #include "components/keep_alive_registry/keep_alive_types.h" + #include "components/keep_alive_registry/scoped_keep_alive.h" + #include "components/language/core/browser/pref_names.h" +@@ -366,6 +368,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" +@@ -489,6 +492,12 @@ + #include "chrome/browser/fuchsia/chrome_browser_main_parts_fuchsia.h" + #endif + ++#if BUILDFLAG(ENABLE_IPFS) ++#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/intent_helper/chromeos_disabled_apps_throttle.h" +@@ -6170,12 +6179,23 @@ 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(); ++ ipfs::IpfsURLLoaderFactory::Create( ++ factories, ++ web_contents->GetBrowserContext(), ++ default_factory, ++ GetSystemNetworkContext() ++ ); ++ } ++#endif // BUILDFLAG(ENABLE_IPFS) + + #if BUILDFLAG(IS_CHROMEOS_ASH) + if (web_contents) { +@@ -6317,6 +6337,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_descriptions.cc b/chrome/browser/flag_descriptions.cc +index cc1f3549227e9..a4ab4f1e09ea2 100644 +--- a/chrome/browser/flag_descriptions.cc ++++ b/chrome/browser/flag_descriptions.cc +@@ -248,6 +248,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 5dd5c631c33e2..e8b0deb43e13f 100644 +--- a/chrome/browser/flag_descriptions.h ++++ b/chrome/browser/flag_descriptions.h +@@ -22,6 +22,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 +@@ -165,6 +166,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/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/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/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 d3a7fabf09fa8..06db17242248f 100644 +--- a/url/url_canon.h ++++ b/url/url_canon.h +@@ -697,6 +697,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..da3a5f032b5e8 +--- /dev/null ++++ b/url/url_canon_ipfs.cc +@@ -0,0 +1,72 @@ ++#include "url_canon_internal.h" ++ ++#include ++#include ++ ++#include ++ ++namespace m = libp2p::multi; ++using Cid = m::ContentIdentifier; ++using CidCodec = m::ContentIdentifierCodec; ++ ++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 cid_str{ spec + parsed.host.begin, static_cast(parsed.host.len) }; ++ auto maybe_cid = CidCodec::fromString(cid_str); ++ if ( !maybe_cid.has_value() ) { ++ auto e = libp2p::multi::Stringify(maybe_cid.error()); ++ std::ostringstream err; ++ err << e << ' ' ++ << std::string_view{spec,static_cast(spec_len)}; ++ maybe_cid = ipfs::id_cid::forText( err.str() ); ++ } ++ auto cid = maybe_cid.value(); ++ if ( cid.version == Cid::Version::V0 ) { ++ //TODO dcheck content_type == DAG_PB && content_address.getType() == sha256 ++ cid = Cid{ ++ Cid::Version::V1, ++ cid.content_type, ++ cid.content_address ++ }; ++ } ++ auto as_str = CidCodec::toString(cid); ++ if ( !as_str.has_value() ) { ++ return false; ++ } ++ std::string stdurl{ spec, static_cast(parsed.host.begin) }; ++ stdurl.append( as_str.value() ); ++ 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/component/patches/121.0.6110.0.patch b/component/patches/121.0.6110.0.patch new file mode 100644 index 00000000..0784fa80 --- /dev/null +++ b/component/patches/121.0.6110.0.patch @@ -0,0 +1,439 @@ +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +index 95c288ea91780..9bb47b8c9a609 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") +@@ -2656,6 +2657,10 @@ static_library("browser") { + ] + } + ++ if (enable_ipfs) { ++ 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 bec81b0940bb5..199e5b0f959ba 100644 +--- a/chrome/browser/about_flags.cc ++++ b/chrome/browser/about_flags.cc +@@ -211,6 +211,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" +@@ -312,6 +313,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 +@@ -9793,6 +9798,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 f3a34ded006fb..8a326c428c39a 100644 +--- a/chrome/browser/chrome_content_browser_client.cc ++++ b/chrome/browser/chrome_content_browser_client.cc +@@ -229,6 +229,8 @@ + #include "components/error_page/common/localized_error.h" + #include "components/error_page/content/browser/net_error_auto_reloader.h" + #include "components/google/core/common/google_switches.h" ++#include "components/ipfs/interceptor.h" ++#include "components/ipfs/url_loader_factory.h" + #include "components/keep_alive_registry/keep_alive_types.h" + #include "components/keep_alive_registry/scoped_keep_alive.h" + #include "components/language/core/browser/pref_names.h" +@@ -367,6 +369,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" +@@ -490,6 +493,12 @@ + #include "chrome/browser/fuchsia/chrome_browser_main_parts_fuchsia.h" + #endif + ++#if BUILDFLAG(ENABLE_IPFS) ++#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/intent_helper/chromeos_disabled_apps_throttle.h" +@@ -6183,12 +6192,23 @@ 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(); ++ ipfs::IpfsURLLoaderFactory::Create( ++ factories, ++ web_contents->GetBrowserContext(), ++ default_factory, ++ GetSystemNetworkContext() ++ ); ++ } ++#endif // BUILDFLAG(ENABLE_IPFS) + + #if BUILDFLAG(IS_CHROMEOS_ASH) + if (web_contents) { +@@ -6330,6 +6350,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_descriptions.cc b/chrome/browser/flag_descriptions.cc +index a7e9114f878bf..5518adc2ea6fd 100644 +--- a/chrome/browser/flag_descriptions.cc ++++ b/chrome/browser/flag_descriptions.cc +@@ -248,6 +248,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 0f2c3caba5ec2..4a0fc4d93dbfd 100644 +--- a/chrome/browser/flag_descriptions.h ++++ b/chrome/browser/flag_descriptions.h +@@ -22,6 +22,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 +@@ -165,6 +166,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/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/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/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 d3a7fabf09fa8..06db17242248f 100644 +--- a/url/url_canon.h ++++ b/url/url_canon.h +@@ -697,6 +697,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..da3a5f032b5e8 +--- /dev/null ++++ b/url/url_canon_ipfs.cc +@@ -0,0 +1,72 @@ ++#include "url_canon_internal.h" ++ ++#include ++#include ++ ++#include ++ ++namespace m = libp2p::multi; ++using Cid = m::ContentIdentifier; ++using CidCodec = m::ContentIdentifierCodec; ++ ++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 cid_str{ spec + parsed.host.begin, static_cast(parsed.host.len) }; ++ auto maybe_cid = CidCodec::fromString(cid_str); ++ if ( !maybe_cid.has_value() ) { ++ auto e = libp2p::multi::Stringify(maybe_cid.error()); ++ std::ostringstream err; ++ err << e << ' ' ++ << std::string_view{spec,static_cast(spec_len)}; ++ maybe_cid = ipfs::id_cid::forText( err.str() ); ++ } ++ auto cid = maybe_cid.value(); ++ if ( cid.version == Cid::Version::V0 ) { ++ //TODO dcheck content_type == DAG_PB && content_address.getType() == sha256 ++ cid = Cid{ ++ Cid::Version::V1, ++ cid.content_type, ++ cid.content_address ++ }; ++ } ++ auto as_str = CidCodec::toString(cid); ++ if ( !as_str.has_value() ) { ++ return false; ++ } ++ std::string stdurl{ spec, static_cast(parsed.host.begin) }; ++ stdurl.append( as_str.value() ); ++ 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/doc/design_notes.md b/doc/design_notes.md index 867beb0a..f9be995a 100644 --- a/doc/design_notes.md +++ b/doc/design_notes.md @@ -80,7 +80,7 @@ It's an unsigned integer in two forms: When issuing a request to another gateway, it checks the gateways in descending-score order: * If the gateway already has a request for that data pending, or has already failed to return successfully, it's skipped. * If the gateways score is less than the number of pending requests currently sent to that gateway, it's skipped as it's considered already-too-busy -* The "need" is generally calculated as the target number of gateways desired to be involved (based on request priority), subtracting the number of gateways currently processing a request for this data. +* The "need" is generally calculated as the target number of gateways desired to be involved (based on request parallel), subtracting the number of gateways currently processing a request for this data. * If the "need" is less than half the count of pending requests on this gateway, it's skipped as it's simply not urgent enough to justify further overloading this gateway. On a side-note, the hard-coded starting points for the scoring effectively encodes known information about those gateways. diff --git a/doc/original_design.md b/doc/original_design.md index c0a2b5df..214a7826 100644 --- a/doc/original_design.md +++ b/doc/original_design.md @@ -90,7 +90,7 @@ graph TD; goodfree --NO--> badfree{"Are there any FREE that have not already TaskFailed this request?"} badfree --NO--> anybusy{"Are there any BUSY gateways?"} anybusy --YES--> wait(("Wait for pending requests to finish (return flow control)")) - badfree --YES--> selbf["Select the one with the fewest TaskFailed requests, initial priority tiebreaks"] + badfree --YES--> selbf["Select the one with the fewest TaskFailed requests, initial parallel tiebreaks"] selbf --> mark_busy["Mark the gateway as BUSY"] goodfree --YES--> selbf mark_busy --> send["Create and send an HTTP URLRequest"] @@ -100,11 +100,11 @@ graph TD; resp[/"URL response"/] --> markfree["Mark the gateway as FREE"] --> success{"status=200 + body?"} style resp fill:#FFE,color:black success --YES--> cancel["Cancel identical requests"] --> markgood["Mark the gateway as GOOD"] - markgood --> incprio["Indicate to Gateways that this gateway's score/priority should be a bit higher"] + markgood --> incprio["Indicate to Gateways that this gateway's score/parallel should be a bit higher"] incprio ----> successed(["Return response (see previous diagram)"]) style successed fill:#9CF,color:black success --NO--> addbad["Add the URL suffix to this gateway's set of failures."] - addbad --> decprio[Indicate to Gateways that this gateway's score/priority should be a bit lower] --> selreq + addbad --> decprio[Indicate to Gateways that this gateway's score/parallel should be a bit lower] --> selreq anybusy --NO---> all_failed(["As all gateways have TaskFailed on a given request, report that failure"]) style all_failed fill:#9CF,color:black ``` diff --git a/library/BUILD.gn.in b/library/BUILD.gn.in index 6e89f3e6..fa72b6d0 100644 --- a/library/BUILD.gn.in +++ b/library/BUILD.gn.in @@ -21,6 +21,8 @@ if (enable_ipfs) { sources = cxx_sources - [ "src/ipfs_client/block_storage.cc", "src/ipfs_client/dag_block.cc", + "src/ipfs_client/gw/gateway_request.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", diff --git a/library/include/ipfs_client/context_api.h b/library/include/ipfs_client/context_api.h index 139cf14c..e687d231 100644 --- a/library/include/ipfs_client/context_api.h +++ b/library/include/ipfs_client/context_api.h @@ -2,9 +2,15 @@ #define IPFS_CONTEXT_API_H_ #include "busy_gateway.h" +#include "ipns_cbor_entry.h" +#include "signing_key_type.h" +#include + +#include #include #include +#include namespace ipfs { class IpfsRequest; @@ -22,6 +28,13 @@ class GatewayRequest { std::string url() const; ///< Same as gateway.url() }; +struct HttpRequestDescription { + std::string url; + int timeout_seconds; + std::string accept; + std::optional max_response_size; +}; + /** * \brief Interface that provides functionality from whatever * environment you're using this library in. @@ -30,9 +43,18 @@ class GatewayRequest { */ class ContextApi : public std::enable_shared_from_this { public: - ///< Send a single http request to its gateway as scheduled - virtual std::shared_ptr InitiateGatewayRequest( - BusyGateway) = 0; + 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. @@ -52,6 +74,18 @@ class ContextApi : public std::enable_shared_from_this { */ virtual std::string UnescapeUrlComponent(std::string_view url_comp) const = 0; + using IpnsCborEntry = ::ipfs::IpnsCborEntry; + // TODO: accept the Type as a parameter as a reference to a base that knows + // about its members + virtual IpnsCborEntry deserialize_cbor(ByteView) const = 0; + + using SigningKeyType = ::ipfs::SigningKeyType; + using ByteView = ::ipfs::ByteView; + virtual bool verify_key_signature(SigningKeyType, + ByteView signature, + ByteView data, + ByteView key_bytes) const = 0; + /*! * \brief Discover more gateways. * \details The in-chromium implementation of this hits @@ -62,6 +96,10 @@ class ContextApi : public std::enable_shared_from_this { * \note TRAILING SLASH (/) EXPECTED! */ virtual void Discover(std::function)> cb) = 0; + + // TODO drop + virtual std::shared_ptr InitiateGatewayRequest( + BusyGateway) = 0; }; } // namespace ipfs diff --git a/library/include/ipfs_client/gw/block_request_splitter.h b/library/include/ipfs_client/gw/block_request_splitter.h new file mode 100644 index 00000000..0f308a99 --- /dev/null +++ b/library/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/library/include/ipfs_client/gw/dnslink_requestor.h b/library/include/ipfs_client/gw/dnslink_requestor.h new file mode 100644 index 00000000..a175d880 --- /dev/null +++ b/library/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 { + HandleOutcome handle(RequestPtr) override; + std::string_view name() const override; + + public: + explicit DnsLinkRequestor(std::shared_ptr); +}; +} // namespace ipfs::gw + +#endif // IPFS_DNSLINK_REQUESTOR_H_ diff --git a/library/include/ipfs_client/gw/gateway_request.h b/library/include/ipfs_client/gw/gateway_request.h index af4837f3..bd73b98d 100644 --- a/library/include/ipfs_client/gw/gateway_request.h +++ b/library/include/ipfs_client/gw/gateway_request.h @@ -1,6 +1,8 @@ #ifndef IPFS_TRUSTLESS_REQUEST_H_ #define IPFS_TRUSTLESS_REQUEST_H_ +#include + #include #include @@ -27,16 +29,23 @@ struct GatewayRequest { std::shared_ptr dependent; std::shared_ptr orchestrator; std::optional cid; + short parallel = 0; + std::string affinity; 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() const; + std::string debug_string() const; static std::shared_ptr fromIpfsPath(SlashDelimited); }; } // namespace ipfs::gw +std::ostream& operator<<(std::ostream&, ipfs::gw::Type); + #endif // IPFS_TRUSTLESS_REQUEST_H_ diff --git a/library/include/ipfs_client/gw/inline_request_handler.h b/library/include/ipfs_client/gw/inline_request_handler.h new file mode 100644 index 00000000..11e2a156 --- /dev/null +++ b/library/include/ipfs_client/gw/inline_request_handler.h @@ -0,0 +1,13 @@ +#ifndef IPFS_INLINE_REQUEST_HANDLER_H_ +#define IPFS_INLINE_REQUEST_HANDLER_H_ + +#include "requestor.h" + +namespace ipfs::gw { +class InlineRequestHandler final : public Requestor { + HandleOutcome handle(RequestPtr) override; + std::string_view name() const override; +}; +} // namespace ipfs::gw + +#endif // IPFS_INLINE_REQUEST_HANDLER_H_ diff --git a/library/include/ipfs_client/gw/requestor.h b/library/include/ipfs_client/gw/requestor.h index 2bedfd7c..5c2f4e9c 100644 --- a/library/include/ipfs_client/gw/requestor.h +++ b/library/include/ipfs_client/gw/requestor.h @@ -9,7 +9,8 @@ namespace ipfs::ipld { class DagNode; } namespace ipfs { -class Response; +class ContextApi; +struct Response; } // namespace ipfs namespace ipfs::gw { @@ -29,8 +30,13 @@ class Requestor { void success(RequestPtr, std::string_view body) const; void failure(RequestPtr) const; void definitive_failure(RequestPtr) const; + void forward(RequestPtr) const; + + std::shared_ptr api_; public: + Requestor() {} + virtual ~Requestor() noexcept {} void request(std::shared_ptr); Requestor& or_else(std::shared_ptr p); diff --git a/library/include/ipfs_client/ipfs_request.h b/library/include/ipfs_client/ipfs_request.h index 2ca22934..9c0cf8d2 100644 --- a/library/include/ipfs_client/ipfs_request.h +++ b/library/include/ipfs_client/ipfs_request.h @@ -15,11 +15,14 @@ class IpfsRequest { private: std::string path_; Finisher callback_; + std::size_t waiting_ = 0UL; public: IpfsRequest(std::string path, Finisher); SlashDelimited path() const { return std::string_view{path_}; } void finish(Response& r); + void till_next(std::size_t); + bool ready_after(); }; } // namespace ipfs diff --git a/library/include/ipfs_client/ipld/dag_node.h b/library/include/ipfs_client/ipld/dag_node.h index 10b41af3..0bca255a 100644 --- a/library/include/ipfs_client/ipld/dag_node.h +++ b/library/include/ipfs_client/ipld/dag_node.h @@ -19,6 +19,7 @@ namespace ipfs { class Block; +class ContextApi; struct ValidatedIpns; } @@ -40,6 +41,7 @@ using ResolveResult = std::variant; class DagNode : public std::enable_shared_from_this { protected: std::vector> links_; + std::shared_ptr api_; public: using BlockLookup = std::function; @@ -51,6 +53,8 @@ class DagNode : public std::enable_shared_from_this { virtual NodePtr deroot(); virtual DirShard* as_hamt(); // Wish I had access to dynamic_cast + void set_api(std::shared_ptr); + static NodePtr fromBlock(Block const&); static NodePtr fromIpnsRecord(ValidatedIpns const&); }; diff --git a/library/include/ipfs_client/ipns_cbor_entry.h b/library/include/ipfs_client/ipns_cbor_entry.h new file mode 100644 index 00000000..23033979 --- /dev/null +++ b/library/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/library/include/ipfs_client/ipns_record.h b/library/include/ipfs_client/ipns_record.h index 303561c2..388a0f96 100644 --- a/library/include/ipfs_client/ipns_record.h +++ b/library/include/ipfs_client/ipns_record.h @@ -1,6 +1,8 @@ #ifndef IPFS_IPNS_RECORD_H_ #define IPFS_IPNS_RECORD_H_ +#include + #include #if __has_include() @@ -16,41 +18,23 @@ namespace libp2p::peer { class PeerId; } namespace libp2p::multi { -class ContentIdentifier; +struct ContentIdentifier; } namespace ipfs { -constexpr static std::size_t MAX_IPNS_PB_SERIALIZED_SIZE = 10 * 1024; - -/*! - * \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 -}; - -using CborDeserializer = IpnsCborEntry(ByteView); +class ContextApi; -using CryptoSignatureVerifier = bool(ipns::KeyType, - ByteView, - ByteView, - ByteView); +constexpr static std::size_t MAX_IPNS_PB_SERIALIZED_SIZE = 10 * 1024; std::optional ValidateIpnsRecord( ByteView top_level_bytes, libp2p::multi::ContentIdentifier const& name, - CryptoSignatureVerifier, - CborDeserializer); + ContextApi&); std::optional ValidateIpnsRecord( ByteView top_level_bytes, libp2p::peer::PeerId const& name, - CryptoSignatureVerifier, - CborDeserializer); + ContextApi&); /*! * \brief Data from IPNS record modulo the verification parts diff --git a/library/include/ipfs_client/orchestrator.h b/library/include/ipfs_client/orchestrator.h index 716d8b6e..b6ec25ec 100644 --- a/library/include/ipfs_client/orchestrator.h +++ b/library/include/ipfs_client/orchestrator.h @@ -3,30 +3,41 @@ #include "ipfs_client/ipld/dag_node.h" -#include "vocab/flat_mapset.h" +#include +#include #include #include namespace ipfs { -class Orchestrator : public std::enable_shared_from_this { - flat_map dags_; +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(GatewayAccess, MimeDetection); + explicit Orchestrator(GatewayAccess, + std::shared_ptr = {}, + std::shared_ptr request_handling_override = {}); void build_response(std::shared_ptr); void add_node(std::string key, ipld::NodePtr); private: + flat_map dags_; GatewayAccess gw_requestor_; - MimeDetection mimer_; - void from_tree(std::shared_ptr, ipld::NodePtr&, SlashDelimited); - bool gw_request(std::shared_ptr, SlashDelimited path); + 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 diff --git a/library/include/ipfs_client/response.h b/library/include/ipfs_client/response.h index cf5e146b..a1817848 100644 --- a/library/include/ipfs_client/response.h +++ b/library/include/ipfs_client/response.h @@ -14,6 +14,9 @@ struct Response { std::string location_; static Response PLAIN_NOT_FOUND; + static Response HOST_NOT_FOUND; + + constexpr static std::uint16_t HOST_NOT_FOUND_STATUS = 9876; }; } // namespace ipfs diff --git a/library/include/ipfs_client/signing_key_type.h b/library/include/ipfs_client/signing_key_type.h new file mode 100644 index 00000000..4a74ad0f --- /dev/null +++ b/library/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/library/include/libp2p/multi/content_identifier.hpp b/library/include/libp2p/multi/content_identifier.hpp index c29eb7c5..d677ddf3 100644 --- a/library/include/libp2p/multi/content_identifier.hpp +++ b/library/include/libp2p/multi/content_identifier.hpp @@ -33,12 +33,6 @@ struct ContentIdentifier { MulticodecType::Code content_type, Multihash content_address); - /** - * @param base is a human-readable multibase prefix - * @returns human readable representation of the CID - */ - std::string toPrettyString(const std::string& base) const; - bool operator==(const ContentIdentifier& c) const; bool operator<(const ContentIdentifier& c) const; diff --git a/library/src/ipfs_client/gw/block_request_splitter.cc b/library/src/ipfs_client/gw/block_request_splitter.cc new file mode 100644 index 00000000..29e4e007 --- /dev/null +++ b/library/src/ipfs_client/gw/block_request_splitter.cc @@ -0,0 +1,32 @@ +#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; + } + if (r->path.empty()) { + r->type = Type::Block; + 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/library/src/ipfs_client/gw/block_request_splitter_unittest.cc b/library/src/ipfs_client/gw/block_request_splitter_unittest.cc new file mode 100644 index 00000000..8308328f --- /dev/null +++ b/library/src/ipfs_client/gw/block_request_splitter_unittest.cc @@ -0,0 +1,45 @@ +#include + +#include + +#include + +namespace g = ipfs::gw; + +using Tested = g::BlockRequestSplitter; +using Req = g::GatewayRequest; + +namespace { +struct Recording : public g::Requestor { + std::string_view name() const { return "recording requestor for test"; } + std::vector requests_received; + HandleOutcome handle(g::RequestPtr r) { + requests_received.push_back(r); + return HandleOutcome::DONE; + } +}; +} // namespace +TEST(BlockRequestSplitterTest, typical) { + Tested tested; + auto rec = std::make_shared(); + tested.or_else(rec); + auto req = std::make_shared(); + req->type = g::Type::Car; + req->main_param = "cid"; + req->path = "path"; + req->parallel = 123; + tested.request(req); + EXPECT_EQ(rec->requests_received.size(), 3); + EXPECT_TRUE(rec->requests_received.at(0)->type == g::Type::Block) << static_cast(rec->requests_received.at(0)->type); + EXPECT_TRUE(rec->requests_received.at(1)->type == g::Type::Providers) << static_cast(rec->requests_received.at(0)->type); + EXPECT_TRUE(rec->requests_received.at(2)->type == g::Type::Car) << static_cast(rec->requests_received.at(0)->type); + EXPECT_EQ(rec->requests_received.at(0)->main_param, "cid"); + EXPECT_EQ(rec->requests_received.at(1)->main_param, "cid"); + EXPECT_EQ(rec->requests_received.at(2)->main_param, "cid"); + EXPECT_EQ(rec->requests_received.at(0)->path, ""); + EXPECT_EQ(rec->requests_received.at(1)->path, ""); + EXPECT_EQ(rec->requests_received.at(2)->path, "path"); + EXPECT_EQ(rec->requests_received.at(0)->parallel, 123); + EXPECT_EQ(rec->requests_received.at(1)->parallel, 123); + EXPECT_EQ(rec->requests_received.at(2)->parallel, 123); +} diff --git a/library/src/ipfs_client/gw/dnslink_requestor.cc b/library/src/ipfs_client/gw/dnslink_requestor.cc new file mode 100644 index 00000000..bbc8ecd0 --- /dev/null +++ b/library/src/ipfs_client/gw/dnslink_requestor.cc @@ -0,0 +1,61 @@ +#include + +#include "ipfs_client/ipld/ipns_name.h" + +#include +#include + +#include +#include +#include + +#define ABSL_USES_STD_STRING_VIEW 1 // Thanks, Windows! +#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); +} +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 res = [req, success](std::vector const& results) { + *success = *success || parse_results(req, results); + }; + auto don = [success, req]() { + 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) { + constexpr auto prefix = "dnslink="sv; + for (auto& result : results) { + if (!absl::StartsWith(result, prefix)) { + auto target = result.substr(prefix.size()); + auto node = std::make_shared(target); + req->orchestrator->add_node(req->main_param, node); + req->orchestrator->build_response(req->dependent); + return true; + } + } + return false; +} +} // namespace diff --git a/library/src/ipfs_client/gw/gateway_request.cc b/library/src/ipfs_client/gw/gateway_request.cc index 4c5216be..49179333 100644 --- a/library/src/ipfs_client/gw/gateway_request.cc +++ b/library/src/ipfs_client/gw/gateway_request.cc @@ -1,8 +1,7 @@ -#include "ipfs_client/gw/gateway_request.h" +#include #include - -#include "ipfs_client/response.h" +#include #include "log_macros.h" @@ -62,8 +61,11 @@ std::string Self::url_suffix() const { case Type::DnsLink: LOG(FATAL) << "Don't try to use HTTP(s) for DNS TXT records."; return {}; + case Type::Identity: + return {}; default: - LOG(FATAL) << "Invalid gateway request type: " << static_cast(type); + LOG(FATAL) << "Unhandled gateway request type: " + << static_cast(type); return {}; } } @@ -87,11 +89,34 @@ std::string_view Self::accept() const { // DNSLink capability. LOG(FATAL) << "Don't try to use HTTP(s) for DNS TXT records."; return {}; + case Type::Identity: + return {}; default: LOG(FATAL) << "Invalid gateway request type: " << static_cast(type); return {}; } } +short Self::timeout_seconds() const { + switch (type) { + case Type::DnsLink: + return 8; + case Type::Block: + return 16; + case Type::Providers: + return 32; + case Type::Car: + return 128; + case Type::Ipns: + return 256; + case Type::Identity: + return 0; + default: + 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) { @@ -105,6 +130,13 @@ auto Self::identity_data() const -> std::string_view { bool Self::is_http() const { return type != Type::DnsLink && type != Type::Identity; } +auto Self::describe_http() const -> std::optional { + if (!is_http()) { + return {}; + } + return HttpRequestDescription{url_suffix(), timeout_seconds(), + std::string{accept()}, max_response_size()}; +} std::optional Self::max_response_size() const { switch (type) { case Type::Identity: @@ -135,3 +167,30 @@ std::optional Self::max_response_size() const { return std::nullopt; } } +std::ostream& operator<<(std::ostream& s, ipfs::gw::Type t) { + using ipfs::gw::Type; + switch (t) { + case Type::Block: + return s << "Block"; + case Type::Car: + return s << "Car"; + case Type::Ipns: + return s << "Ipns"; + case Type::DnsLink: + return s << "DnsLink"; + case Type::Providers: + return s << "Providers"; + case Type::Identity: + return s << "Identity"; + default: + return s << "InvalidType=" << static_cast(t); + } +} +std::string Self::debug_string() const { + std::ostringstream oss; + oss << "Request{Type=" << type << ' ' << main_param; + if (!path.empty()) { + oss << ' ' << path; + } + return oss.str(); +} diff --git a/library/src/ipfs_client/gw/inline_request_handler.cc b/library/src/ipfs_client/gw/inline_request_handler.cc new file mode 100644 index 00000000..43f49ef7 --- /dev/null +++ b/library/src/ipfs_client/gw/inline_request_handler.cc @@ -0,0 +1,20 @@ +#include + +#include +#include +#include + +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) { + return HandleOutcome::NOT_HANDLED; + } + auto node = std::make_shared(std::string{req->identity_data()}); + req->orchestrator->add_node(req->main_param, node); + req->orchestrator->build_response(req->dependent); + return HandleOutcome::DONE; +} diff --git a/library/src/ipfs_client/gw/requestor.cc b/library/src/ipfs_client/gw/requestor.cc index ed361649..d85242ea 100644 --- a/library/src/ipfs_client/gw/requestor.cc +++ b/library/src/ipfs_client/gw/requestor.cc @@ -1,7 +1,6 @@ #include #include - #include #include @@ -22,12 +21,10 @@ Self& Self::or_else(std::shared_ptr p) { } else { next_ = p; } + p->api_ = api_; return *this; } -template -Stream& operator<<(Stream s, ipfs::gw::GatewayRequest const& r) { - return s << static_cast(r.type) << ' ' << r.main_param << ' ' << r.path; -} + void Self::request(ReqPtr req) { switch (handle(req)) { case HandleOutcome::NOT_HANDLED: @@ -38,22 +35,22 @@ void Self::request(ReqPtr req) { } else { LOG(ERROR) << "Ran out of Requestors in the chain while looking for " "one that can handle Request{type=" - << *req; + << req->debug_string(); definitive_failure(req); } break; case HandleOutcome::PENDING: - VLOG(1) << *req << " sent via requestor " << name(); + VLOG(1) << req->debug_string() << " sent via requestor " << name(); break; case HandleOutcome::DONE: - LOG(INFO) << *req << " finished synchronously: " << name(); + LOG(INFO) << req->debug_string() << " finished synchronously: " << name(); break; } } void Self::failure(ipfs::gw::RequestPtr r) const { if (next_) { - LOG(WARNING) << name() << " failed on " << *r << " ... passing along to " - << next_->name(); + LOG(WARNING) << name() << " failed on " << r->debug_string() + << " ... passing along to " << next_->name(); next_->request(r); } else { definitive_failure(r); @@ -91,7 +88,7 @@ void Self::iterate_nodes( void Self::receive_response(ipfs::gw::RequestPtr req, ipfs::Response const& res) const { if (!req->orchestrator) { - LOG(ERROR) << name() << ": Got response for " << *req + LOG(ERROR) << name() << ": Got response for " << req->debug_string() << " but it doesn't have its orchestrator pointer set, so can't " "deliver response."; return; @@ -101,3 +98,8 @@ void Self::receive_response(ipfs::gw::RequestPtr req, [o](std::string k, ipld::NodePtr n) { o->add_node(k, n); }); req->orchestrator->build_response(req->dependent); } +void Self::forward(ipfs::gw::RequestPtr req) const { + if (next_) { + next_->request(req); + } +} \ No newline at end of file diff --git a/library/src/ipfs_client/gw/requestor_unittest.cc b/library/src/ipfs_client/gw/requestor_unittest.cc new file mode 100644 index 00000000..5e534bd1 --- /dev/null +++ b/library/src/ipfs_client/gw/requestor_unittest.cc @@ -0,0 +1,54 @@ +#include + +#include + +#include + +namespace g = ipfs::gw; +using T = g::Requestor; + +namespace { +struct TestRequestor : public g::Requestor { + std::vector requests_sent_to_handle; + std::vector outcomes; + HandleOutcome handle(g::RequestPtr r) { + requests_sent_to_handle.push_back(r); + auto res = outcomes.at(0); + outcomes.erase(outcomes.begin()); + return res; + } + std::string name_ = "tested requestor"; + std::string_view name() const { return name_; } + using Out = HandleOutcome; +}; +using Out = TestRequestor::Out; +struct RequestorTest : public ::testing::Test { + std::shared_ptr a = std::make_shared(); + std::shared_ptr b = std::make_shared(); + std::shared_ptr req_ = + std::make_shared(); +}; +} // namespace + +TEST_F(RequestorTest, can_ctor) {} + +TEST_F(RequestorTest, nothandled_continues) { + a->or_else(b); + a->outcomes.push_back(Out::NOT_HANDLED); + b->outcomes.push_back(Out::PENDING); + a->request(req_); + EXPECT_EQ(a->requests_sent_to_handle.size(), 1); + EXPECT_EQ(b->requests_sent_to_handle.size(), 1); + EXPECT_TRUE(a->requests_sent_to_handle.at(0) == req_); + EXPECT_TRUE(b->requests_sent_to_handle.at(0) == req_); +} + +TEST_F(RequestorTest, done_stops_request) { + a->or_else(b); + a->outcomes.push_back(Out::DONE); + b->outcomes.push_back(Out::PENDING); + a->request(req_); + EXPECT_EQ(a->requests_sent_to_handle.size(), 1); + EXPECT_EQ(b->requests_sent_to_handle.size(), 0); + EXPECT_TRUE(a->requests_sent_to_handle.at(0) == req_); +} diff --git a/library/src/ipfs_client/gw/unlimited_gateway_http_requestor.cc b/library/src/ipfs_client/gw/unlimited_gateway_http_requestor.cc new file mode 100644 index 00000000..ae0875a0 --- /dev/null +++ b/library/src/ipfs_client/gw/unlimited_gateway_http_requestor.cc @@ -0,0 +1,95 @@ +#include "unlimited_gateway_http_requestor.h" + +#include +#include + +#include +#include +#include +#include + +#include + +#include "log_macros.h" + +using Self = ipfs::gw::UnlimitedGatewayHttpRequestor; +using ReqTyp = ipfs::gw::Type; +using CidCodec = libp2p::multi::ContentIdentifierCodec; + +std::string_view Self::name() const { + return "The simplest HTTP requestor for a given gateway I could write "; +} +auto Self::handle(ipfs::gw::RequestPtr r) -> HandleOutcome { + if (!r->is_http()) { + return HandleOutcome::NOT_HANDLED; + } + auto desc = r->describe_http(); + if (!desc.has_value()) { + LOG(ERROR) + << r->debug_string() + << " is HTTP but can't describe the HTTP request that would happen?"; + return HandleOutcome::NOT_HANDLED; + } + desc.value().url.insert(0, prefix_); + auto cb = [this, r](std::int16_t status, std::string_view body, + ContextApi::HeaderAccess) { + if (status / 100 != 2) { + LOG(INFO) << r->debug_string() << " got a failure of status " << status + << " from " << prefix_; + this->forward(r); + } else { + auto nod = node_from_type(r->main_param, r->type, body); + r->orchestrator->add_node(r->main_param, nod); + r->orchestrator->build_response(r->dependent); + } + }; + api_->SendHttpRequest(desc.value(), cb); + return HandleOutcome::PENDING; +} + +Self::UnlimitedGatewayHttpRequestor(std::string gateway_prefix) + : prefix_{gateway_prefix} {} +Self::~UnlimitedGatewayHttpRequestor() {} + +ipfs::ipld::NodePtr Self::node_from_type(std::string cid_str, + ReqTyp t, + std::string_view body) const { + auto cid = CidCodec::fromString(cid_str); + switch (t) { + case ReqTyp::Block: { + if (cid.has_value()) { + ipfs::Block blk{cid.value(), std::string{body}}; + if (blk.valid()) { + return ipfs::ipld::DagNode::fromBlock(blk); + } + } + return {}; + } + + case ReqTyp::Ipns: { + if (cid.has_value()) { + auto byte_ptr = reinterpret_cast(body.data()); + auto rec = ipfs::ValidateIpnsRecord({byte_ptr, body.size()}, + cid.value(), *api_); + // TODO + } + return {}; + } + case ReqTyp::Identity: + LOG(ERROR) << "An HTTP response from a gateway received for an identity " + "(inlined) CID"; + return {}; + case ReqTyp::DnsLink: + LOG(WARNING) << "HTTP responses to DnsLink requests not yet implemented, " + "and it's not clear they will be."; + return {}; + case ReqTyp::Car: + LOG(INFO) << "TODO responses to Car requests not yet implemented."; + return {}; + case ReqTyp::Providers: + LOG(INFO) << "TODO responses to Car requests not yet implemented: " + << body; + return {}; + } + return {}; // TODO +} diff --git a/library/src/ipfs_client/gw/unlimited_gateway_http_requestor.h b/library/src/ipfs_client/gw/unlimited_gateway_http_requestor.h new file mode 100644 index 00000000..88cb9a60 --- /dev/null +++ b/library/src/ipfs_client/gw/unlimited_gateway_http_requestor.h @@ -0,0 +1,26 @@ +#ifndef IPFS_UNLIMITED_GATEWAY_HTTP_REQUESTOR_H_ +#define IPFS_UNLIMITED_GATEWAY_HTTP_REQUESTOR_H_ + +#include +#include + +#include + +namespace ipfs::gw { +class UnlimitedGatewayHttpRequestor final : public Requestor { + std::string prefix_; + + HandleOutcome handle(RequestPtr) override; + std::string_view name() const override; + + ipfs::ipld::NodePtr node_from_type(std::string cid, + ipfs::gw::Type, + std::string_view body) const; + + public: + UnlimitedGatewayHttpRequestor(std::string gateway_prefix); + ~UnlimitedGatewayHttpRequestor() noexcept override; +}; +} // namespace ipfs::gw + +#endif // IPFS_UNLIMITED_GATEWAY_HTTP_REQUESTOR_H_ diff --git a/library/src/ipfs_client/ipfs_request.cc b/library/src/ipfs_client/ipfs_request.cc index 5e25e687..84102251 100644 --- a/library/src/ipfs_client/ipfs_request.cc +++ b/library/src/ipfs_client/ipfs_request.cc @@ -8,12 +8,22 @@ using Self = ipfs::IpfsRequest; // : path_{path_p}, callback_([](auto&, auto&) {}) {} Self::IpfsRequest(std::string path_p, Finisher f) : path_{path_p}, callback_{f} {} - +void Self::till_next(std::size_t w) { + waiting_ = w; +} void Self::finish(ipfs::Response& r) { - LOG(INFO) << "IpfsRequest::finish(...);"; + LOG(INFO) << "IpfsRequest::finish(" << waiting_ << ");"; + if (waiting_) { + if (--waiting_) { + return; + } + } callback_(*this, r); // TODO - cancel other gw req pointing into this callback_ = [](auto& q, auto&) { LOG(INFO) << "IPFS request " << q.path().pop_all() << " satisfied multiply"; }; } +bool Self::ready_after() { + return waiting_ == 0 || 0 == --waiting_; +} diff --git a/library/src/ipfs_client/ipld/dag_node.cc b/library/src/ipfs_client/ipld/dag_node.cc index 0a92fb5e..e660d9c8 100644 --- a/library/src/ipfs_client/ipld/dag_node.cc +++ b/library/src/ipfs_client/ipld/dag_node.cc @@ -60,3 +60,6 @@ std::shared_ptr Node::rooted() { auto Node::as_hamt() -> DirShard* { return nullptr; } +void Node::set_api(std::shared_ptr api) { + api_ = api; +} \ No newline at end of file diff --git a/library/src/ipfs_client/ipld/directory_shard.cc b/library/src/ipfs_client/ipld/directory_shard.cc index 4e0032fe..c06805d3 100644 --- a/library/src/ipfs_client/ipld/directory_shard.cc +++ b/library/src/ipfs_client/ipld/directory_shard.cc @@ -2,6 +2,7 @@ #include "log_macros.h" +#include #include #include @@ -29,7 +30,7 @@ auto Self::resolve(ipfs::SlashDelimited relpath, } return result; } - auto name = relpath.pop(); + auto name = api_->UnescapeUrlComponent(relpath.pop()); auto hash = hexhash(name); return resolve_internal(hash.begin(), hash.end(), name, relpath, blu, to_here); diff --git a/library/src/ipfs_client/ipld/small_directory.cc b/library/src/ipfs_client/ipld/small_directory.cc index ccff62e0..716e37ad 100644 --- a/library/src/ipfs_client/ipld/small_directory.cc +++ b/library/src/ipfs_client/ipld/small_directory.cc @@ -1,5 +1,6 @@ #include "small_directory.h" +#include #include "ipfs_client/generated_directory_listing.h" #include "ipfs_client/path2url.h" @@ -45,11 +46,12 @@ auto Self::resolve(SlashDelimited path, BlockLookup blu, std::string& to_here) } return Response{"text/html", 200, index_html.Finish(), ""}; } - auto name = path.pop(); + auto name = api_->UnescapeUrlComponent(path.pop()); // TODO binary search auto match = [&name](auto& l) { return l.first == name; }; auto it = std::find_if(links_.begin(), links_.end(), match); if (links_.end() == it) { + LOG(INFO) << name << " does not exist in directory."; return ProvenAbsent{}; } auto& link = it->second; @@ -59,8 +61,11 @@ auto Self::resolve(SlashDelimited path, BlockLookup blu, std::string& to_here) to_here.push_back('/'); } to_here.append(name); + LOG(INFO) << "Descending into " << link.cid << " for " << name; return nod->resolve(path, blu, to_here); } else { + LOG(INFO) << "Should descending into " << link.cid << " for " << name + << " but can't because it's missing. Will request."; return MoreDataNeeded{std::vector{"/ipfs/" + link.cid}}; } } diff --git a/library/src/ipfs_client/ipld/unixfs_file.cc b/library/src/ipfs_client/ipld/unixfs_file.cc index 4faa0646..8e538524 100644 --- a/library/src/ipfs_client/ipld/unixfs_file.cc +++ b/library/src/ipfs_client/ipld/unixfs_file.cc @@ -1,5 +1,7 @@ #include "unixfs_file.h" +#include "log_macros.h" + using namespace std::literals; using Self = ipfs::ipld::UnixfsFile; @@ -8,7 +10,8 @@ auto Self::resolve(ipfs::SlashDelimited path, ipfs::ipld::DagNode::BlockLookup blu, std::string& to_here) -> ResolveResult { if (path) { - // You can't path through a file. + LOG(ERROR) << "Can't path through a file, (at " << to_here + << ") but given the path " << path.to_string(); return ProvenAbsent{}; } std::vector missing; @@ -30,10 +33,14 @@ auto Self::resolve(ipfs::SlashDelimited path, body.append(std::get(recurse).body_); } } else { + LOG(INFO) << "In order to resolve the file at path " << to_here + << " I need CID " << link.cid; missing.push_back("/ipfs/" + link.cid); } } if (missing.empty()) { + LOG(INFO) << "Assembled file (or file part) of " << body.size() + << " bytes from multiple nodes."; return Response{ "", 200, diff --git a/library/src/ipfs_client/ipns_record.cc b/library/src/ipfs_client/ipns_record.cc index 516fc10d..b1ca813d 100644 --- a/library/src/ipfs_client/ipns_record.cc +++ b/library/src/ipfs_client/ipns_record.cc @@ -1,5 +1,7 @@ #include +#include + #include "log_macros.h" #if __has_include() @@ -31,25 +33,21 @@ bool matches(libp2p::multi::Multihash const& hash, auto ipfs::ValidateIpnsRecord(ipfs::ByteView top_level_bytes, libp2p::multi::ContentIdentifier const& name, - ipfs::CryptoSignatureVerifier* v, - ipfs::CborDeserializer* d) - -> std::optional { + ContextApi& api) -> std::optional { DCHECK_EQ(name.content_type, libp2p::multi::MulticodecType::Code::LIBP2P_KEY); if (name.content_type != libp2p::multi::MulticodecType::Code::LIBP2P_KEY) { return {}; } auto as_peer = libp2p::peer::PeerId::fromHash(name.content_address); if (as_peer.has_value()) { - return ValidateIpnsRecord(top_level_bytes, as_peer.value(), v, d); + return ValidateIpnsRecord(top_level_bytes, as_peer.value(), api); } else { return {}; } } auto ipfs::ValidateIpnsRecord(ByteView top_level_bytes, libp2p::peer::PeerId const& name, - CryptoSignatureVerifier verify, - CborDeserializer dser) - -> std::optional { + ContextApi& api) -> std::optional { // https://github.com/ipfs/specs/blob/main/ipns/IPNS.md#record-verification // Before parsing the protobuf, confirm that the serialized IpnsEntry bytes @@ -80,8 +78,9 @@ auto ipfs::ValidateIpnsRecord(ByteView top_level_bytes, // the expiration date after which the IPNS record becomes invalid. DCHECK_EQ(entry.validitytype(), 0); - auto parsed = dser({reinterpret_cast(entry.data().data()), - entry.data().size()}); + auto parsed = + api.deserialize_cbor({reinterpret_cast(entry.data().data()), + entry.data().size()}); if (entry.has_value() && parsed.value != entry.value()) { LOG(ERROR) << "CBOR contains value '" << parsed.value << "' but PB contains value '" << entry.value() << "'!"; @@ -102,7 +101,7 @@ auto ipfs::ValidateIpnsRecord(ByteView top_level_bytes, public_key = name.toMultihash().getHash(); } else { LOG(ERROR) << "IPNS record contains no public key, and the IPNS name " - << name.toMultihash().toHex() + << name.toBase58() << " is a true hash, not identity. Validation impossible."; return {}; } @@ -118,8 +117,8 @@ auto ipfs::ValidateIpnsRecord(ByteView top_level_bytes, 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 ipns-signature: - // prefix (bytes in hex: 69706e732d7369676e61747572653a) with raw CBOR bytes + // 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( @@ -128,7 +127,8 @@ auto ipfs::ValidateIpnsRecord(ByteView top_level_bytes, bytes_str.size()}; ByteView key_bytes{reinterpret_cast(pk.data().data()), pk.data().size()}; - if (!verify(pk.type(), signature, bytes, key_bytes)) { + if (!api.verify_key_signature(static_cast(pk.type()), + signature, bytes, key_bytes)) { LOG(ERROR) << "Verification failed!!"; return {}; } @@ -178,7 +178,7 @@ auto ipfs::ValidatedIpns::operator=(ValidatedIpns const&) ipfs::ValidatedIpns::ValidatedIpns(IpnsCborEntry const& e) : value{e.value}, sequence{e.sequence} { std::istringstream ss{e.validity}; - std::tm t; + std::tm t = {}; ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%S"); long ttl = (e.ttl / 1'000'000'000UL) + 1; use_until = std::mktime(&t); diff --git a/library/src/ipfs_client/ipns_record_unittest.cc b/library/src/ipfs_client/ipns_record_unittest.cc index 3efa7a84..3cec0553 100644 --- a/library/src/ipfs_client/ipns_record_unittest.cc +++ b/library/src/ipfs_client/ipns_record_unittest.cc @@ -1,12 +1,72 @@ #include +#include #include #include #include #include +namespace { +struct Api final : public ipfs::ContextApi { + void SendHttpRequest(ipfs::HttpRequestDescription, + HttpCompleteCallback cb) const { + throw 1; + } + void SendDnsTextRequest(std::string hostname, + DnsTextResultsCallback, + DnsTextCompleteCallback) { + throw 1; + } + std::string MimeType(std::string extension, + std::string_view content, + std::string const& url) const { + throw 1; + } + bool verify_key_signature(SigningKeyType, + ByteView signature, + ByteView data, + ByteView key_bytes) const { + return true; + } + std::string UnescapeUrlComponent(std::string_view url_comp) const { throw 1; } + ipfs::IpnsCborEntry deserialize_cbor(ipfs::ByteView b) const { + return dser_(b); + } + bool verify_key_signature(ipfs::SigningKeyType, + ipfs::ByteView signature, + ipfs::ByteView data, + ipfs::ByteView key_bytes) { + return true; + } + void Discover(std::function)> cb) { throw 1; } + std::shared_ptr InitiateGatewayRequest( + ipfs::BusyGateway) { + throw 1; + } + std::function dser_; + Api(std::function f = {}) { + if (f) { + dser_ = f; + } else { + dser_ = [](auto) { + ipfs::IpnsCborEntry e; + e.value = + "/ipfs/bafybeig57t2dp435aupttilimd6767kppfebaa3gnunmqden66dgkhugwi"; + e.validity = "2023-03-24T05:10:02.161162321Z"; + e.validityType = 0; + e.sequence = 384; + e.ttl = 60000000000; + return e; + }; + } + } + virtual ~Api() noexcept {} +}; +} // namespace + TEST(IpnsRecordTest, AKnownKuboRecord) { + Api api; std::array xxd{ 0x0a, 0x41, 0x2f, 0x69, 0x70, 0x66, 0x73, 0x2f, 0x62, 0x61, 0x66, 0x79, 0x62, 0x65, 0x69, 0x67, 0x35, 0x37, 0x74, 0x32, 0x64, 0x70, 0x34, 0x33, @@ -56,18 +116,7 @@ TEST(IpnsRecordTest, AKnownKuboRecord) { auto my_name_res = libp2p::peer::PeerId::fromHash(ci.content_address); ASSERT_TRUE(my_name_res.has_value()); auto& my_name = my_name_res.value(); - auto result = ipfs::ValidateIpnsRecord( - known_record, my_name, [](auto, auto, auto, auto) { return true; }, - [](auto) { - ipfs::IpnsCborEntry e; - e.value = - "/ipfs/bafybeig57t2dp435aupttilimd6767kppfebaa3gnunmqden66dgkhugwi"; - e.validity = "2023-03-24T05:10:02.161162321Z"; - e.validityType = 0; - e.sequence = 384; - e.ttl = 60000000000; - return e; - }); + auto result = ipfs::ValidateIpnsRecord(known_record, my_name, api); std::string_view expected{ "/ipfs/bafybeig57t2dp435aupttilimd6767kppfebaa3gnunmqden66dgkhugwi"}; EXPECT_EQ(result.has_value(), true); @@ -98,7 +147,8 @@ TEST(IpnsRecordTest, TooBig) { ipfs::Byte* p; auto peer = libp2p::peer::PeerId::fromBase58( "12D3KooWGDMwwqrpcYKpKCgxuKT2NfqPqa94QnkoBBpqvCaiCzWd"); - auto actual = ipfs::ValidateIpnsRecord({p, 12345}, peer.value(), {}, {}); + Api api; + auto actual = ipfs::ValidateIpnsRecord({p, 12345}, peer.value(), api); EXPECT_FALSE(actual.has_value()); } TEST(IpnsRecordTest, copyctorcopiesfields) { @@ -159,18 +209,16 @@ TEST(IpnsRecordTest, V2_Only_Lean) { auto peer = libp2p::peer::PeerId::fromBase58( "12D3KooWRtQ4MBxXXzioRZHs7NeGuyNHzJiN8X15wxydH4cnGDYZ"); ASSERT_TRUE(peer.has_value()); - auto actual = ipfs::ValidateIpnsRecord( - as_seen_in_spec, peer.value(), - [](auto, auto, auto, auto) { return true; }, - [](ipfs::ByteView c) { - auto v = j::from_cbor(c); - // std::cout << std::setw(2) << v << std::endl; - ipfs::IpnsCborEntry e; - e.sequence = v.at("Sequence"); - auto& bin_val = v.at("Value").get_binary(); - e.value.assign(bin_val.begin(), bin_val.end()); - return e; - }); + Api api{[](ipfs::ByteView c) { + auto v = j::from_cbor(c); + // std::cout << std::setw(2) << v << std::endl; + ipfs::IpnsCborEntry e; + e.sequence = v.at("Sequence"); + auto& bin_val = v.at("Value").get_binary(); + e.value.assign(bin_val.begin(), bin_val.end()); + return e; + }}; + auto actual = ipfs::ValidateIpnsRecord(as_seen_in_spec, peer.value(), api); EXPECT_TRUE(actual); } TEST(IpnsRecordTest, has_pubkey_field) { @@ -271,22 +319,20 @@ TEST(IpnsRecordTest, has_pubkey_field) { auto c = libp2p::multi::ContentIdentifierCodec::fromString( "k2k4r8p8axjle4tulaj8f6423nuwnyjswl3iq7ppmqci5efgn7vg6ah4"); ASSERT_TRUE(c.has_value()); - // SetLevel(ipfs::log::Level::ERROR); - auto actual = ipfs::ValidateIpnsRecord( - as_seen_in_spec, c.value(), [](auto, auto, auto, auto) { return true; }, - [](ipfs::ByteView c) { - auto v = j::from_cbor(c); - // std::cout << std::setw(2) << v << std::endl; - ipfs::IpnsCborEntry e; - e.sequence = v.at("Sequence"); - auto& bin_val = v.at("Value").get_binary(); - e.value.assign(bin_val.begin(), bin_val.end()); - auto& bin_vdty = v.at("Validity").get_binary(); - e.validity.assign(bin_vdty.begin(), bin_vdty.end()); - e.ttl = v.at("TTL"); - e.validityType = v.at("ValidityType"); - return e; - }); + Api api{[](ipfs::ByteView c) { + auto v = j::from_cbor(c); + // std::cout << std::setw(2) << v << std::endl; + ipfs::IpnsCborEntry e; + e.sequence = v.at("Sequence"); + auto& bin_val = v.at("Value").get_binary(); + e.value.assign(bin_val.begin(), bin_val.end()); + auto& bin_vdty = v.at("Validity").get_binary(); + e.validity.assign(bin_vdty.begin(), bin_vdty.end()); + e.ttl = v.at("TTL"); + e.validityType = v.at("ValidityType"); + return e; + }}; + auto actual = ipfs::ValidateIpnsRecord(as_seen_in_spec, c.value(), api); EXPECT_TRUE(actual); } #endif diff --git a/library/src/ipfs_client/orchestrator.cc b/library/src/ipfs_client/orchestrator.cc index de6d2e6a..0482452f 100644 --- a/library/src/ipfs_client/orchestrator.cc +++ b/library/src/ipfs_client/orchestrator.cc @@ -1,5 +1,9 @@ #include "ipfs_client/orchestrator.h" +#include +#include +#include +#include #include #include "ipld/chunk.h" @@ -11,26 +15,40 @@ using namespace std::literals; using Self = ipfs::Orchestrator; -Self::Orchestrator(GatewayAccess ga, MimeDetection mimer) - : gw_requestor_{ga}, mimer_{mimer} {} +Self::Orchestrator(GatewayAccess ga, + std::shared_ptr api, + std::shared_ptr request_handling_override) + : gw_requestor_{ga}, api_{api}, requestor_{request_handling_override} { + if (!requestor_) { + requestor_ = std::make_shared(); + requestor_->or_else(std::make_shared()); + requestor_->or_else(std::make_shared(api)); + } +} void Self::build_response(std::shared_ptr req) { + if (!req->ready_after()) { + return; + } auto req_path = req->path(); LOG(INFO) << "build_response(" << req_path.to_string() << ')'; req_path.pop(); // namespace - std::string origin{req_path.pop()}; - auto it = dags_.find(origin); + std::string affinity{req_path.pop()}; + auto it = dags_.find(affinity); if (dags_.end() == it) { - if (gw_request(req, req->path())) { + if (gw_request(req, req->path(), affinity)) { build_response(req); } } else { - from_tree(req, it->second, req_path); + LOG(INFO) << "Requesting root " << affinity << " resolve path " + << req_path.to_string(); + from_tree(req, it->second, req_path, affinity); } } void Self::from_tree(std::shared_ptr req, ipfs::ipld::NodePtr& node, - SlashDelimited relative_path) { + SlashDelimited relative_path, + std::string const& affinity) { auto root = node->rooted(); auto block_look_up = [this](auto& k) { auto i = dags_.find(k); @@ -52,44 +70,39 @@ void Self::from_tree(std::shared_ptr req, response->mime_ = sniff(SlashDelimited{hit_path}, response->body_); } } - // TODO is this necessary? It might be convenient to have this info - // available, even if it's usually redundant. - // if (response->status_ / 100 != 3) { - // response->location_.clear(); - // } req->finish(*response); } else if (std::get_if(&result)) { req->finish(Response::PLAIN_NOT_FOUND); } else { - for (auto& path : std::get(result).ipfs_abs_paths_) { - if (gw_request(req, std::string_view{path})) { - from_tree(req, node, relative_path); - return; + auto& missing_paths = + std::get(result).ipfs_abs_paths_; + req->till_next(missing_paths.size()); + bool repeat = false; + for (auto& path : missing_paths) { + if (gw_request(req, std::string_view{path}, affinity)) { + repeat = true; } } + if (repeat) { + from_tree(req, node, relative_path, affinity); + } } } bool Self::gw_request(std::shared_ptr ir, - ipfs::SlashDelimited path) { + ipfs::SlashDelimited path, + std::string const& aff) { auto req = gw::GatewayRequest::fromIpfsPath(path); - if (req->type == gw::Type::Identity) { - auto node = - std::make_shared(std::string{req->identity_data()}); - add_node(req->main_param, node); - return true; - } req->dependent = ir; req->orchestrator = shared_from_this(); + req->affinity = aff; gw_requestor_(req); - if (req->type == gw::Type::Car) { - gw_request(ir, path.pop_n(2)); - } return false; } void Self::add_node(std::string key, ipfs::ipld::NodePtr p) { if (p) { LOG(INFO) << "add_node(" << key << ')'; + p->set_api(api_); dags_[key] = p; } else { LOG(ERROR) << "NULL block attempted to be added for " << key; @@ -100,9 +113,9 @@ 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_view ext = ""; + std::string ext = ""; if (dot < file_name.size()) { - ext = file_name.substr(dot + 1); + ext.assign(file_name, dot + 1); } - return mimer_(std::string{ext}, body, fake_url); + return api_->MimeType(ext, body, fake_url); } diff --git a/library/src/ipfs_client/orchestrator_unittest.cc b/library/src/ipfs_client/orchestrator_unittest.cc index 5e023741..04876b0c 100644 --- a/library/src/ipfs_client/orchestrator_unittest.cc +++ b/library/src/ipfs_client/orchestrator_unittest.cc @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -20,26 +21,52 @@ using Success = i::Response; using Codec = libp2p::multi::ContentIdentifierCodec; namespace { +struct FakeApi final : public ipfs::ContextApi { + bool verify_key_signature(ipfs::SigningKeyType, + ipfs::ByteView signature, + ipfs::ByteView data, + ipfs::ByteView key_bytes) const { + return false; + } + std::string MimeType(std::string e, + std::string_view c, + std::string const& u) const { + auto result = "Mime from extension=" + e + " 'url'=" + u + " content='"; + if (c.size() < 9) { + result.append(c); + } else { + result.append(c.substr(0, 8)).append("..."); + } + return result + "'"; + } + std::string UnescapeUrlComponent(std::string_view url_comp) { + return std::string{url_comp}; + } + std::string UnescapeUrlComponent(std::string_view u) const { + return std::string{u}; + } + ipfs::IpnsCborEntry deserialize_cbor(ipfs::ByteView) const { return {}; } + std::shared_ptr InitiateGatewayRequest( + ipfs::BusyGateway) { + return nullptr; + } + void Discover(std::function)> cb) {} + void SendDnsTextRequest(std::string, + DnsTextResultsCallback, + DnsTextCompleteCallback) {} + void SendHttpRequest(ipfs::HttpRequestDescription, + HttpCompleteCallback) const {} +}; struct OrchestratingRealData : public ::testing::Test { - // std::shared_ptr orc_ = std::make_shared( - // [this](auto r) { load_test_data(r->main_param); }); + std::shared_ptr api_ = std::make_shared(); std::shared_ptr orc_; OrchestratingRealData() { auto f = [this](auto r) { load_test_data(r->main_param, *r); }; - auto m = [](auto e, auto c, auto& u) { - auto result = "Mime from extension=" + e + " 'url'=" + u + " content='"; - if (c.size() < 9) { - result.append(c); - } else { - result.append(c.substr(0, 8)).append("..."); - } - return result + "'"; - }; resp_.body_ = "No response received."; resp_.status_ = 0; resp_.mime_ = "uninit - no mime provided"; resp_.location_ = "uninit"; - orc_ = std::make_shared(f, m); + orc_ = std::make_shared(f, api_); } i::Response resp_; void dorequest(std::string_view ipfs_path) { @@ -75,6 +102,7 @@ struct OrchestratingRealData : public ::testing::Test { auto node = ii::DagNode::fromIpnsRecord(testingnoneed2validate); orc_->add_node(cid, node); } break; + case i::gw::Type::Car: case i::gw::Type::Block: { auto blocs_dir = base_dir / "test_data" / "blocks"; EXPECT_TRUE(is_directory(blocs_dir)); @@ -113,8 +141,6 @@ struct OrchestratingRealData : public ::testing::Test { std::getline(fs, target); orc_->add_node(cid, std::make_shared(target)); } break; - case i::gw::Type::Car: - return; default: return; } diff --git a/library/src/ipfs_client/response.cc b/library/src/ipfs_client/response.cc index 062042d4..95cb0d6c 100644 --- a/library/src/ipfs_client/response.cc +++ b/library/src/ipfs_client/response.cc @@ -4,3 +4,8 @@ using Self = ipfs::Response; Self Self::PLAIN_NOT_FOUND{std::string{}, static_cast(404), std::string{}, 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/library/src/ipfs_client/scheduler.cc b/library/src/ipfs_client/scheduler.cc index 71a72963..0f639548 100644 --- a/library/src/ipfs_client/scheduler.cc +++ b/library/src/ipfs_client/scheduler.cc @@ -29,8 +29,7 @@ void ipfs::Scheduler::Enqueue(std::shared_ptr api, std::string_view accept, Priority p, std::shared_ptr top) { - LOG(INFO) << "Scheduler::Enqueue(...," << suffix << ',' << accept << ',' << p - << ')'; + VLOG(1) << "Sched::EnQ(...," << suffix << ',' << accept << ',' << p << ')'; if (!top) { LOG(ERROR) << "No IpfsRequest?"; } diff --git a/library/src/ipfs_client/scoring.md b/library/src/ipfs_client/scoring.md index d6c79916..01b1ce67 100644 --- a/library/src/ipfs_client/scoring.md +++ b/library/src/ipfs_client/scoring.md @@ -25,7 +25,7 @@ It's an unsigned integer in two forms: When issuing a request to another gateway, it checks the gateways in descending-score order: * If the gateway already has a request for that data pending, or has already failed to return successfully, it's skipped. * If the gateways score is less than the number of pending requests currently sent to that gateway, it's skipped as it's considered already-too-busy -* The "need" is generally calculated as the target number of gateways desired to be involved (based on request priority), subtracting the number of gateways currently processing a request for this data. +* The "need" is generally calculated as the target number of gateways desired to be involved (based on request parallel), subtracting the number of gateways currently processing a request for this data. * If the "need" is less than half the count of pending requests on this gateway, it's skipped as it's simply not urgent enough to justify further overloading this gateway. diff --git a/library/src/ipfs_client/signing_key_type.cc b/library/src/ipfs_client/signing_key_type.cc new file mode 100644 index 00000000..b6489a47 --- /dev/null +++ b/library/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/library/src/ipfs_client/unixfs_path_resolver_unittest.cc b/library/src/ipfs_client/unixfs_path_resolver_unittest.cc index b754a7b4..a314ddae 100644 --- a/library/src/ipfs_client/unixfs_path_resolver_unittest.cc +++ b/library/src/ipfs_client/unixfs_path_resolver_unittest.cc @@ -37,6 +37,18 @@ struct Api final : public ipfs::ContextApi { std::string const& url) const { throw 8; } + ipfs::IpnsCborEntry deserialize_cbor(ipfs::ByteView) const { return {}; } + bool verify_key_signature(ipfs::SigningKeyType, + ipfs::ByteView signature, + ipfs::ByteView data, + ipfs::ByteView key_bytes) const { + return false; + } + void SendDnsTextRequest(std::string, + DnsTextResultsCallback, + DnsTextCompleteCallback) {} + void SendHttpRequest(ipfs::HttpRequestDescription, + HttpCompleteCallback cb) const {} std::size_t head_size = 0UL; std::string MimeType(std::string ext, std::string_view cont, diff --git a/library/src/libp2p/common/hexutil.cc b/library/src/libp2p/common/hexutil.cc deleted file mode 100644 index 82416ccf..00000000 --- a/library/src/libp2p/common/hexutil.cc +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright Soramitsu Co., Ltd. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "vocab/byte_view.h" - -#include "libp2p/common/hexutil.hpp" - -#include - -/* -OUTCOME_CPP_DEFINE_CATEGORY(libp2p::common, UnhexError, e) { - using libp2p::common::UnhexError; - switch (e) { - case UnhexError::NON_HEX_INPUT: - return "Input contains non-hex characters"; - case UnhexError::NOT_ENOUGH_INPUT: - return "Input contains odd number of characters"; - default: - return "Unknown error"; - } -} -*/ -namespace libp2p::common { - -std::string int_to_hex(uint64_t n, size_t fixed_width) noexcept { - std::stringstream result; - result.width(static_cast(fixed_width)); - result.fill('0'); - result << std::hex << std::uppercase << n; - auto str = result.str(); - if (str.length() % 2 != 0) { - str.push_back('\0'); - for (int64_t i = static_cast(str.length()) - 2; i >= 0; --i) { - str[i + 1] = str[i]; - } - str[0] = '0'; - } - return str; -} - -namespace { -std::string to_hex(ipfs::ByteView bytes, std::string_view alpha) { - std::string res(bytes.size() * 2, '\x00'); - auto o = res.begin(); - for (auto b : bytes) { - auto i = to_integer(b); - *(o++) = alpha[i / 256]; - *(o++) = alpha[i % 256]; - } - return res; -} -} // namespace - -std::string hex_upper(ipfs::ByteView bytes) noexcept { - return to_hex(bytes, "0123456789ABCDEF"); -} - -std::string hex_lower(ipfs::ByteView bytes) noexcept { - return to_hex(bytes, "0123456789abcdef"); -} - -ipfs::expected, UnhexError> unhex( - std::string_view hex) { - if (hex.size() % 2) { - return ipfs::unexpected(UnhexError::NOT_ENOUGH_INPUT); - } - std::vector blob; - blob.reserve(hex.size() / 2); - bool bad_input = false; - auto toi = [&bad_input](auto c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } else if (c >= 'a' && c <= 'f') { - return c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - return c - 'F' + 10; - } else { - bad_input = true; - return 0; - } - }; - for (auto i = 0U; i < hex.size(); i += 2) { - auto hi = toi(hex[i]); - auto lo = toi(hex[i + 1]); - blob.push_back(static_cast((hi << 4) | lo)); - } - if (bad_input) { - return ipfs::unexpected(UnhexError::NOT_ENOUGH_INPUT); - } else { - return blob; - } -} -} // namespace libp2p::common diff --git a/library/src/libp2p/multi/content_identifier.cc b/library/src/libp2p/multi/content_identifier.cc index 8f84eb8a..6015fbba 100644 --- a/library/src/libp2p/multi/content_identifier.cc +++ b/library/src/libp2p/multi/content_identifier.cc @@ -17,21 +17,6 @@ ContentIdentifier::ContentIdentifier(Version version, content_type{content_type}, content_address{std::move(content_address)} {} -std::string ContentIdentifier::toPrettyString(const std::string& base) const { - /// TODO(Harrm) FIL-14: hash type is a subset of multicodec type, better - /// move them to one place - auto hash_type = MulticodecType::getName( - static_cast(content_address.getType())); - std::string hash_hex = common::hex_lower(content_address.getHash()); - std::string hash_length = - std::to_string(content_address.getHash().size() * 8); - std::string v = "cidv" + std::to_string(static_cast(version)); - std::ostringstream oss; - oss << base << " - " << v << " - " << MulticodecType::getName(content_type) - << " - " << hash_type << '-' << hash_length << '-' << hash_hex; - return oss.str(); -} - bool ContentIdentifier::operator==(const ContentIdentifier& c) const { return version == c.version and content_type == c.content_type and content_address == c.content_address; diff --git a/library/src/libp2p/multi/multihash.cc b/library/src/libp2p/multi/multihash.cc index a7652c34..3667664d 100644 --- a/library/src/libp2p/multi/multihash.cc +++ b/library/src/libp2p/multi/multihash.cc @@ -88,16 +88,6 @@ Result Multihash::create(HashType type, ipfs::ByteView hash) { return Multihash{type, hash}; } -Result Multihash::createFromHex(std::string_view hex) { - auto result = unhex(hex); - if (result.has_value()) { - auto view = ipfs::ByteView{result.value().data(), result.value().size()}; - return Multihash::createFromBytes(view); - } else { - return ipfs::unexpected(Error::INVALID_HEXADECIMAL_INPUT); - } -} - Result Multihash::createFromBytes(ipfs::ByteView b) { if (b.size() < kHeaderSize) { return ipfs::unexpected(Error::INPUT_TOO_SHORT); @@ -136,10 +126,6 @@ ipfs::ByteView Multihash::getHash() const { return ipfs::ByteView(d.bytes.data(), d.bytes.size()).subspan(d.hash_offset); } -std::string Multihash::toHex() const { - return hex_upper(data().bytes); -} - std::vector const& Multihash::toBuffer() const { auto& d = data(); return d.bytes; diff --git a/library/src/libp2p/multi/uvarint.cc b/library/src/libp2p/multi/uvarint.cc index b8b2712c..ca6c5cfd 100644 --- a/library/src/libp2p/multi/uvarint.cc +++ b/library/src/libp2p/multi/uvarint.cc @@ -55,10 +55,6 @@ std::vector const& UVarint::toVector() const { return bytes_; } -std::string UVarint::toHex() const { - return hex_upper(bytes_); -} - size_t UVarint::size() const { return bytes_.size(); } diff --git a/library/src/libp2p/peer/peer_id.cc b/library/src/libp2p/peer/peer_id.cc index 4fd03bf7..be21e302 100644 --- a/library/src/libp2p/peer/peer_id.cc +++ b/library/src/libp2p/peer/peer_id.cc @@ -115,10 +115,6 @@ const std::vector& PeerId::toVector() const { return hash_.toBuffer(); } -std::string PeerId::toHex() const { - return hash_.toHex(); -} - const multi::Multihash& PeerId::toMultihash() const { return hash_; } diff --git a/test_data/blocks/bafkreiagoa7j73kp7wt7odvi3waeab7xbxyebjhbzgguj2uv63t4hsvdri b/test_data/blocks/bafkreiagoa7j73kp7wt7odvi3waeab7xbxyebjhbzgguj2uv63t4hsvdri new file mode 100644 index 00000000..61c5e36b --- /dev/null +++ b/test_data/blocks/bafkreiagoa7j73kp7wt7odvi3waeab7xbxyebjhbzgguj2uv63t4hsvdri @@ -0,0 +1,260 @@ +html { + box-sizing: border-box; +} + +/* *, +*:before, +*:after { + box-sizing: inherit; +} */ + +body { + margin: 0; + border-width: 0; + padding: 0; + display: flex; + justify-content: center; +} + +h1, +h2 { + color: #333; + text-shadow: #1114 1px 1px 1px; + text-align: center; + border-bottom: none; + padding: 0; +} + +#container { + margin: 0; + border-width: 0; + padding: 0; + display: flex; + justify-content: center; + background:black; + position: absolute; + left:0; + top:0; + height:100%; + width:100%; + overflow: auto; +} + +#content { + width: 100%; + overflow: hidden; + font-size: 1em; + background: black; + max-width: 8096px; +} + +#list { + background: white; +} + +.nojs #content { + column-count: 4; + column-gap: 0; +} + +.item { + display: block; + position: relative; + overflow: hidden; +} + +.nojs #content .item { + display: inline-block; + width: 100%; +} + +.item img { + width: 100%; + height: auto; + transition: all 0.3s; + transform: scale(1); + background: white; +} + +.item:hover img { + transform: scale(1.1); +} + +.item figure { + margin: 0; +} + +.item figcaption { + position: absolute; + bottom: 0; + width: 100%; + vertical-align: bottom; + background: rgba(48, 48, 48, 0.5); + color: white; + padding: 0.6em; + font-weight: bold; + transition: all 0.3s; + transform: scale(1); +} + +/* .item:hover figcaption { + transform: scale(1); + bottom: 50%; +} */ + +#footer { + margin: 0; +} + +#footer ul { + margin: 0; + margin-left: 10%; +} + +@media only screen and (min-width: 8096px) { + h1 { + font-size: 0.75vw; + } + + h2 { + font-size: 0.5vw; + } + + #content .item { + width: 3.125%; + } + + .nojs #content { + column-count: 32; + } + + ul { + column-count: 32; + } +} + +@media only screen and (min-width: 4048px) { + h1 { + font-size: 1.5vw; + } + + h2 { + font-size: 1vw; + } + + #content .item { + width: 6.25%; + } + + .nojs #content { + column-count: 16; + } + + ul { + column-count: 16; + } +} + +@media only screen and (max-width: 4047px) and (min-width: 2024px) { + h1 { + font-size: 3vw; + } + + h2 { + font-size: 2vw; + } + + #content .item { + width: 12.5%; + } + + .nojs #content { + column-count: 8; + } + + ul { + column-count: 8; + } +} + +@media only screen and (max-width: 2023px) and (min-width: 1024px) { + h1 { + font-size: 6vw; + } + + h2 { + font-size: 4vw; + } + + #content .item { + width: 25%; + } + + .nojs #content { + column-count: 4; + } + + ul { + column-count: 4; + } +} + +@media only screen and (max-width: 1023px) and (min-width: 768px) { + h1 { + font-size: 6vw; + } + + h2 { + font-size: 4vw; + } + + #content { + font-size: 1em; + } + + #content .item { + width: 33.3333%; + } + + .nojs #content { + column-count: 3; + } + + ul { + column-count: 2; + } + + #footer ul li { + font-size: 0.9em; + } +} + +@media only screen and (max-width: 767px) { + h1 { + font-size: 7vw; + } + + h2 { + font-size: 5vw; + } + + #content { + font-size: 1.2em; + } + + #content .item { + width: 50% + } + + .nojs #content { + column-count: 2; + } + + ul { + column-count: 1; + } + + #footer ul li { + font-size: 1em; + } +} \ No newline at end of file diff --git a/test_data/blocks/bafybeic7wwiyffvkr7xknfpzp7wch53owkr6dxv45cspxn7hfqccdel4fe b/test_data/blocks/bafybeic7wwiyffvkr7xknfpzp7wch53owkr6dxv45cspxn7hfqccdel4fe new file mode 100644 index 00000000..5bad4c76 Binary files /dev/null and b/test_data/blocks/bafybeic7wwiyffvkr7xknfpzp7wch53owkr6dxv45cspxn7hfqccdel4fe differ