Coverage Report

Created: 2026-10-28 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl_fuzzer/proto_fuzzer/ipfs_url.cc
Line
Count
Source
1
/*
2
 * Copyright (C) Max Dymond, <cmeister2@gmail.com>, et al.
3
 *
4
 * SPDX-License-Identifier: curl
5
 */
6
7
/// @file
8
/// @brief Implementation of IPFS/IPNS gateway URL construction.
9
10
#include "proto_fuzzer/ipfs_url.h"
11
12
#include <string_view>
13
14
namespace proto_fuzzer {
15
16
namespace {
17
18
// libcurl has no ipfs:// handler: curl's command-line tool rewrites those
19
// schemes onto an HTTP gateway before configuring its easy handle. The runner
20
// mirrors that rewrite against a fixed loopback authority; CONNECT_TO and
21
// OPENSOCKET still confine the resulting HTTP GET to the in-process
22
// MockServer, so no gateway process, DNS lookup, or external port is involved.
23
constexpr char kIpfsGatewayBaseUrl[] = "http://127.0.1.127:8080";
24
25
/// Collapse "." and ".." path segments per RFC 3986 section 5.2.4 without
26
/// decoding percent-encoded bytes (an encoded "%2e" must not become a real
27
/// separator). The result is rooted at the input's own root, so ".." can never
28
/// climb above it — matching how curl's tool_ipfs.c normalizes the user path
29
/// before prepending `/ipfs/<cid>`.
30
926
std::string RemoveDotSegments(std::string_view path) {
31
926
  std::string input(path);
32
926
  std::string output;
33
96.0k
  while (!input.empty()) {
34
95.0k
    if (input.rfind("../", 0) == 0) {
35
0
      input.erase(0, 3);
36
95.0k
    } else if (input.rfind("./", 0) == 0) {
37
0
      input.erase(0, 2);
38
95.0k
    } else if (input.rfind("/./", 0) == 0) {
39
268
      input.replace(0, 3, "/");
40
94.8k
    } else if (input == "/.") {
41
9
      input = "/";
42
94.8k
    } else if (input.rfind("/../", 0) == 0) {
43
1.46k
      input.replace(0, 4, "/");
44
1.46k
      const std::size_t slash = output.rfind('/');
45
1.46k
      output.erase(slash == std::string::npos ? 0 : slash);
46
93.3k
    } else if (input == "/..") {
47
27
      input = "/";
48
27
      const std::size_t slash = output.rfind('/');
49
27
      output.erase(slash == std::string::npos ? 0 : slash);
50
93.3k
    } else if (input == "." || input == "..") {
51
0
      input.clear();
52
93.3k
    } else {
53
93.3k
      const std::size_t segment_start = (input[0] == '/') ? 1 : 0;
54
93.3k
      const std::size_t segment_end = input.find('/', segment_start);
55
93.3k
      if (segment_end == std::string::npos) {
56
810
        output += input;
57
810
        input.clear();
58
92.5k
      } else {
59
92.5k
        output += input.substr(0, segment_end);
60
92.5k
        input.erase(0, segment_end);
61
92.5k
      }
62
93.3k
    }
63
95.0k
  }
64
926
  return output;
65
926
}
66
67
/// Split a pinned CID/name from its mutable suffix, collapse dot segments in
68
/// the suffix's path portion (leaving any query and fragment intact), and
69
/// rejoin. This reproduces tool_ipfs.c, which normalizes the user path before
70
/// prepending `/ipfs/<cid>` so a suffix ending in "/../.." cannot erase the
71
/// gateway namespace.
72
9.27k
std::string NormalizeIpfsHostPath(std::string_view host_path) {
73
9.27k
  const std::size_t suffix_start = host_path.find_first_of("/?#");
74
9.27k
  if (suffix_start == std::string_view::npos) {
75
8.35k
    return std::string(host_path);
76
8.35k
  }
77
926
  const std::string_view identifier = host_path.substr(0, suffix_start);
78
926
  const std::string_view suffix = host_path.substr(suffix_start);
79
926
  const std::size_t path_end = suffix.find_first_of("?#");
80
926
  const std::string_view path = (path_end == std::string_view::npos) ? suffix : suffix.substr(0, path_end);
81
926
  const std::string_view query_fragment =
82
926
      (path_end == std::string_view::npos) ? std::string_view() : suffix.substr(path_end);
83
926
  std::string result(identifier);
84
926
  result += RemoveDotSegments(path);
85
926
  result += query_fragment;
86
926
  return result;
87
9.27k
}
88
89
}  // namespace
90
91
/// Build the IPFS/IPNS gateway URL for the given scheme and pinned host_path.
92
/// The mutable suffix's dot segments are normalized before the `/ipfs/<cid>` or
93
/// `/ipns/<name>` namespace is prepended, matching curl's tool_ipfs.c ordering
94
/// so a suffix ending in "/../.." cannot erase the namespace.
95
/// @param scheme SCHEME_IPFS or SCHEME_IPNS.
96
/// @param host_path Canonical identifier (CID/name) followed by the mutable
97
///                  path, query, and fragment suffix.
98
/// @return The HTTP gateway URL curl will request.
99
9.27k
std::string BuildIpfsGatewayUrl(curl::fuzzer::proto::Scheme scheme, const std::string& host_path) {
100
9.27k
  const char* prefix = scheme == curl::fuzzer::proto::SCHEME_IPNS ? "ipns" : "ipfs";
101
9.27k
  return std::string(kIpfsGatewayBaseUrl) + "/" + prefix + "/" + NormalizeIpfsHostPath(host_path);
102
9.27k
}
103
104
}  // namespace proto_fuzzer