From bc1912f3862e9d534b238027bd0b5e89ee46b5c1 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Wed, 1 Nov 2023 17:26:22 -0400 Subject: [PATCH] hacky shoehorn --- cmake/chromium.py | 2 +- cmake/inc_link.py | 12 +- cmake/patch.py | 3 +- cmake/trim.patches.sh | 4 +- component/CMakeLists.txt | 7 +- component/gateway_requests.cc | 33 +- component/gateway_requests.h | 1 + component/inter_request_state.cc | 82 +++- component/inter_request_state.h | 3 + component/ipfs_url_loader.cc | 45 +- component/ipfs_url_loader.h | 1 + component/ipns_url_loader.cc | 4 +- component/patches/121.0.6100.0.patch | 439 ++++++++++++++++++ library/CMakeLists.txt | 1 - library/include/ipfs_client/busy_gateway.h | 20 +- library/include/ipfs_client/context_api.h | 1 + library/include/ipfs_client/gateway.h | 16 +- .../include/ipfs_client/gw/gateway_request.h | 9 +- library/include/ipfs_client/gw/requestor.h | 10 - library/include/ipfs_client/ipld/dag_node.h | 2 +- library/include/ipfs_client/orchestrator.h | 2 +- library/include/ipfs_client/scheduler.h | 13 +- library/include/ipfs_client/url_spec.h | 24 + library/src/ipfs_client/busy_gateway.cc | 41 +- .../src/ipfs_client/busy_gateway_unittest.cc | 4 +- .../src/ipfs_client/context_api_unittest.cc | 2 +- library/src/ipfs_client/gateway.cc | 16 +- .../generated_directory_listing.cc | 10 +- library/src/ipfs_client/gw/gateway_request.cc | 90 +++- library/src/ipfs_client/ipfs_request.cc | 1 + .../src/ipfs_client/ipld/directory_shard.cc | 4 +- .../src/ipfs_client/ipld/directory_shard.h | 1 + library/src/ipfs_client/ipld/ipns_name.h | 1 + .../src/ipfs_client/ipld/small_directory.cc | 34 +- library/src/ipfs_client/orchestrator.cc | 22 +- .../src/ipfs_client/orchestrator_unittest.cc | 123 +++-- library/src/ipfs_client/path2url.cc | 16 +- library/src/ipfs_client/scheduler.cc | 71 +-- library/src/ipfs_client/scheduler_unittest.cc | 4 +- test_data/names/ipfs.tech | 1 + 40 files changed, 974 insertions(+), 201 deletions(-) create mode 100644 component/patches/121.0.6100.0.patch delete mode 100644 library/include/ipfs_client/gw/requestor.h create mode 100644 library/include/ipfs_client/url_spec.h create mode 100644 test_data/names/ipfs.tech diff --git a/cmake/chromium.py b/cmake/chromium.py index cbd1fc89..3ad01d98 100755 --- a/cmake/chromium.py +++ b/cmake/chromium.py @@ -235,7 +235,7 @@ def sync_dir(source_relative, target_relative, complete=True): n = join(out, 'obj', 'components', 'ipfs', 'ipfs.ninja') if not isfile(n) or (isfile(UPDATED) and getmtime(UPDATED) > getmtime(n)): a = [python, join(depot_tools_dir, 'gn.py'), 'gen', '--args='+gnargs.replace("'", ""), out] - print('Running gn gen', a) + verbose('Running gn gen', a) run(a) for target in argv[2:]: diff --git a/cmake/inc_link.py b/cmake/inc_link.py index 4cab1e66..7eced21b 100755 --- a/cmake/inc_link.py +++ b/cmake/inc_link.py @@ -14,9 +14,10 @@ inc_link = join(build_dir,'component','inc_link') chromium_src = vars['CHROMIUM_SOURCE_TREE'].rstrip('/') profile = vars['CHROMIUM_PROFILE'] +gen_dir = join(chromium_src,'out',profile,'gen') source_bases = [ chromium_src, - join(chromium_src,'out',profile,'gen'), + gen_dir, join(chromium_src,'v8','include'), join(chromium_src,'third_party', 'abseil-cpp') ] @@ -119,6 +120,15 @@ def search() -> bool: global preempt link_count = 0 unfound_count = 0 + # ipfs_client_gen = join(gen_dir,'third_party','ipfs_client') + # for h in listdir(ipfs_client_gen): + # if h.endswith('.h'): + # source = join(ipfs_client_gen, h) + # target = join(inc_link, 'third_party', 'ipfs_client', h) + # if isfile(source) and not exists(target): + # print("linked our gen file ",source,target) + # symlink(source, target) + # link_count += 1 compile_commands = json.load(open(join(build_dir,'compile_commands.json'))) commands = [] for command_obj in compile_commands: diff --git a/cmake/patch.py b/cmake/patch.py index fcb93247..d9e1072b 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 = 113 + fuzz = 114 def check(version, version_set, s): i = as_int(version) by = (fuzz,0) @@ -163,7 +163,6 @@ def check(version, version_set, s): by = ( d, a ) if version not in version_set: sortable = [int(c) for c in version.split('.')] - # print('Adding',version,s) version_set[version] = [sortable, version, s] elif s not in version_set[version]: #print('2 Adding',version,s) diff --git a/cmake/trim.patches.sh b/cmake/trim.patches.sh index fe23fc4f..34503510 100755 --- a/cmake/trim.patches.sh +++ b/cmake/trim.patches.sh @@ -1,6 +1,8 @@ #!/bin/bash -ex cd `dirname "${0}"`/../component/patches -okregex='deps .= . "//components/ipfs" ' +#okregex='kEnableIpfs' +#okregex='deps .= . "//components/ipfs" ' +okregex='flag-metadata.json' if ! grep -qE "${okregex}" *.patch then echo "Regex broken: ${okregex}" diff --git a/component/CMakeLists.txt b/component/CMakeLists.txt index b687a142..90ef680a 100644 --- a/component/CMakeLists.txt +++ b/component/CMakeLists.txt @@ -1,3 +1,4 @@ +include(setup) if(NOT CHROMIUM_PROFILE) message(FATAL_ERROR "Your Chromium profile should name a realistic subdir of chromium/src/out, perhaps Debug, Release, or Default") @@ -150,16 +151,20 @@ target_compile_options(out_of_tree PUBLIC ${WARNING_FLAGS} ) +find_package(Protobuf) target_link_libraries(out_of_tree PUBLIC + protobuf::libprotobuf ipfs_client ) target_include_directories(out_of_tree SYSTEM BEFORE - PUBLIC + PRIVATE + "${protobuf_INCLUDE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/inc_link" ) + target_include_directories(out_of_tree PUBLIC ../library/src diff --git a/component/gateway_requests.cc b/component/gateway_requests.cc index 637e23ae..7e609028 100644 --- a/component/gateway_requests.cc +++ b/component/gateway_requests.cc @@ -95,6 +95,9 @@ auto Self::InitiateGatewayRequest(BusyGateway assigned) auto req = std::make_unique(); req->url = GURL{url}; req->priority = net::HIGHEST; // TODO + if (!assigned.accept().empty()) { + req->headers.SetHeader("Accept", assigned.accept()); + } auto out = std::make_shared(std::move(assigned)); GOOGLE_DCHECK_GT(out->gateway->url_prefix().size(), 0U); out->loader = network::SimpleURLLoader::Create(std::move(req), @@ -118,6 +121,8 @@ void Self::OnResponse(std::shared_ptr api, std::shared_ptr req, base::TimeTicks start_time, std::unique_ptr body) { + auto sz = body ? body->size() : 0UL; + LOG(INFO) << "OnResponse(...," << start_time << ", " << sz << "B )"; DCHECK(req); auto task = req->task(); if (task.empty()) { @@ -130,10 +135,13 @@ void Self::OnResponse(std::shared_ptr api, auto& ldr = req->loader; // auto listener = req->listener; if (ProcessResponse(bg, ldr.get(), body.get(), start_time)) { + LOG(INFO) << url << " success."; bg.Success(state_->gateways(), shared_from_this()); } else { + LOG(INFO) << url << " failure."; bg.Failure(state_->gateways(), shared_from_this()); } + VLOG(1) << "Recheck for more activity."; state_->storage().CheckListening(); state_->scheduler().IssueRequests(api); } @@ -152,10 +160,9 @@ bool Self::ProcessResponse(BusyGateway& gw, LOG(ERROR) << "No loader for processing " << gw.url(); return false; } - // LOG(INFO) << "Neterror(" << ldr->NetError() << ')'; if (!body) { - // LOG(INFO) << "ProcessResponse(" << gw.url() - // << ") Null body - presumably http error.\n"; + LOG(INFO) << "ProcessResponse(" << gw.url() + << ") Null body - presumably http error.\n"; return false; } network::mojom::URLResponseHead const* head = ldr->ResponseInfo(); @@ -163,7 +170,7 @@ bool Self::ProcessResponse(BusyGateway& gw, LOG(INFO) << "ProcessResponse(" << gw.url() << ") Null head.\n"; return false; } - GOOGLE_DCHECK_LT(gw.url().find("?format="), gw.url().size()); + DCHECK(gw.url().find("?format=") < gw.url().size() || gw.accept().size() > 0); std::string reported_content_type; head->headers->EnumerateHeader(nullptr, "Content-Type", &reported_content_type); @@ -184,7 +191,10 @@ bool Self::ProcessResponse(BusyGateway& gw, } auto cid_str = gw.task(); cid_str.erase(0, 5); // ipfs/ - cid_str.erase(cid_str.find('?')); + auto qmark = cid_str.find('?'); + if (qmark < cid_str.size()) { + cid_str.erase(qmark); + } if (state_->storage().Get(cid_str)) { // LOG(INFO) << "Got multiple successful responses for " << cid_str; return true; @@ -234,6 +244,15 @@ bool Self::ProcessResponse(BusyGateway& gw, " : load over http(s)\";dur=" + std::to_string(duration)); state_->storage().Store(cid_str, cid.value(), head->headers->raw_headers(), *body); + auto& orc = state_->orchestrator(); + orc.add_node(cid_str, ipld::DagNode::fromBlock(block)); + if (gw.srcreq) { + orc.build_response(gw.srcreq->dependent); + } else { + LOG(ERROR) << "This BusyGateway with response has no top-level " + "IpfsRequest associated with it " + << gw.url() << " " << gw.accept(); + } scheduler().IssueRequests(shared_from_this()); return true; } else { @@ -295,7 +314,9 @@ void Self::RequestByCid(std::string cid, std::shared_ptr listener, Priority prio) { auto me = shared_from_this(); - sched_.Enqueue(me, listener, {}, "ipfs/" + cid + "?format=raw", prio); + LOG(ERROR) << "Look out! RequestByCid(" << cid << ",...," << prio << ')'; + sched_.Enqueue(me, listener, {}, "ipfs/" + cid, "application/vnd.ipld.raw", + prio, {}); sched_.IssueRequests(me); } diff --git a/component/gateway_requests.h b/component/gateway_requests.h index 74f61e88..44f0588e 100644 --- a/component/gateway_requests.h +++ b/component/gateway_requests.h @@ -21,6 +21,7 @@ class URLLoaderFactory; namespace ipfs { class InterRequestState; +class IpfsRequest; class NetworkRequestor; class GatewayRequests final : public ContextApi { diff --git a/component/inter_request_state.cc b/component/inter_request_state.cc index 1c536f37..ee1e2041 100644 --- a/component/inter_request_state.cc +++ b/component/inter_request_state.cc @@ -6,14 +6,19 @@ #include "base/logging.h" #include "content/public/browser/browser_context.h" +#include +#include +#include #include +using Self = ipfs::InterRequestState; + namespace { constexpr char user_data_key[] = "ipfs_request_userdata"; } -auto ipfs::InterRequestState::FromBrowserContext( - content::BrowserContext* context) -> InterRequestState& { +auto Self::FromBrowserContext(content::BrowserContext* context) + -> InterRequestState& { if (!context) { LOG(WARNING) << "No browser context! Using a default IPFS state."; static ipfs::InterRequestState static_state({}); @@ -30,8 +35,7 @@ auto ipfs::InterRequestState::FromBrowserContext( context->SetUserData(user_data_key, std::move(owned)); return *raw; } -auto ipfs::InterRequestState::serialized_caches() - -> std::array { +auto Self::serialized_caches() -> std::array { if (!mem_) { auto p = mem_ = std::make_shared( net::CacheType::MEMORY_CACHE, *this, base::FilePath{}); @@ -46,7 +50,7 @@ auto ipfs::InterRequestState::serialized_caches() } return {mem_, dsk_}; } -auto ipfs::InterRequestState::requestor() -> BlockRequestor& { +auto Self::requestor() -> BlockRequestor& { if (!requestor_.Valid()) { serialized_caches(); requestor_.Add(mem_); @@ -55,7 +59,7 @@ auto ipfs::InterRequestState::requestor() -> BlockRequestor& { } return requestor_; } -std::shared_ptr ipfs::InterRequestState::api() { +std::shared_ptr Self::api() { auto existing = api_.lock(); if (existing) { return existing; @@ -69,11 +73,71 @@ std::shared_ptr ipfs::InterRequestState::api() { } return created; } -auto ipfs::InterRequestState::scheduler() -> Scheduler& { +auto Self::scheduler() -> Scheduler& { auto api = api_.lock(); DCHECK(api); return api->scheduler(); } -ipfs::InterRequestState::InterRequestState(base::FilePath p) : disk_path_{p} {} -ipfs::InterRequestState::~InterRequestState() noexcept {} +namespace { + +void send_gateway_request(Self* me, + std::shared_ptr req) { + if (!req->dependent) { + LOG(FATAL) << "This makes no sense whatsoever - why do you want to request " + "things if nothing awaits."; + } + struct DagListenerAdapter final : public ipfs::DagListener { + std::shared_ptr gw_req; + std::string bytes; + std::shared_ptr api; + void ReceiveBlockBytes(std::string_view b) override { + LOG(INFO) << "DagListenerAdapter::ReceiveBlockBytes(" << b.size() << "B)"; + bytes.assign(b); + } + void BlocksComplete(std::string mime_type) override { + LOG(INFO) << "DagListenerAdapter::BlocksComplete(" << mime_type << ")"; + ipfs::Response r{mime_type, 200, std::move(bytes), ""}; + gw_req->dependent->finish(r); + } + void NotHere(std::string_view cid, std::string_view path) override { + LOG(INFO) << "DagListenerAdapter::NotHere(" << cid << ',' << path << ")"; + api->scheduler().IssueRequests(api); + } + void DoesNotExist(std::string_view cid, std::string_view path) override { + LOG(INFO) << "DagListenerAdapter::DoesNotExist(" << cid << ',' << path + << ")"; + ipfs::Response r{"", 404, "", ""}; + gw_req->dependent->finish(r); + } + }; + auto dl = std::make_shared(); + dl->api = me->api(); + dl->gw_req = req; + auto& sched = me->scheduler(); + sched.Enqueue(me->api(), dl, {}, req->url_suffix().substr(1), req->accept(), + 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); + } + return *orc_; +} + +Self::InterRequestState(base::FilePath p) : disk_path_{p} {} +Self::~InterRequestState() noexcept {} diff --git a/component/inter_request_state.h b/component/inter_request_state.h index 3982bdbf..6b5f1354 100644 --- a/component/inter_request_state.h +++ b/component/inter_request_state.h @@ -7,6 +7,7 @@ #include "ipfs_client/chained_requestors.h" #include "ipfs_client/gateways.h" #include "ipfs_client/ipns_names.h" +#include "ipfs_client/orchestrator.h" #include "base/supports_user_data.h" @@ -26,6 +27,7 @@ class InterRequestState : public base::SupportsUserData::Data { std::time_t last_discovery_ = 0; std::shared_ptr mem_, dsk_; base::FilePath const disk_path_; + std::shared_ptr orc_; // TODO - map of origin to Orchestrator public: InterRequestState(base::FilePath); @@ -38,6 +40,7 @@ class InterRequestState : public base::SupportsUserData::Data { Scheduler& scheduler(); std::shared_ptr api(); std::array,2> serialized_caches(); + Orchestrator& orchestrator(); static InterRequestState& FromBrowserContext(content::BrowserContext*); }; diff --git a/component/ipfs_url_loader.cc b/component/ipfs_url_loader.cc index 30a7bb02..a433b362 100644 --- a/component/ipfs_url_loader.cc +++ b/component/ipfs_url_loader.cc @@ -5,11 +5,14 @@ #include "summarize_headers.h" #include "ipfs_client/gateways.h" +#include "ipfs_client/ipfs_request.h" #include "ipfs_client/unixfs_path_resolver.h" #include "base/debug/stack_trace.h" #include "base/notreached.h" +#include "base/strings/stringprintf.h" #include "base/threading/platform_thread.h" +#include "net/http/http_status_code.h" #include "services/network/public/cpp/parsed_headers.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_loader_factory.mojom.h" @@ -100,14 +103,42 @@ void ipfs::IpfsUrlLoader::StartUnixFsProc(ptr me, std::string_view ipfs_ref) { } VLOG(1) << "cid=" << cid << " remainder=" << remainder; me->root_ = cid; + me->api_->SetLoaderFactory(*lower_loader_factory_); + /* me->resolver_ = std::make_shared( me->state_->storage(), me->state_->requestor(), std::string{cid}, remainder, me->api_); - me->api_->SetLoaderFactory(*lower_loader_factory_); me->stepper_ = std::make_unique(); me->stepper_->Start(FROM_HERE, base::Milliseconds(500), base::BindRepeating(&IpfsUrlLoader::TakeStep, me)); me->TakeStep(); + */ + auto whendone = [me](IpfsRequest const& req, ipfs::Response const& res) { + LOG(INFO) << "whendone(" << req.path().to_string() << ',' << res.status_ + << ',' << res.body_.size() << "B)"; + if (!res.body_.empty()) { + me->ReceiveBlockBytes(res.body_); + } + me->status_ = res.status_; + if (res.status_ / 100 == 4) { + auto p = req.path(); + p.pop(); + std::string cid{p.pop()}; + me->DoesNotExist(cid, p.to_string()); + } else { + me->BlocksComplete(res.mime_); + } + }; + auto abs_path = std::string{"/ipfs/"}; + abs_path.append(cid); + if (!remainder.empty()) { + if (abs_path.back() != '/' && remainder[0] != '/') { + abs_path.push_back('/'); + } + abs_path.append(remainder); + } + auto req = std::make_shared(abs_path, whendone); + me->state_->orchestrator().build_response(req); } void ipfs::IpfsUrlLoader::TakeStep() { @@ -156,10 +187,19 @@ void ipfs::IpfsUrlLoader::BlocksComplete(std::string mime_type) { LOG(ERROR) << "\n\tFailed to create headers!\n"; return; } + auto* reason = + net::GetHttpReasonPhrase(static_cast(status_)); + auto status_line = base::StringPrintf("HTTP/1.1 %d %s", status_, reason); + LOG(INFO) << "Returning with status line '" << status_line << "'.\n"; + head->headers->ReplaceStatusLine(status_line); head->headers->SetHeader("Content-Type", mime_type); head->headers->SetHeader("Access-Control-Allow-Origin", "*"); head->was_fetched_via_spdy = false; - AppendGatewayHeaders(resolver_->involved_cids(), *head->headers); + if (resolver_) { + AppendGatewayHeaders(resolver_->involved_cids(), *head->headers); + } else { + LOG(INFO) << "TODO"; + } for (auto& [n, v] : additional_outgoing_headers_) { VLOG(1) << "Appending 'additional' header:" << n << '=' << v << '.'; head->headers->AddHeader(n, v); @@ -169,7 +209,6 @@ void ipfs::IpfsUrlLoader::BlocksComplete(std::string mime_type) { network::PopulateParsedHeaders(head->headers.get(), GURL{original_url_}); VLOG(1) << "Sending response for " << original_url_ << " with mime type " << head->mime_type << " @" << (void*)(this) - //<< " stack: " << base::debug::StackTrace() ; client_->OnReceiveResponse(std::move(head), std::move(pipe_cons_), absl::nullopt); diff --git a/component/ipfs_url_loader.h b/component/ipfs_url_loader.h index df4f1a69..f0634b3c 100644 --- a/component/ipfs_url_loader.h +++ b/component/ipfs_url_loader.h @@ -80,6 +80,7 @@ class IpfsUrlLoader final : public network::mojom::URLLoader, std::shared_ptr extra_; std::unique_ptr stepper_; std::string root_; + int status_ = 200; void CreateBlockRequest(std::string cid); diff --git a/component/ipns_url_loader.cc b/component/ipns_url_loader.cc index 284365b8..24dfe322 100644 --- a/component/ipns_url_loader.cc +++ b/component/ipns_url_loader.cc @@ -256,8 +256,8 @@ void ipfs::IpnsUrlLoader::CacheHit(std::shared_ptr cache, void ipfs::IpnsUrlLoader::RequestFromGateway() { api_ = state_->api(); api_->SetLoaderFactory(*http_loader_); - state_->scheduler().Enqueue(api_, {}, shared_from_this(), - "ipns/" + host_ + "?format=ipns-record", 3); + state_->scheduler().Enqueue(api_, {}, shared_from_this(), "ipns/" + host_, + "application/vnd.ipfs.ipns-record", 3, {}); state_->scheduler().IssueRequests(api_); } void ipfs::IpnsUrlLoader::Complete() { diff --git a/component/patches/121.0.6100.0.patch b/component/patches/121.0.6100.0.patch new file mode 100644 index 00000000..ec0d91ec --- /dev/null +++ b/component/patches/121.0.6100.0.patch @@ -0,0 +1,439 @@ +diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn +index d79a6c13ce425..0c86c80832d32 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") +@@ -2658,6 +2659,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 938168c67a40c..cee2a2d9fef96 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 +@@ -9791,6 +9796,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 0734e78329fdf..3ddfc82252727 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" +@@ -6167,12 +6176,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) { +@@ -6314,6 +6334,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 06ef7a1e49326..aa891a0d8e9ae 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 73ef16fc27c28..3ffc1a42e8e20 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/library/CMakeLists.txt b/library/CMakeLists.txt index da8bc4af..e2118443 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -68,7 +68,6 @@ foreach(libname ipfs_client ipfs_client_covered) ${WARNING_FLAGS} ) target_include_directories(${libname} - BEFORE PUBLIC include/ "${CMAKE_CURRENT_BINARY_DIR}/gen" diff --git a/library/include/ipfs_client/busy_gateway.h b/library/include/ipfs_client/busy_gateway.h index 64495f2a..e5dccc05 100644 --- a/library/include/ipfs_client/busy_gateway.h +++ b/library/include/ipfs_client/busy_gateway.h @@ -1,6 +1,8 @@ #ifndef IPFS_BUSY_GATEWAY_H_ #define IPFS_BUSY_GATEWAY_H_ +#include + #include #include @@ -11,12 +13,17 @@ struct TestParams; } namespace ipfs { +class ContextApi; class DagListener; class Gateway; class Gateways; -class ContextApi; +class IpfsRequest; class Scheduler; +namespace gw { +struct GatewayRequest; +} + /*! * \brief RAII class embodying the assignment of a given task to a given gateway */ @@ -59,22 +66,25 @@ class BusyGateway { * \brief What task does this refer to? * \return Suffix to the URL being fetched */ - std::string const& task() const { return suffix_; } + std::string const& task() const { return spec_.suffix; } /*! * \brief The full url intended to be fetched here * \return get()->url_prefix() + task() */ - std::string url() const { return prefix_ + suffix_; } + std::string url() const { return prefix_ + task(); } + + std::string_view accept() const { return spec_.accept; } static void TestAccess(TestParams*); + std::shared_ptr srcreq; // TODO no private: friend class Scheduler; - BusyGateway(std::string, std::string, Scheduler*); + BusyGateway(std::string, UrlSpec, Scheduler*); std::string prefix_; - std::string suffix_; + UrlSpec spec_; raw_ptr scheduler_; std::size_t maybe_offset_; }; diff --git a/library/include/ipfs_client/context_api.h b/library/include/ipfs_client/context_api.h index 83b75ce4..139cf14c 100644 --- a/library/include/ipfs_client/context_api.h +++ b/library/include/ipfs_client/context_api.h @@ -7,6 +7,7 @@ #include namespace ipfs { +class IpfsRequest; /*! * \brief Represents an issued request diff --git a/library/include/ipfs_client/gateway.h b/library/include/ipfs_client/gateway.h index 48f6a647..26933953 100644 --- a/library/include/ipfs_client/gateway.h +++ b/library/include/ipfs_client/gateway.h @@ -1,6 +1,8 @@ #ifndef CHROMIUM_IPFS_GATEWAY_H_ #define CHROMIUM_IPFS_GATEWAY_H_ +#include "url_spec.h" + #include "vocab/flat_mapset.h" #include @@ -13,10 +15,10 @@ namespace ipfs { */ class Gateway { std::string prefix_; - flat_map failed_requests_; + flat_map failed_requests_; unsigned priority_; /// This is not request priority. This is how eager we /// are to use this particular gateway. - flat_set tasks_; + flat_set tasks_; public: /*! @@ -43,31 +45,31 @@ class Gateway { * \param need - How important is this task * \return whether the task was accepted */ - bool accept(std::string const& suffix, long need); + bool accept(UrlSpec const& req, long need); /*! * \brief Indicate a task completed successfully * \param ts - The suffix of the task */ - void TaskSuccess(std::string const& ts); + void TaskSuccess(UrlSpec const& ts); /*! * \brief Indicate a task completed unsuccessfully * \param ts - The suffix of the task */ - void TaskFailed(std::string const&); + void TaskFailed(UrlSpec const&); /*! * \brief Indicate a task was cancelled * \param ts - The suffix of the task */ - void TaskCancelled(std::string const&); + void TaskCancelled(UrlSpec const&); /*! * \brief Whether this gateway has previously failed to serve that task * \param suffix - The suffix of the task */ - bool PreviouslyFailed(std::string const& suffix) const; + bool PreviouslyFailed(UrlSpec const&) const; /*! * \return Whether this gateway should be generally preferred over another diff --git a/library/include/ipfs_client/gw/gateway_request.h b/library/include/ipfs_client/gw/gateway_request.h index 5aa6a9ea..cf727d8c 100644 --- a/library/include/ipfs_client/gw/gateway_request.h +++ b/library/include/ipfs_client/gw/gateway_request.h @@ -1,9 +1,12 @@ #ifndef IPFS_TRUSTLESS_REQUEST_H_ #define IPFS_TRUSTLESS_REQUEST_H_ +#include + #include #include +#include #include namespace ipfs { @@ -12,7 +15,7 @@ class Orchestrator; } // namespace ipfs namespace ipfs::gw { -enum class Type { Block, Car, Ipns, DnsLink, Providers }; +enum class Type { Block, Car, Ipns, DnsLink, Providers, Identity }; struct GatewayRequest { Type type; @@ -20,7 +23,11 @@ struct GatewayRequest { std::string path; ///< For CAR requests std::shared_ptr dependent; std::shared_ptr orchestrator; + std::optional cid; + std::string url_suffix() const; + std::string_view accept() const; + std::string_view identity_data() const; static std::shared_ptr fromIpfsPath(SlashDelimited); }; diff --git a/library/include/ipfs_client/gw/requestor.h b/library/include/ipfs_client/gw/requestor.h deleted file mode 100644 index b77be14b..00000000 --- a/library/include/ipfs_client/gw/requestor.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef IPFS_REQUESTOR_H_ -#define IPFS_REQUESTOR_H_ - -#include "gateway_request.h" - -namespace ipfs::gw { -class Requestor {}; -} // namespace ipfs::gw - -#endif // IPFS_REQUESTOR_H_ diff --git a/library/include/ipfs_client/ipld/dag_node.h b/library/include/ipfs_client/ipld/dag_node.h index 805f5d01..10b41af3 100644 --- a/library/include/ipfs_client/ipld/dag_node.h +++ b/library/include/ipfs_client/ipld/dag_node.h @@ -19,7 +19,7 @@ namespace ipfs { class Block; -class ValidatedIpns; +struct ValidatedIpns; } namespace ipfs::ipld { diff --git a/library/include/ipfs_client/orchestrator.h b/library/include/ipfs_client/orchestrator.h index 8d73fe04..716d8b6e 100644 --- a/library/include/ipfs_client/orchestrator.h +++ b/library/include/ipfs_client/orchestrator.h @@ -26,7 +26,7 @@ class Orchestrator : public std::enable_shared_from_this { GatewayAccess gw_requestor_; MimeDetection mimer_; void from_tree(std::shared_ptr, ipld::NodePtr&, SlashDelimited); - void gw_request(std::shared_ptr, SlashDelimited path); + bool gw_request(std::shared_ptr, SlashDelimited path); std::string sniff(SlashDelimited, std::string const&) const; }; } // namespace ipfs::ipld diff --git a/library/include/ipfs_client/scheduler.h b/library/include/ipfs_client/scheduler.h index b6df44d0..a85c165c 100644 --- a/library/include/ipfs_client/scheduler.h +++ b/library/include/ipfs_client/scheduler.h @@ -4,6 +4,7 @@ #include "block_requestor.h" #include "busy_gateway.h" #include "gateways.h" +#include "url_spec.h" #include #include @@ -43,7 +44,9 @@ class Scheduler { std::shared_ptr dl, std::shared_ptr nl, std::string const& sfx, - Priority pio); + std::string_view accept, + Priority pio, + std::shared_ptr top); /*! * \brief Check enqueued requests to see if a gateway request should be made @@ -57,13 +60,13 @@ class Scheduler { * \param task - The task one might consider not enqueuing again. * \return Whether the candidate gateway list is exhausted */ - bool DetectCompleteFailure(std::string task) const; + bool DetectCompleteFailure(UrlSpec const& task) const; /*! * \brief Indicate this task has completed * \param task - URL suffix of the task in question */ - void TaskComplete(std::string const& task); + void TaskComplete(UrlSpec const& task); private: std::function list_gen_; @@ -75,17 +78,17 @@ class Scheduler { std::set> requests; std::set> dag_listeners; std::set> name_listeners; + std::set> source_reqs; Priority priority; long under_target() const; }; - std::map task2todo_; + std::map task2todo_; void Issue(std::shared_ptr, std::shared_ptr&, std::vector todos, unsigned up_to); void CheckSwap(std::size_t); - void UpdateDevPage(); }; } // namespace ipfs diff --git a/library/include/ipfs_client/url_spec.h b/library/include/ipfs_client/url_spec.h new file mode 100644 index 00000000..a61aec25 --- /dev/null +++ b/library/include/ipfs_client/url_spec.h @@ -0,0 +1,24 @@ +#ifndef IPFS_URL_SPEC_H_ +#define IPFS_URL_SPEC_H_ + +// TODO - Give more thought to how this interplays with gw::Request + +#include +#include + +namespace ipfs { +struct UrlSpec { + std::string suffix; + std::string_view accept; + + bool operator<(UrlSpec const& rhs) const { + if (suffix != rhs.suffix) { + return suffix < rhs.suffix; + } + return accept < rhs.accept; + } + bool none() const { return suffix.empty(); } +}; +} // namespace ipfs + +#endif // IPFS_URL_SPEC_H_ diff --git a/library/src/ipfs_client/busy_gateway.cc b/library/src/ipfs_client/busy_gateway.cc index 996b9052..4d96b166 100644 --- a/library/src/ipfs_client/busy_gateway.cc +++ b/library/src/ipfs_client/busy_gateway.cc @@ -1,32 +1,34 @@ #include #include +#include +#include +#include #include "log_macros.h" #include -ipfs::BusyGateway::BusyGateway(std::string pre, - std::string suf, - Scheduler* sched) - : prefix_(pre), suffix_(suf), scheduler_{sched}, maybe_offset_(0UL) {} +ipfs::BusyGateway::BusyGateway(std::string pre, UrlSpec spec, Scheduler* sched) + : prefix_(pre), spec_(spec), scheduler_{sched}, maybe_offset_{0UL} {} ipfs::BusyGateway::BusyGateway(BusyGateway&& rhs) : prefix_(rhs.prefix_), - suffix_(rhs.suffix_), + spec_(rhs.spec_), scheduler_(rhs.scheduler_), maybe_offset_(0UL) { // LOG(INFO) << "BusyGateway(" << prefix_ << ',' << suffix_ << ')'; rhs.prefix_.clear(); - rhs.suffix_.clear(); + rhs.spec_ = {}; rhs.scheduler_ = nullptr; rhs.maybe_offset_ = 0UL; + srcreq = rhs.srcreq; } ipfs::BusyGateway::~BusyGateway() { if (*this && get()) { - (*this)->TaskCancelled(suffix_); + (*this)->TaskCancelled(spec_); } } ipfs::Gateway* ipfs::BusyGateway::get() { - if (!scheduler_ || prefix_.empty() || suffix_.empty()) { + if (!scheduler_ || prefix_.empty() || spec_.none()) { return nullptr; } auto& gws = scheduler_->gateways_; @@ -54,12 +56,12 @@ ipfs::Gateway& ipfs::BusyGateway::operator*() { return *get(); } ipfs::BusyGateway::operator bool() const { - return scheduler_ && prefix_.size() && suffix_.size(); + return scheduler_ && prefix_.size() && !spec_.none(); } void ipfs::BusyGateway::reset() { // LOG(INFO) << "BusyGateway::reset()"; if (scheduler_) { - auto todo_it = scheduler_->task2todo_.find(suffix_); + auto todo_it = scheduler_->task2todo_.find(spec_); if (todo_it != scheduler_->task2todo_.end()) { std::set>& reqs = todo_it->second.requests; @@ -74,7 +76,7 @@ void ipfs::BusyGateway::reset() { scheduler_ = nullptr; } prefix_.clear(); - suffix_.clear(); + spec_ = {}; } void ipfs::BusyGateway::Success(Gateways& g, std::shared_ptr api) { if (prefix_.empty()) { @@ -82,9 +84,9 @@ void ipfs::BusyGateway::Success(Gateways& g, std::shared_ptr api) { } g.promote(prefix_); DCHECK(get()); - get()->TaskSuccess(suffix_); + get()->TaskSuccess(spec_); auto sched = scheduler_; - sched->TaskComplete(suffix_); + sched->TaskComplete(spec_); if (maybe_offset_) { sched->CheckSwap(--maybe_offset_); } @@ -94,14 +96,19 @@ void ipfs::BusyGateway::Success(Gateways& g, std::shared_ptr api) { void ipfs::BusyGateway::Failure(Gateways& g, std::shared_ptr api) { DCHECK(prefix_.size() > 0U); g.demote(prefix_); - get()->TaskFailed(suffix_); + get()->TaskFailed(spec_); auto sched = scheduler_; sched->CheckSwap(maybe_offset_); - if (sched->DetectCompleteFailure(suffix_)) { - LOG(WARNING) << "Giving up on task " << suffix_ + if (sched->DetectCompleteFailure(spec_)) { + LOG(WARNING) << "Giving up on task " << spec_.suffix << " due to complete failure."; - sched->TaskComplete(suffix_); + if (srcreq) { + srcreq->dependent->finish(Response::PLAIN_NOT_FOUND); + } + sched->TaskComplete(spec_); } else { + LOG(INFO) << prefix_ << " gave up on " << spec_.suffix + << " , but maybe others may continue."; sched->IssueRequests(api); } reset(); diff --git a/library/src/ipfs_client/busy_gateway_unittest.cc b/library/src/ipfs_client/busy_gateway_unittest.cc index 840ed2c9..cfea793a 100644 --- a/library/src/ipfs_client/busy_gateway_unittest.cc +++ b/library/src/ipfs_client/busy_gateway_unittest.cc @@ -50,6 +50,6 @@ TEST_F(BusyGatewayTest, get_WithoutScheduler) { } void BusyGateway::TestAccess(TestParams* p) { - p->under_test.reset(new BusyGateway(p->gw_pre, p->tsk_suf, p->sched)); - p->orphan.reset(new BusyGateway(p->gw_pre, p->tsk_suf, {})); + p->under_test.reset(new BusyGateway(p->gw_pre, {p->tsk_suf, ""}, p->sched)); + p->orphan.reset(new BusyGateway(p->gw_pre, {p->tsk_suf, ""}, {})); } diff --git a/library/src/ipfs_client/context_api_unittest.cc b/library/src/ipfs_client/context_api_unittest.cc index 2b7c5797..06e60805 100644 --- a/library/src/ipfs_client/context_api_unittest.cc +++ b/library/src/ipfs_client/context_api_unittest.cc @@ -34,5 +34,5 @@ TEST_F(ContextApiTest,task_is_passthrough) { } void ipfs::BusyGateway::TestAccess(TestParams* p) { - p->under_test.reset(new BusyGateway(p->gw_pre, p->tsk_suf, p->sched)); + p->under_test.reset(new BusyGateway(p->gw_pre, {p->tsk_suf, ""}, p->sched)); } diff --git a/library/src/ipfs_client/gateway.cc b/library/src/ipfs_client/gateway.cc index 008109b3..1d0ebeab 100644 --- a/library/src/ipfs_client/gateway.cc +++ b/library/src/ipfs_client/gateway.cc @@ -20,7 +20,7 @@ bool ipfs::Gateway::operator<(Gateway const& rhs) const { } return prefix_ < rhs.prefix_; } -bool ipfs::Gateway::accept(std::string const& suffix, long need) { +bool ipfs::Gateway::accept(UrlSpec const& spec, long need) { if (need < 0) { return false; } @@ -30,10 +30,10 @@ bool ipfs::Gateway::accept(std::string const& suffix, long need) { if (priority_ < tasks_.size() * tasks_.size()) { return false; } - if (PreviouslyFailed(suffix)) { + if (PreviouslyFailed(spec)) { return false; } - return tasks_.insert(suffix).second; + return tasks_.insert(spec).second; } std::string const& ipfs::Gateway::url_prefix() const { return prefix_; @@ -41,21 +41,21 @@ std::string const& ipfs::Gateway::url_prefix() const { long ipfs::Gateway::load() const { return static_cast(tasks_.size()); } -void ipfs::Gateway::TaskSuccess(std::string const& task) { +void ipfs::Gateway::TaskSuccess(UrlSpec const& task) { tasks_.erase(task); ++priority_; } -void ipfs::Gateway::TaskFailed(std::string const& task) { +void ipfs::Gateway::TaskFailed(UrlSpec const& task) { // LOG(INFO) << prefix_ << task << " TaskFailed"; failed_requests_[task] = std::time(nullptr); priority_ /= 2; tasks_.erase(task); } -void ipfs::Gateway::TaskCancelled(std::string const& task) { +void ipfs::Gateway::TaskCancelled(UrlSpec const& task) { tasks_.erase(task); } -bool ipfs::Gateway::PreviouslyFailed(std::string const& suffix) const { - auto it = failed_requests_.find(suffix); +bool ipfs::Gateway::PreviouslyFailed(UrlSpec const& spec) const { + auto it = failed_requests_.find(spec); if (it == failed_requests_.end()) { return false; } diff --git a/library/src/ipfs_client/generated_directory_listing.cc b/library/src/ipfs_client/generated_directory_listing.cc index 31e5edec..a3ffdc84 100644 --- a/library/src/ipfs_client/generated_directory_listing.cc +++ b/library/src/ipfs_client/generated_directory_listing.cc @@ -1,5 +1,7 @@ #include "generated_directory_listing.h" +#include "log_macros.h" + ipfs::GeneratedDirectoryListing::GeneratedDirectoryListing( std::string_view base_path) : html_("\n "), base_path_(base_path) { @@ -23,12 +25,14 @@ ipfs::GeneratedDirectoryListing::GeneratedDirectoryListing( } void ipfs::GeneratedDirectoryListing::AddEntry(std::string_view name) { - auto path = base_path_; - path.append(name); - AddLink(name, path); + // auto path = base_path_; + // path.append(name); + // AddLink(name, path); + AddLink(name, name); } void ipfs::GeneratedDirectoryListing::AddLink(std::string_view name, std::string_view path) { + LOG(INFO) << "Adding link to generated index.html " << name << '=' << path; html_.append(" <li>\n") .append(" <a href='") .append(path) diff --git a/library/src/ipfs_client/gw/gateway_request.cc b/library/src/ipfs_client/gw/gateway_request.cc index a098b8ec..69de795e 100644 --- a/library/src/ipfs_client/gw/gateway_request.cc +++ b/library/src/ipfs_client/gw/gateway_request.cc @@ -1,23 +1,101 @@ #include "ipfs_client/gw/gateway_request.h" +#include "ipfs_client/response.h" + #include "log_macros.h" +#include <libp2p/multi/content_identifier_codec.hpp> + +using namespace std::literals; + using Self = ipfs::gw::GatewayRequest; +using CidCodec = libp2p::multi::ContentIdentifierCodec; std::shared_ptr<Self> Self::fromIpfsPath(ipfs::SlashDelimited p) { auto name_space = p.pop(); auto result = std::make_shared<Self>(); + result->main_param = p.pop(); + auto maybe_cid = CidCodec::fromString(result->main_param); + if (maybe_cid.has_value()) { + result->cid = maybe_cid.value(); + } else { + result->cid = std::nullopt; + } if (name_space == "ipfs") { - result->main_param = p.pop(); - result->path = p.pop_all(); - result->type = result->path.empty() ? Type::Block : Type::Car; + if (!result->cid.has_value()) { + LOG(ERROR) << "IPFS request with invalid/unsupported CID " + << result->main_param; + } + if (result->cid.value().content_address.getType() == + libp2p::multi::HashType::identity) { + result->type = Type::Identity; + } else { + result->path = p.pop_all(); + result->type = result->path.empty() ? Type::Block : Type::Car; + } } else if (name_space == "ipns") { - result->main_param = p.pop(); result->path = p.pop_all(); - result->type = Type::Ipns; + if (CidCodec::fromString(result->main_param).has_value()) { + result->type = Type::Ipns; + } else { + result->type = Type::DnsLink; + } } else { LOG(FATAL) << "Unsupported namespace in ipfs path: /" << name_space << '/' << p.pop_all(); } return result; -} \ No newline at end of file +} + +std::string Self::url_suffix() const { + switch (type) { + case Type::Block: + return "/ipfs/" + main_param; + case Type::Car: + return "/ipfs/" + main_param + "/" + path + "?dag-scope=entity"; + case Type::Ipns: + return "/ipns/" + main_param; + case Type::Providers: + return "/routing/v1/providers/" + main_param; + case Type::DnsLink: + LOG(FATAL) << "Don't try to use HTTP(s) for DNS TXT records."; + return {}; + default: + LOG(FATAL) << "Invalid gateway request type: " << static_cast<int>(type); + return {}; + } +} +std::string_view Self::accept() const { + switch (type) { + case Type::Block: + return "application/vnd.ipld.raw"sv; + case Type::Ipns: + return "application/vnd.ipfs.ipns-record"sv; + case Type::Car: + return "application/vnd.ipld.car"sv; + case Type::Providers: + return "application/json"sv; + case Type::DnsLink: + // TODO : not sure this advice is 100% good, actually. + // If the user's system setup allows for text records to actually work, + // it would be good to respect their autonomy and try to follow the + // system's DNS setup. However, it's extremely easy to get yourself in a + // situation where Chromium _cannot_ access text records. If you're in + // that scenario, it might be better to try to use an IPFS gateway with + // DNSLink capability. + LOG(FATAL) << "Don't try to use HTTP(s) for DNS TXT records."; + return {}; + default: + LOG(FATAL) << "Invalid gateway request type: " << static_cast<int>(type); + return {}; + } +} + +auto Self::identity_data() const -> std::string_view { + if (type != Type::Identity) { + return ""; + } + auto hash = cid.value().content_address.getHash(); + auto d = reinterpret_cast<char const*>(hash.data()); + return std::string_view{d, hash.size()}; +} diff --git a/library/src/ipfs_client/ipfs_request.cc b/library/src/ipfs_client/ipfs_request.cc index a7b92e96..5e25e687 100644 --- a/library/src/ipfs_client/ipfs_request.cc +++ b/library/src/ipfs_client/ipfs_request.cc @@ -10,6 +10,7 @@ Self::IpfsRequest(std::string path_p, Finisher f) : path_{path_p}, callback_{f} {} void Self::finish(ipfs::Response& r) { + LOG(INFO) << "IpfsRequest::finish(...);"; callback_(*this, r); // TODO - cancel other gw req pointing into this callback_ = [](auto& q, auto&) { diff --git a/library/src/ipfs_client/ipld/directory_shard.cc b/library/src/ipfs_client/ipld/directory_shard.cc index 30627e82..4e0032fe 100644 --- a/library/src/ipfs_client/ipld/directory_shard.cc +++ b/library/src/ipfs_client/ipld/directory_shard.cc @@ -6,9 +6,10 @@ #include <smhasher/MurmurHash3.h> -#define ABSL_USES_STD_STRING_VIEW +#define ABSL_USES_STD_STRING_VIEW 1 #include <absl/strings/match.h> +#include <array> #include <iomanip> #include <sstream> @@ -95,6 +96,7 @@ std::vector<std::string> Self::hexhash(std::string_view path_element) const { } Self::DirShard(std::uint64_t fanout) : fanout_{fanout} {} +Self::~DirShard() {} Self* Self::as_hamt() { return this; } diff --git a/library/src/ipfs_client/ipld/directory_shard.h b/library/src/ipfs_client/ipld/directory_shard.h index 6534ad41..0a4a6cca 100644 --- a/library/src/ipfs_client/ipld/directory_shard.h +++ b/library/src/ipfs_client/ipld/directory_shard.h @@ -21,6 +21,7 @@ class DirShard : public DagNode { public: explicit DirShard(std::uint64_t fanout = 256UL); + virtual ~DirShard() noexcept; }; } // namespace ipfs::ipld diff --git a/library/src/ipfs_client/ipld/ipns_name.h b/library/src/ipfs_client/ipld/ipns_name.h index 17db9f2a..33648483 100644 --- a/library/src/ipfs_client/ipld/ipns_name.h +++ b/library/src/ipfs_client/ipld/ipns_name.h @@ -14,6 +14,7 @@ class IpnsName : public DagNode { public: IpnsName(std::string target_abs_path); + virtual ~IpnsName() noexcept {} }; } // namespace ipfs::ipld diff --git a/library/src/ipfs_client/ipld/small_directory.cc b/library/src/ipfs_client/ipld/small_directory.cc index 9168080a..ccff62e0 100644 --- a/library/src/ipfs_client/ipld/small_directory.cc +++ b/library/src/ipfs_client/ipld/small_directory.cc @@ -3,6 +3,8 @@ #include "ipfs_client/generated_directory_listing.h" #include "ipfs_client/path2url.h" +#include "log_macros.h" + using namespace std::literals; using Self = ipfs::ipld::SmallDirectory; @@ -15,27 +17,18 @@ ipfs::ipld::NodePtr& node(ipfs::ipld::Link& l, } return l.node; } -ipfs::ipld::NodePtr& node(std::pair<std::string, ipfs::ipld::Link>& l, - ipfs::ipld::DagNode::BlockLookup f) { - return node(l.second, f); -} -std::string const& name(std::pair<std::string, ipfs::ipld::Link> const& p) { - return p.first; -} -std::string const& cid(std::pair<std::string, ipfs::ipld::Link> const& p) { - return p.second.cid; -} } // namespace auto Self::resolve(SlashDelimited path, BlockLookup blu, std::string& to_here) -> ResolveResult { if (!path) { - GeneratedDirectoryListing index_html{path2url(to_here)}; - for (auto& l : links_) { - if (name(l) != "index.html") { - index_html.AddEntry(name(l)); - } else { - auto& n = node(l, blu); + // GeneratedDirectoryListing index_html{path2url(to_here)}; + GeneratedDirectoryListing index_html{to_here}; + for (auto& [name, link] : links_) { + LOG(INFO) << "Listing " << to_here << " encountered " << name << '=' + << link.cid; + if (name == "index.html") { + auto& n = node(link, blu); if (n) { auto result = n->resolve(""sv, blu, to_here.append("/index.html")); auto resp = std::get_if<Response>(&result); @@ -44,8 +37,10 @@ auto Self::resolve(SlashDelimited path, BlockLookup blu, std::string& to_here) } return result; } else { - return MoreDataNeeded{std::vector{"/ipfs/" + cid(l)}}; + return MoreDataNeeded{std::vector{"/ipfs/" + link.cid}}; } + } else { + index_html.AddEntry(name); } } return Response{"text/html", 200, index_html.Finish(), ""}; @@ -57,7 +52,8 @@ auto Self::resolve(SlashDelimited path, BlockLookup blu, std::string& to_here) if (links_.end() == it) { return ProvenAbsent{}; } - auto& nod = node(*it, blu); + auto& link = it->second; + auto& nod = node(link, blu); if (nod) { if (to_here.back() != '/') { to_here.push_back('/'); @@ -65,7 +61,7 @@ auto Self::resolve(SlashDelimited path, BlockLookup blu, std::string& to_here) to_here.append(name); return nod->resolve(path, blu, to_here); } else { - return MoreDataNeeded{std::vector{"/ipfs/" + cid(*it)}}; + return MoreDataNeeded{std::vector{"/ipfs/" + link.cid}}; } } diff --git a/library/src/ipfs_client/orchestrator.cc b/library/src/ipfs_client/orchestrator.cc index 2c2f1b46..de6d2e6a 100644 --- a/library/src/ipfs_client/orchestrator.cc +++ b/library/src/ipfs_client/orchestrator.cc @@ -2,6 +2,8 @@ #include <ipfs_client/ipfs_request.h> +#include "ipld/chunk.h" + #include "log_macros.h" #include "path2url.h" @@ -14,11 +16,14 @@ Self::Orchestrator(GatewayAccess ga, MimeDetection mimer) void Self::build_response(std::shared_ptr<IpfsRequest> req) { 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); if (dags_.end() == it) { - gw_request(req, req->path()); + if (gw_request(req, req->path())) { + build_response(req); + } } else { from_tree(req, it->second, req_path); } @@ -57,23 +62,34 @@ void Self::from_tree(std::shared_ptr<IpfsRequest> req, req->finish(Response::PLAIN_NOT_FOUND); } else { for (auto& path : std::get<ipld::MoreDataNeeded>(result).ipfs_abs_paths_) { - gw_request(req, std::string_view{path}); + if (gw_request(req, std::string_view{path})) { + from_tree(req, node, relative_path); + return; + } } } } -void Self::gw_request(std::shared_ptr<IpfsRequest> ir, +bool Self::gw_request(std::shared_ptr<IpfsRequest> ir, ipfs::SlashDelimited path) { auto req = gw::GatewayRequest::fromIpfsPath(path); + if (req->type == gw::Type::Identity) { + auto node = + std::make_shared<ipld::Chunk>(std::string{req->identity_data()}); + add_node(req->main_param, node); + return true; + } req->dependent = ir; req->orchestrator = shared_from_this(); 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 << ')'; dags_[key] = p; } else { LOG(ERROR) << "NULL block attempted to be added for " << key; diff --git a/library/src/ipfs_client/orchestrator_unittest.cc b/library/src/ipfs_client/orchestrator_unittest.cc index 7b98896d..804e6370 100644 --- a/library/src/ipfs_client/orchestrator_unittest.cc +++ b/library/src/ipfs_client/orchestrator_unittest.cc @@ -6,7 +6,9 @@ #include <ipfs_client/ipfs_request.h> #include <ipfs_client/ipns_record.h> #include <ipfs_client/ipns_record.pb.h> -#include "libp2p/multi/content_identifier_codec.hpp" +#include <libp2p/multi/content_identifier_codec.hpp> + +#include "ipld/ipns_name.h" #include <filesystem> #include <fstream> @@ -53,47 +55,71 @@ struct OrchestratingRealData : public ::testing::Test { base_dir.generic_string().size() > 2) { base_dir = base_dir.parent_path(); } - if (r.type == i::gw::Type::Ipns) { - auto dir = base_dir / "test_data" / "names"; - auto f = dir / cid; - EXPECT_TRUE(is_regular_file(f)) << cid << " missing"; - if (!is_regular_file(f)) { - auto cmd = "ipfs routing get /ipns/" + cid + " > " + f.generic_string(); - auto ec = std::system(cmd.c_str()); - EXPECT_EQ(ec, 0); - } - ipfs::ipns::IpnsEntry entry; - std::ifstream fs{f}; - auto parsed = entry.ParseFromIstream(&fs); - EXPECT_TRUE(parsed); - ipfs::ValidatedIpns testingnoneed2validate; - testingnoneed2validate.value = entry.value(); - auto node = ii::DagNode::fromIpnsRecord(testingnoneed2validate); - orc_->add_node(cid, node); - // retry - r.orchestrator->build_response(r.dependent); - return; - } - auto blocs_dir = base_dir / "test_data" / "blocks"; - EXPECT_TRUE(is_directory(blocs_dir)); - auto f = blocs_dir / cid; - EXPECT_TRUE(is_regular_file(f)) << cid << " missing"; - if (is_regular_file(f)) { - std::ifstream fs{f}; - ipfs::Block block{Codec::fromString(cid).value(), fs}; - EXPECT_TRUE(block.valid()) << f; - auto new_node = ii::DagNode::fromBlock(block); - orc_->add_node(cid, new_node); - // retry - r.orchestrator->build_response(r.dependent); - } else { - auto cmd = "ipfs block get " + cid + " > '" + f.generic_string() + "'"; - std::cout << cmd << '\n'; - auto ec = std::system(cmd.c_str()); - EXPECT_EQ(ec, 0); - resp_.status_ = static_cast<std::uint16_t>(987); - resp_.body_ = cid + " fetched"; + switch (r.type) { + case i::gw::Type::Ipns: { + auto dir = base_dir / "test_data" / "names"; + auto f = dir / cid; + EXPECT_TRUE(is_regular_file(f)) << cid << " missing"; + if (!is_regular_file(f)) { + auto cmd = + "ipfs routing get /ipns/" + cid + " > " + f.generic_string(); + auto ec = std::system(cmd.c_str()); + EXPECT_EQ(ec, 0); + } + ipfs::ipns::IpnsEntry entry; + std::ifstream fs{f}; + auto parsed = entry.ParseFromIstream(&fs); + EXPECT_TRUE(parsed); + ipfs::ValidatedIpns testingnoneed2validate; + testingnoneed2validate.value = entry.value(); + auto node = ii::DagNode::fromIpnsRecord(testingnoneed2validate); + orc_->add_node(cid, node); + } break; + case i::gw::Type::Block: { + auto blocs_dir = base_dir / "test_data" / "blocks"; + EXPECT_TRUE(is_directory(blocs_dir)); + auto f = blocs_dir / cid; + EXPECT_TRUE(is_regular_file(f)) << cid << " missing"; + if (is_regular_file(f)) { + std::ifstream fs{f}; + ipfs::Block block{Codec::fromString(cid).value(), fs}; + EXPECT_TRUE(block.valid()) << f; + auto new_node = ii::DagNode::fromBlock(block); + orc_->add_node(cid, new_node); + } else { + auto cmd = + "ipfs block get " + cid + " > '" + f.generic_string() + "'"; + std::cout << cmd << '\n'; + auto ec = std::system(cmd.c_str()); + EXPECT_EQ(ec, 0); + resp_.status_ = static_cast<std::uint16_t>(987); + resp_.body_ = cid + " fetched"; + return; + } + } break; + case i::gw::Type::DnsLink: { + auto dir = base_dir / "test_data" / "names"; + auto f = dir / cid; + EXPECT_TRUE(is_regular_file(f)) << cid << " missing"; + if (!is_regular_file(f)) { + auto cmd = "host -t TXT _dnslink." + cid + + " | grep dnslink= | cut -d '=' -f 2- | tr -d '\"' > " + + f.generic_string(); + auto ec = std::system(cmd.c_str()); + EXPECT_EQ(ec, 0); + } + std::ifstream fs{f}; + std::string target; + std::getline(fs, target); + orc_->add_node(cid, std::make_shared<ipfs::ipld::IpnsName>(target)); + } break; + case i::gw::Type::Car: + return; + default: + return; } + // retry + r.orchestrator->build_response(r.dependent); } std::string abs_path(std::string rest) { return "/ipfs/QmYBhLYDwVFvxos9h8CGU2ibaY66QNgv8hpfewxaQrPiZj" + rest; @@ -184,10 +210,10 @@ TEST_F(OrchestratingRealData, examples_articles_generates_list) { <body> <ul> <li> - <a href='/ipfs://QmQyqMY5vUBSbSxyitJqthgwZunCQjDVtNd8ggVCxzuPQ4/examples/'>..</a> + <a href='ipfs://QmQyqMY5vUBSbSxyitJqthgwZunCQjDVtNd8ggVCxzuPQ4/examples/'>..</a> </li> <li> - <a href='/ipfs://QmQyqMY5vUBSbSxyitJqthgwZunCQjDVtNd8ggVCxzuPQ4/examples/articles/2022'>2022</a> + <a href='ipfs://QmQyqMY5vUBSbSxyitJqthgwZunCQjDVtNd8ggVCxzuPQ4/examples/articles/2022'>2022</a> </li> </ul> </body> @@ -264,3 +290,14 @@ TEST_F(OrchestratingRealData, ipns) { <title>An open system to manage data without a central server | IPFS )"); } +TEST_F(OrchestratingRealData, dnslink) { + dorequest("/ipns/ipfs.tech"); + EXPECT_EQ(resp_.status_, 200); + EXPECT_EQ(resp_.mime_, "text/html"); + EXPECT_EQ(resp_.body_.substr(0, 212), R"( + + + +An open system to manage data without a central server | IPFS +)"); +} diff --git a/library/src/ipfs_client/path2url.cc b/library/src/ipfs_client/path2url.cc index 160083ed..0d7cf305 100644 --- a/library/src/ipfs_client/path2url.cc +++ b/library/src/ipfs_client/path2url.cc @@ -3,14 +3,14 @@ #include "log_macros.h" std::string ipfs::path2url(std::string p) { - DCHECK(p.size() > 6); - DCHECK_EQ(p.at(0), '/'); - DCHECK_EQ(p.at(1), 'i'); - DCHECK_EQ(p.at(2), 'p'); - DCHECK(p.at(3) == 'f' || p.at(3) == 'n'); - DCHECK_EQ(p.at(4), 's'); - DCHECK_EQ(p.at(5), '/'); - p.erase(0, 1); // drop leading slash + while (!p.empty() && p[0] == '/') { + p.erase(0UL, 1UL); + } + DCHECK_EQ(p.at(0), 'i'); + DCHECK_EQ(p.at(1), 'p'); + DCHECK(p.at(2) == 'f' || p.at(2) == 'n'); + DCHECK_EQ(p.at(3), 's'); + DCHECK_EQ(p.at(4), '/'); p.insert(4, ":/"); return p; } diff --git a/library/src/ipfs_client/scheduler.cc b/library/src/ipfs_client/scheduler.cc index 88f424b3..71a72963 100644 --- a/library/src/ipfs_client/scheduler.cc +++ b/library/src/ipfs_client/scheduler.cc @@ -1,7 +1,11 @@ #include #include +#include +#include #include +#include +#include #include "log_macros.h" @@ -22,11 +26,19 @@ void ipfs::Scheduler::Enqueue(std::shared_ptr api, std::shared_ptr dag_listener, std::shared_ptr name_listener, std::string const& suffix, - Priority p) { + std::string_view accept, + Priority p, + std::shared_ptr top) { + LOG(INFO) << "Scheduler::Enqueue(...," << suffix << ',' << accept << ',' << p + << ')'; + if (!top) { + LOG(ERROR) << "No IpfsRequest?"; + } if (suffix.empty()) { LOG(ERROR) << "Do not issue a request with no task!"; } else { - auto& todo = task2todo_[suffix]; + auto key = UrlSpec{suffix, accept}; + auto& todo = task2todo_[key]; todo.priority = std::max(todo.priority, p); if (dag_listener) { todo.dag_listeners.insert(dag_listener); @@ -34,6 +46,11 @@ void ipfs::Scheduler::Enqueue(std::shared_ptr api, if (name_listener) { todo.name_listeners.insert(name_listener); } + if (top) { + todo.source_reqs.insert(top); + } else { + LOG(WARNING) << "Enqueue with no top: " << suffix; + } } IssueRequests(api); } @@ -42,11 +59,19 @@ bool ipfs::Scheduler::IssueRequests(std::shared_ptr api) { decltype(task2todo_)::value_type* unmet = nullptr; auto assign = [this, api](auto& gw, auto& task, auto& todo, auto need) { if (gw.accept(task, need)) { - auto req = - api->InitiateGatewayRequest(BusyGateway{gw.url_prefix(), task, this}); + BusyGateway bgw{gw.url_prefix(), task, this}; + std::shared_ptr top; + if (todo.source_reqs.empty()) { + LOG(ERROR) << "IssueRequests with no top-level requests awaiting!"; + } else { + top = *todo.source_reqs.begin(); // This is wrong, but so is + // everything about this + } + bgw.srcreq = top; + auto req = api->InitiateGatewayRequest(std::move(bgw)); todo.requests.insert(req); - // LOG(INFO) << "Initiated request " << req->url() << " (" << need << - // ')'; + VLOG(2) << "Initiated request " << req->url() << " (" << need << ')' + << todo.source_reqs.size() << " await."; return true; } return false; @@ -71,11 +96,10 @@ bool ipfs::Scheduler::IssueRequests(std::shared_ptr api) { } } } - // UpdateDevPage(); return !unmet; } -bool ipfs::Scheduler::DetectCompleteFailure(std::string task) const { +bool ipfs::Scheduler::DetectCompleteFailure(UrlSpec const& task) const { auto fail_count = std::count_if(gateways_.begin(), gateways_.end(), [&task](auto& g) { return g.PreviouslyFailed(task); }); @@ -89,38 +113,23 @@ void ipfs::Scheduler::CheckSwap(std::size_t index) { std::swap(gateways_[index], gateways_[index + 1UL]); } } -void ipfs::Scheduler::UpdateDevPage() { - { - std::ofstream f{"temp.devpage.html"}; - f << "IPFS Gateway Requests" - << "

