From a9b18bfc6356e88204582f428de56c6c5452aeef Mon Sep 17 00:00:00 2001 From: John Turpish Date: Thu, 8 Feb 2024 17:09:58 -0500 Subject: [PATCH] web_utils --- .../chrome_content_browser_client.cc.patch | 3 +- component/cache_requestor.cc | 3 +- component/chromium_cbor_adapter.cc | 13 +++++ component/chromium_cbor_adapter.h | 8 ++- component/chromium_ipfs_context.cc | 26 ++++----- component/chromium_ipfs_context.h | 6 -- component/ipfs_url_loader.cc | 2 +- library/include/ipfs_client/context_api.h | 17 ++++-- .../ipfs_client/ipld/resolution_state.h | 2 +- library/include/ipfs_client/test_context.h | 45 --------------- library/src/ipfs_client/car.cc | 8 +-- library/src/ipfs_client/car.h | 2 +- library/src/ipfs_client/context_api.cc | 27 +++++++++ .../src/ipfs_client/ctx/default_gateways.cc | 25 ++++----- library/src/ipfs_client/ipld/dag_node.cc | 2 +- .../src/ipfs_client/ipld/resolution_state.cc | 2 +- library/src/ipfs_client/ipns_record.cc | 4 +- .../src/ipfs_client/orchestrator_unittest.cc | 24 ++------ library/src/ipfs_client/web_util.cc | 55 +++++++++++++++++++ library/src/ipfs_client/web_util.h | 14 +++++ ...4eFTrYzvGm7WMZYjNn5zW51jrhnzpwuKYf3MR23Ny8 | 2 - ...eM4Hig9RiNKBDmtZqbeJ2ZYNNjQnrJWqPtkpySV4Yc | 2 - ...auzbmuo257jxjtogqhoan6vgly3u4obtpoemubdn5i | 1 - 23 files changed, 167 insertions(+), 126 deletions(-) create mode 100644 library/src/ipfs_client/web_util.cc create mode 100644 library/src/ipfs_client/web_util.h delete mode 100644 test_data/blocks/QmVB4eFTrYzvGm7WMZYjNn5zW51jrhnzpwuKYf3MR23Ny8 delete mode 100644 test_data/blocks/QmVCeM4Hig9RiNKBDmtZqbeJ2ZYNNjQnrJWqPtkpySV4Yc delete mode 100644 test_data/blocks/bafkreib7pg5xwq23auzbmuo257jxjtogqhoan6vgly3u4obtpoemubdn5i diff --git a/chromium_edits/122.0.6226.2/chrome/browser/chrome_content_browser_client.cc.patch b/chromium_edits/122.0.6226.2/chrome/browser/chrome_content_browser_client.cc.patch index f6f88ae7..f4ba2c40 100644 --- a/chromium_edits/122.0.6226.2/chrome/browser/chrome_content_browser_client.cc.patch +++ b/chromium_edits/122.0.6226.2/chrome/browser/chrome_content_browser_client.cc.patch @@ -46,7 +46,8 @@ index 23ee785c34a30..8550b43c61f2f 100644 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(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(); diff --git a/component/cache_requestor.cc b/component/cache_requestor.cc index 49d3cb7c..ea73bb35 100644 --- a/component/cache_requestor.cc +++ b/component/cache_requestor.cc @@ -123,8 +123,7 @@ void Self::OnHeaderRead(Task task, int code) { } void Self::OnBodyRead(Task task, int code) { if (code <= 0) { - LOG(INFO) << "Failed to read body for entry " << task.key << " in " - << name(); + VLOG(1) << "Failed to read body for entry " << task.key << " in " << name(); Miss(task); return; } diff --git a/component/chromium_cbor_adapter.cc b/component/chromium_cbor_adapter.cc index d7d43b81..401fc17c 100644 --- a/component/chromium_cbor_adapter.cc +++ b/component/chromium_cbor_adapter.cc @@ -1,9 +1,21 @@ #include "chromium_cbor_adapter.h" #include +#include using Self = ipfs::ChromiumCborAdapter; +auto Self::Parse(ipfs::ByteView bytes) -> std::unique_ptr { + cbor::Reader::Config cfg; + cfg.parse_tags = true; + auto parsed = cbor::Reader::Read(as_octets(bytes), cfg); + if (parsed.has_value()) { + return std::make_unique(std::move(parsed.value())); + } + LOG(ERROR) << "Failed to parse CBOR."; + return {}; +} + bool Self::is_map() const { return cbor_.is_map(); } @@ -83,6 +95,7 @@ void Self::iterate_array(ArrayElementCallback cb) const { } } +Self::ChromiumCborAdapter() : cbor_{cbor::Value::SimpleValue::UNDEFINED} {} Self::ChromiumCborAdapter(cbor::Value const& v) : cbor_{v.Clone()} {} Self::ChromiumCborAdapter(cbor::Value&& v) : cbor_{std::move(v)} {} Self::ChromiumCborAdapter(ChromiumCborAdapter const& rhs) diff --git a/component/chromium_cbor_adapter.h b/component/chromium_cbor_adapter.h index 65c2d746..08493b96 100644 --- a/component/chromium_cbor_adapter.h +++ b/component/chromium_cbor_adapter.h @@ -3,10 +3,11 @@ #include -#include +#include +#include namespace ipfs { -class ChromiumCborAdapter final : public DagCborValue { +class ChromiumCborAdapter final : public DagCborValue, public ctx::CborParser { cbor::Value cbor_; std::unique_ptr at(std::string_view) const override; @@ -23,10 +24,13 @@ class ChromiumCborAdapter final : public DagCborValue { void iterate_array(ArrayElementCallback) const override; public: + explicit ChromiumCborAdapter(); ChromiumCborAdapter(cbor::Value&&); ChromiumCborAdapter(cbor::Value const&); ChromiumCborAdapter(ChromiumCborAdapter const& rhs); ~ChromiumCborAdapter() noexcept override; + + std::unique_ptr Parse(ByteView) override; }; } // namespace ipfs diff --git a/component/chromium_ipfs_context.cc b/component/chromium_ipfs_context.cc index c2cd2b51..7966ec5a 100644 --- a/component/chromium_ipfs_context.cc +++ b/component/chromium_ipfs_context.cc @@ -6,8 +6,8 @@ #include "chromium_http.h" #include "chromium_json_adapter.h" #include "inter_request_state.h" +#include "json_parser_adapter.h" -#include #include #include @@ -30,9 +30,10 @@ void Self::SetupHttp(network::mojom::URLLoaderFactory& lf) { http_api_ = std::make_unique(lf); } -std::string Self::MimeType(std::string extension, +namespace { +std::string DeduceMimeType(std::string extension, std::string_view content, - std::string const& url) const { + std::string const& url) { std::string result; auto fp_ext = base::FilePath::FromUTF8Unsafe(extension).value(); if (extension.empty()) { @@ -49,27 +50,22 @@ std::string Self::MimeType(std::string extension, } return result; } -std::string Self::UnescapeUrlComponent(std::string_view comp) const { +std::string Unescape(std::string_view comp) { using Rule = base::UnescapeRule; auto rules = Rule::PATH_SEPARATORS | Rule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | Rule::SPACES; auto result = base::UnescapeURLComponent({comp.data(), comp.size()}, rules); return result; } -auto Self::ParseCbor(ipfs::ContextApi::ByteView bytes) const - -> std::unique_ptr { - cbor::Reader::Config cfg; - cfg.parse_tags = true; - auto parsed = cbor::Reader::Read(as_octets(bytes), cfg); - if (parsed.has_value()) { - return std::make_unique(std::move(parsed.value())); - } - LOG(ERROR) << "Failed to parse CBOR."; - return {}; -} +} // namespace + Self::ChromiumIpfsContext(InterRequestState& state, PrefService* prefs) { with(std::make_unique(prefs)); with(std::make_unique(state)); + with(&DeduceMimeType); + with(&Unescape); + with(std::make_unique()); + with(std::make_unique()); using K = crypto::SigningKeyType; with(K::RSA, std::make_unique(EVP_PKEY_RSA)); diff --git a/component/chromium_ipfs_context.h b/component/chromium_ipfs_context.h index 24751beb..e35e446f 100644 --- a/component/chromium_ipfs_context.h +++ b/component/chromium_ipfs_context.h @@ -29,12 +29,6 @@ class IpfsRequest; class NetworkRequestor; class ChromiumIpfsContext final : public ContextApi { - std::string MimeType(std::string extension, - std::string_view content, - std::string const& url) const override; - std::string UnescapeUrlComponent(std::string_view) const override; - - std::unique_ptr ParseCbor(ByteView) const override; public: ChromiumIpfsContext(InterRequestState&, PrefService* prefs); diff --git a/component/ipfs_url_loader.cc b/component/ipfs_url_loader.cc index 7edc3aa2..228a1500 100644 --- a/component/ipfs_url_loader.cc +++ b/component/ipfs_url_loader.cc @@ -180,7 +180,7 @@ void ipfs::IpfsUrlLoader::BlocksComplete(std::string mime_type) { void ipfs::IpfsUrlLoader::DoesNotExist(std::string_view cid, std::string_view path) { - LOG(WARNING) << "Immutable data 404 for " << cid << '/' << path; + LOG(INFO) << "Immutable data 404 for " << cid << '/' << path; complete_ = true; client_->OnComplete( network::URLLoaderCompletionStatus{net::ERR_FILE_NOT_FOUND}); diff --git a/library/include/ipfs_client/context_api.h b/library/include/ipfs_client/context_api.h index 48778eb0..48048b1a 100644 --- a/library/include/ipfs_client/context_api.h +++ b/library/include/ipfs_client/context_api.h @@ -40,6 +40,9 @@ class DagCborValue; class ContextApi : public std::enable_shared_from_this { public: using SigningKeyType = ::ipfs::crypto::SigningKeyType; + using MimeTypeDeduction = std::function< + std::string(std::string, std::string_view, std::string const&)>; + using UrlUnescaping = std::function; ContextApi(); virtual ~ContextApi() noexcept {} @@ -55,6 +58,8 @@ class ContextApi : public std::enable_shared_from_this { ContextApi& with(std::unique_ptr); ContextApi& with(std::unique_ptr); ContextApi& with(SigningKeyType, std::unique_ptr); + ContextApi& with(MimeTypeDeduction); + ContextApi& with(UrlUnescaping); /*! * \brief Determine a mime type for a given file. @@ -62,9 +67,9 @@ class ContextApi : public std::enable_shared_from_this { * \param content - The content of the resource or a large prefix thereof * \param url - A URL it was fetched from (of any sort, ipfs:// is fine) */ - virtual std::string MimeType(std::string extension, - std::string_view content, - std::string const& url) const = 0; + std::string MimeType(std::string extension, + std::string_view content, + std::string const& url); /*! * \brief Remove URL escaping, e.g. %20 @@ -72,9 +77,7 @@ class ContextApi : public std::enable_shared_from_this { * not including / * \return The unescaped string */ - virtual std::string UnescapeUrlComponent(std::string_view url_comp) const = 0; - - virtual std::unique_ptr ParseCbor(ByteView) const = 0; + std::string UnescapeUrlComponent(std::string_view url_comp); using ByteView = ::ipfs::ByteView; bool VerifyKeySignature(SigningKeyType, @@ -93,6 +96,8 @@ class ContextApi : public std::enable_shared_from_this { std::unique_ptr gateway_config_; std::unique_ptr json_parser_; std::unique_ptr cbor_parser_; + MimeTypeDeduction deduce_mime_type_; + UrlUnescaping unescape_; }; } // namespace ipfs diff --git a/library/include/ipfs_client/ipld/resolution_state.h b/library/include/ipfs_client/ipld/resolution_state.h index 56e4e529..66f1f0cb 100644 --- a/library/include/ipfs_client/ipld/resolution_state.h +++ b/library/include/ipfs_client/ipld/resolution_state.h @@ -27,7 +27,7 @@ class ResolutionState { SlashDelimited MyPath() const; SlashDelimited PathToResolve() const; bool IsFinalComponent() const; - std::string NextComponent(ContextApi const*) const; + std::string NextComponent(ContextApi*) const; NodePtr GetBlock(std::string const& block_key) const; ResolutionState WithPath(std::string_view) const; diff --git a/library/include/ipfs_client/test_context.h b/library/include/ipfs_client/test_context.h index 63970f36..f9ad7352 100644 --- a/library/include/ipfs_client/test_context.h +++ b/library/include/ipfs_client/test_context.h @@ -45,51 +45,6 @@ namespace ipfs { // LCOV_EXCL_START class TestContext final : public ContextApi { - std::string MimeType(std::string extension, - std::string_view, - std::string const&) const override { - // TODO implement real mime type detection - return "text/" + extension; - } - std::string UnescapeUrlComponent(std::string_view url_comp) const override { - std::string rv{url_comp}; - auto xval = [](char c) { - if (c <= '9') { - return c - '0'; - } - if (c <= 'Z') { - return c - 'A'; - } - return c - 'a'; - }; - for (auto i = 0UL; i + 1UL < rv.size(); ++i) { - if (rv[i] != '%') { - continue; - } - auto a = rv[i + 1UL]; - if (rv[i + 1UL] == '%') { - rv.erase(i, 1UL); - continue; - } - if (i + 2UL >= rv.size()) { - break; - } - if (!std::isxdigit(a)) { - continue; - } - auto b = rv[i + 2UL]; - if (std::isxdigit(b)) { - rv[i] = xval(a) * 16 + xval(b); - rv.erase(i + 1UL, 2); - } - } - return rv; - } - std::unique_ptr ParseCbor(ByteView bytes) const override { - auto data = nlohmann::json::from_cbor( - bytes, false, true, nlohmann::detail::cbor_tag_handler_t::store); - return std::make_unique(data); - } boost::asio::io_context& io_; public: diff --git a/library/src/ipfs_client/car.cc b/library/src/ipfs_client/car.cc index 0b508698..fefd7c20 100644 --- a/library/src/ipfs_client/car.cc +++ b/library/src/ipfs_client/car.cc @@ -14,11 +14,11 @@ using ByteView = ipfs::ByteView; using VarInt = libp2p::multi::UVarint; namespace { -short ReadHeader(ByteView&, ipfs::ContextApi const&); +short ReadHeader(ByteView&, ipfs::ContextApi&); std::pair GetV1PayloadPos(ByteView); } // namespace -Self::Car(ByteView bytes, ContextApi const& api) { +Self::Car(ByteView bytes, ContextApi& api) { auto after_header = bytes; auto version = ReadHeader(after_header, api); switch (version) { @@ -65,7 +65,7 @@ auto Self::NextBlock() -> std::optional { namespace { // https://ipld.io/specs/transport/car/carv2/ -short ReadHeader(ByteView& bytes, ipfs::ContextApi const& api) { +short ReadHeader(ByteView& bytes, ipfs::ContextApi& api) { auto header_len = VarInt::create(bytes); if (!header_len || header_len->toUInt64() + header_len->size() > bytes.size()) { @@ -73,7 +73,7 @@ short ReadHeader(ByteView& bytes, ipfs::ContextApi const& api) { } bytes = bytes.subspan(header_len->size()); auto header_bytes = bytes.subspan(0UL, header_len->toUInt64()); - auto header = api.ParseCbor(header_bytes); + auto header = api.cbor().Parse(header_bytes); if (!header) { return 0; } diff --git a/library/src/ipfs_client/car.h b/library/src/ipfs_client/car.h index 619ac48e..30ded75c 100644 --- a/library/src/ipfs_client/car.h +++ b/library/src/ipfs_client/car.h @@ -11,7 +11,7 @@ namespace ipfs { class ContextApi; class Car { public: - Car(ByteView, ContextApi const&); + Car(ByteView, ContextApi&); struct Block { Cid cid; ByteView bytes; diff --git a/library/src/ipfs_client/context_api.cc b/library/src/ipfs_client/context_api.cc index 7da52f07..16385cb7 100644 --- a/library/src/ipfs_client/context_api.cc +++ b/library/src/ipfs_client/context_api.cc @@ -8,6 +8,7 @@ #include "ipfs_client/ctx/null_dns_txt_lookup.h" #include "ipfs_client/ctx/null_http_provider.h" #include "ipfs_client/ctx/transitory_gateway_config.h" +#include "ipfs_client/web_util.h" #include "log_macros.h" @@ -47,6 +48,24 @@ bool Self::VerifyKeySignature(SigningKeyType typ, } return it->second->VerifySignature(signature, data, key_bytes); } +std::string Self::MimeType(std::string extension, + std::string_view content, + std::string const& url) { + if (!deduce_mime_type_) { + LOG(WARNING) << "No mime-type deduction algo provided. Will do something " + "trivial/inaccurate."; + deduce_mime_type_ = util::TrivialMimeGuess; + } + return deduce_mime_type_(extension, content, url); +} +std::string Self::UnescapeUrlComponent(std::string_view url_comp) { + if (!unescape_) { + LOG(WARNING) + << "No URL (un)escaping algo provided. Will do something simple/wrong."; + unescape_ = util::RoughlyUnescapeUrlComponent; + } + return unescape_(url_comp); +} auto Self::http() -> ctx::HttpApi& { if (!http_api_) { @@ -114,6 +133,14 @@ Self& Self::with(SigningKeyType t, } return *this; } +Self& Self::with(ipfs::ContextApi::MimeTypeDeduction deduce_mime_type) { + deduce_mime_type_ = deduce_mime_type; + return *this; +} +Self& Self::with(ipfs::ContextApi::UrlUnescaping unescape) { + unescape_ = unescape; + return *this; +} Self& Self::with(std::unique_ptr p) { gateway_config_ = std::move(p); return *this; diff --git a/library/src/ipfs_client/ctx/default_gateways.cc b/library/src/ipfs_client/ctx/default_gateways.cc index 7b74abe5..0ae5d261 100644 --- a/library/src/ipfs_client/ctx/default_gateways.cc +++ b/library/src/ipfs_client/ctx/default_gateways.cc @@ -27,31 +27,30 @@ bool ctx::LoadGatewaysFromEnvironmentVariable(ipfs::ctx::GatewayConfig& cfg) { void ctx::LoadStaticGatewayList(ipfs::ctx::GatewayConfig& cfg) { auto static_list = { - std::pair{"http://localhost:8080/", 8000}, - {"https://jcsl.hopto.org/", 745}, - {"https://ipfs.io/", 741}, - {"https://gateway.ipfs.io/", 626}, + std::pair{"http://localhost:8080/", 9000}, + {"https://jcsl.hopto.org/", 746}, + {"https://ipfs.io/", 743}, + {"https://gateway.ipfs.io/", 629}, {"https://human.mypinata.cloud/", 342}, {"https://dag.w3s.link/", 216}, - {"https://ipfs.runfission.com/", 114}, + {"https://ipfs.runfission.com/", 115}, {"https://delegated-ipfs.dev/", 75}, {"https://permaweb.eu.org/", 66}, {"https://cesginc.com/", 56}, {"https://dweb.link/", 55}, - {"https://http.f02620.devtty.eu/", 46}, - {"https://f010479.twinquasar.io/", 33}, - {"https://ipfs.omnicloudstorage.com:9443/", 26}, + {"https://http.f02620.devtty.eu/", 47}, + {"https://f010479.twinquasar.io/", 34}, + {"https://ipfs.omnicloudstorage.com:9443/", 27}, {"http://f02095132.datasetcreators.com/", 23}, + {"https://ipfs.joaoleitao.org/", 17}, {"https://gateway.pinata.cloud/", 16}, - {"https://ipfs.joaoleitao.org/", 16}, {"https://data.filstorage.io/", 8}, - {"https://nftstorage.link/", 7}, - {"https://ipfs.fleek.co/", 6}, + {"https://nftstorage.link/", 8}, + {"https://ipfs.fleek.co/", 7}, {"https://w3s.link/", 3}, {"https://hardbin.com/", 2}, {"https://jorropo.net/", 1}, - {"https://ipfs.jpu.jp/", 1}, - {"https://ipfs.soul-network.com/", 0}}; + {"https://ipfs.jpu.jp/", 0}}; for (auto [gw, rt] : static_list) { cfg.AddGateway(gw); cfg.SetGatewayRate(gw, rt); diff --git a/library/src/ipfs_client/ipld/dag_node.cc b/library/src/ipfs_client/ipld/dag_node.cc index 148377ae..be17bf9d 100644 --- a/library/src/ipfs_client/ipld/dag_node.cc +++ b/library/src/ipfs_client/ipld/dag_node.cc @@ -56,7 +56,7 @@ auto Node::fromBytes(std::shared_ptr const& api, switch (cid.codec()) { case MultiCodec::DAG_CBOR: { auto p = reinterpret_cast(bytes.data()); - auto cbor = api->ParseCbor({p, bytes.size()}); + auto cbor = api->cbor().Parse({p, bytes.size()}); if (cbor) { result = std::make_shared(std::move(cbor)); } else { diff --git a/library/src/ipfs_client/ipld/resolution_state.cc b/library/src/ipfs_client/ipld/resolution_state.cc index bf8f907b..d2290f67 100644 --- a/library/src/ipfs_client/ipld/resolution_state.cc +++ b/library/src/ipfs_client/ipld/resolution_state.cc @@ -21,7 +21,7 @@ auto Self::PathToResolve() const -> SlashDelimited { auto Self::MyPath() const -> SlashDelimited { return SlashDelimited{resolved_path_components}; } -std::string Self::NextComponent(ContextApi const* api) const { +std::string Self::NextComponent(ContextApi* api) const { auto copy = unresolved_path; if (api) { return api->UnescapeUrlComponent(copy.pop()); diff --git a/library/src/ipfs_client/ipns_record.cc b/library/src/ipfs_client/ipns_record.cc index 19f6b95c..030c1e14 100644 --- a/library/src/ipfs_client/ipns_record.cc +++ b/library/src/ipfs_client/ipns_record.cc @@ -106,8 +106,8 @@ auto ipfs::ValidateIpnsRecord(ipfs::ByteView top_level_bytes, DCHECK_EQ(entry.validitytype(), 0); auto parsed = - api.ParseCbor({reinterpret_cast(entry.data().data()), - entry.data().size()}); + api.cbor().Parse({reinterpret_cast(entry.data().data()), + entry.data().size()}); if (!parsed) { LOG(ERROR) << "CBOR parsing failed."; return {}; diff --git a/library/src/ipfs_client/orchestrator_unittest.cc b/library/src/ipfs_client/orchestrator_unittest.cc index 2a087218..89d3875b 100644 --- a/library/src/ipfs_client/orchestrator_unittest.cc +++ b/library/src/ipfs_client/orchestrator_unittest.cc @@ -187,10 +187,7 @@ TEST_F(OrchestratingRealData, one_html_present) { dorequest(abs_path("/one.html")); EXPECT_EQ(resp_.status_, 200); EXPECT_EQ(resp_.body_, "my one\n"); - EXPECT_EQ(resp_.mime_, - "Mime from extension=html " - "'url'=ipfs://QmYBhLYDwVFvxos9h8CGU2ibaY66QNgv8hpfewxaQrPiZj/" - "one.html content='my one\n'"); + EXPECT_EQ(resp_.mime_, "text/html"); } TEST_F(OrchestratingRealData, hit_404_splat) { @@ -202,10 +199,7 @@ TEST_F(OrchestratingRealData, hit_index_catchall) { dorequest(abs_path("/definitely_not_there")); EXPECT_EQ(resp_.status_, 200); EXPECT_EQ(resp_.body_, "my index\n"); - EXPECT_EQ(resp_.mime_, - "Mime from extension=html " - "'url'=ipfs://QmYBhLYDwVFvxos9h8CGU2ibaY66QNgv8hpfewxaQrPiZj/" - "index.html content='my index...'"); + EXPECT_EQ(resp_.mime_, "text/html"); } TEST_F(OrchestratingRealData, normal_permanent_redirect) { dorequest(abs_path("/redirect-one")); @@ -282,12 +276,7 @@ TEST_F(OrchestratingRealData, dirshard_immediate_hit) { "/ipfs/bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze/" "index.html"); EXPECT_EQ(resp_.status_, 200); - EXPECT_EQ( - resp_.mime_, - "Mime from extension=html " - "'url'=ipfs://" - "bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze/index.html" - " content='\n ...'"); + EXPECT_EQ(resp_.mime_, "text/html"); EXPECT_EQ(resp_.body_, R"(