Coverage Report

Created: 2026-09-14 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/botan/build/include/public/botan/uri.h
Line
Count
Source
1
/*
2
* (C) 2026 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6
7
#ifndef BOTAN_URI_H_
8
#define BOTAN_URI_H_
9
10
#include <botan/dns_name.h>
11
#include <botan/ipv4_address.h>
12
#include <botan/ipv6_address.h>
13
#include <botan/types.h>
14
#include <functional>
15
#include <optional>
16
#include <string>
17
#include <string_view>
18
#include <variant>
19
20
namespace Botan {
21
22
/**
23
* URI (RFC 3986 subset)
24
*/
25
class BOTAN_PUBLIC_API(3, 13) URI final {
26
   public:
27
      /**
28
      * The optional authority component of a URI: a validated DNS name, IPv4
29
      * literal, or IPv6 literal, with an optional port.
30
      */
31
      class BOTAN_PUBLIC_API(3, 13) Authority final {
32
         public:
33
            /**
34
            * A validated DNS name, or a literal IPv4 or IPv6 address.
35
            */
36
            using Host = std::variant<DNSName, IPv4Address, IPv6Address>;
37
38
            /**
39
            * Tag for the alternative held by `Host`.
40
            */
41
            enum class HostKind : uint8_t {
42
               DNS = 0,
43
               IPv4 = 1,
44
               IPv6 = 2,
45
            };
46
47
            /**
48
            * Parse a bare authority "host[:port]" or "[ipv6][:port]".
49
            * Returns nullopt for any parse failure.
50
            */
51
            static std::optional<Authority> from_string(std::string_view raw);
52
53
            /**
54
            * Parsed host: a DNS name, an IPv4 literal, or an IPv6 literal.
55
            */
56
0
            const Host& host() const { return m_host; }
57
58
            /**
59
            * Which alternative of `host()` is held.
60
            */
61
            HostKind host_kind() const;
62
63
            /**
64
            * The host as a string: DNS names and dotted-IPv4 literals are
65
            * returned verbatim; IPv6 literals are returned without surrounding
66
            * brackets. Lowercased for DNS / IPv4; the IPv6 form is whatever
67
            * `IPv6Address::to_string` produces.
68
            */
69
            std::string host_to_string() const;
70
71
            /**
72
            * Port if present; nullopt otherwise.
73
            */
74
0
            std::optional<uint16_t> port() const { return m_port; }
75
76
            /**
77
            * The original input that was parsed
78
            */
79
0
            const std::string& original_input() const { return m_raw; }
80
81
            /**
82
            * The userinfo component, preserved verbatim (no case normalization
83
            * or pct-decoding) and compared verbatim for identity. nullopt if no
84
            * "@" was present; present-but-empty (e.g. "https://@example.com/")
85
            * is distinguished from absent.
86
            */
87
0
            const std::optional<std::string>& userinfo() const { return m_userinfo; }
88
89
            /**
90
            * Order two authorities
91
            * @param other the authority to compare against
92
            * @return the ordering of this authority relative to other
93
            */
94
            std::strong_ordering operator<=>(const Authority& other) const;
95
96
            /**
97
            * Compare two authorities
98
            * @param other the authority to compare against
99
            * @return true if the two authorities are equal
100
            */
101
            bool operator==(const Authority& other) const;
102
103
         private:
104
            Authority(std::string raw, std::optional<std::string> userinfo, Host host, std::optional<uint16_t> port) :
105
6.03k
                  m_raw(std::move(raw)), m_userinfo(std::move(userinfo)), m_host(std::move(host)), m_port(port) {}
106
107
            std::string m_raw;
108
            std::optional<std::string> m_userinfo;
109
            Host m_host;
110
            std::optional<uint16_t> m_port;
111
      };
112
113
      /// A validated DNS name, or a literal IPv4 or IPv6 address
114
      using Host = Authority::Host;
115
116
      /// Tag for the alternative held by `Host`
117
      using HostKind = Authority::HostKind;
118
119
      /**
120
      * Parse a URI, return nullopt on failure
121
      */
122
      static std::optional<URI> from_string(std::string_view raw);
123
124
      /**
125
      * Return the scheme, lowercase normalized
126
      */
127
0
      const std::string& scheme() const { return m_scheme; }
128
129
      /**
130
      * Return the parsed URI authority, if this URI has one.
131
      */
132
0
      const std::optional<Authority>& authority() const { return m_authority; }
133
134
      /**
135
      * Return the raw authority component if this URI included one, including
136
      * the empty string for URIs such as "ldap:///CN=...".
137
      */
138
      std::optional<std::string_view> raw_authority() const;
139
140
      /**
141
      * Return the parsed host, if this URI has an authority.
142
      * TODO(C++26) This can return std::optional<const Host&>
143
      */
144
0
      std::optional<std::reference_wrapper<const Host>> host() const {
145
0
         return m_authority.has_value() ? std::optional<std::reference_wrapper<const Host>>(m_authority->host())
146
0
                                        : std::nullopt;
147
0
      }
148
149
      /**
150
      * The path component, preserved verbatim. Begins with "/" when present;
151
      * empty if the parsed URI had no path (e.g. "http://example.com" or
152
      * "http://example.com?q").
153
      */
154
0
      const std::string& path() const { return m_path; }
155
156
      /**
157
      * The query component, without the leading "?". Nullopt if no "?" was
158
      * present; present-but-empty distinguishes "http://h/p?" from
159
      * "http://h/p".
160
      */
161
0
      const std::optional<std::string>& query() const { return m_query; }
162
163
      /**
164
      * The fragment component, without the leading "#". Nullopt if no "#"
165
      * was present; present-but-empty distinguishes "http://h/p#" from
166
      * "http://h/p".
167
      */
168
0
      const std::optional<std::string>& fragment() const { return m_fragment; }
169
170
      /**
171
      * The original input that was parsed.
172
      */
173
0
      const std::string& original_input() const { return m_raw; }
174
175
      /**
176
      * Order two URIs
177
      * @param other the URI to compare against
178
      * @return the ordering of this URI relative to other
179
      */
180
      std::strong_ordering operator<=>(const URI& other) const;
181
182
      /**
183
      * Compare two URIs
184
      * @param other the URI to compare against
185
      * @return true if the two URIs are equal
186
      */
187
      bool operator==(const URI& other) const;
188
189
      /**
190
      * Return a list of URIs (possibly empty) which match the specified scheme
191
      * and which contain a non-empty authority
192
      */
193
      static std::vector<URI> filter_scheme(std::string_view scheme, std::span<const URI> uris);
194
195
   private:
196
      URI(std::string raw,
197
          std::string scheme,
198
          std::optional<Authority> authority,
199
          std::string path,
200
          std::optional<std::string> query,
201
          std::optional<std::string> fragment) :
202
8.91k
            m_raw(std::move(raw)),
203
8.91k
            m_scheme(std::move(scheme)),
204
8.91k
            m_authority(std::move(authority)),
205
8.91k
            m_path(std::move(path)),
206
8.91k
            m_query(std::move(query)),
207
8.91k
            m_fragment(std::move(fragment)) {}
208
209
      std::string m_raw;
210
      std::string m_scheme;
211
      std::optional<Authority> m_authority;
212
      std::string m_path;
213
      std::optional<std::string> m_query;
214
      std::optional<std::string> m_fragment;
215
};
216
217
}  // namespace Botan
218
219
#endif