TODOs: " << task2todo_.size() << "

\n"; - using namespace std::literals; - for (auto& e : task2todo_) { - auto& task = e.first; - auto& todo = e.second; - f << " \n"; - } - f << "
" << task << "\n"; - for (auto& req : todo.requests) { - f << "

" << req->gateway->url_prefix() << "

\n"; - } - f << "
"; - } - std::rename("temp.devpage.html", "devpage.html"); -} -void ipfs::Scheduler::TaskComplete(std::string const& task) { +void ipfs::Scheduler::TaskComplete(UrlSpec const& task) { auto todo = task2todo_.find(task); if (task2todo_.end() == todo) { - VLOG(2) << "An unknown TODO " << task << " finished."; + VLOG(2) << "An unknown TODO " << task.suffix << " finished."; return; } - VLOG(1) << "Task " << task << " completed with " - << todo->second.name_listeners.size() << " name listeners."; + LOG(INFO) << "Task " << task.suffix << " completed with " + << todo->second.name_listeners.size() << " name listeners and " + << todo->second.source_reqs.size() << " IpfsRequest s"; // Don't need to call back on dag listeners because storage covered that for (auto& nl : todo->second.name_listeners) { LOG(INFO) << "Notifying a name listener that its listener is ready."; nl->Complete(); } + for (auto& r : todo->second.source_reqs) { + r->orchestrator->build_response(r->dependent); + } task2todo_.erase(todo); } diff --git a/library/src/ipfs_client/scheduler_unittest.cc b/library/src/ipfs_client/scheduler_unittest.cc index 3f80ad2b..27596b9a 100644 --- a/library/src/ipfs_client/scheduler_unittest.cc +++ b/library/src/ipfs_client/scheduler_unittest.cc @@ -36,11 +36,11 @@ TEST_F(SchedulerTest, EmptyNewSchedulerIsNotOverburdened) { EXPECT_TRUE(under_test_.IssueRequests({})); } TEST_F(SchedulerTest, EmptySchedulerIsAlwaysCompleteFailureForAnything) { - EXPECT_TRUE(under_test_.DetectCompleteFailure("No such task")); + EXPECT_TRUE(under_test_.DetectCompleteFailure({"No such task", ""})); } TEST_F(SchedulerTest, NonExistentTaskCompletingIsHarmless) { //If we wanted to we could assert a log line is written at verbose level - EXPECT_NO_THROW(under_test_.TaskComplete("No such task")); + EXPECT_NO_THROW(under_test_.TaskComplete({"No such task", ""})); } /* TEST_F(SchedulerTest, NoSuffixErrorMessage) { diff --git a/test_data/names/ipfs.tech b/test_data/names/ipfs.tech new file mode 100644 index 00000000..4dc6add8 --- /dev/null +++ b/test_data/names/ipfs.tech @@ -0,0 +1 @@ +/ipfs/QmRzVCV5KLFQNDPCe2Pr1Rg6CECbdrnNFzXFe2XgZNNsJJ