/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 |