|
| 1 | +// SPDX-FileCopyrightText: 2004-2026 Michael Medin |
| 2 | +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-only |
| 3 | + |
| 4 | +#include <gtest/gtest.h> |
| 5 | + |
| 6 | +#include <net/net.hpp> |
| 7 | + |
| 8 | +TEST(net_url, parse_splits_protocol_host_port_and_path) { |
| 9 | + const net::url u = net::parse("http://example.com:8080/dir/file.ini"); |
| 10 | + EXPECT_EQ(u.protocol, "http"); |
| 11 | + EXPECT_EQ(u.host, "example.com"); |
| 12 | + EXPECT_EQ(u.port, 8080u); |
| 13 | + EXPECT_EQ(u.path, "/dir/file.ini"); |
| 14 | + EXPECT_TRUE(u.query.empty()); |
| 15 | +} |
| 16 | + |
| 17 | +TEST(net_url, parse_extracts_the_query_string) { |
| 18 | + const net::url u = net::parse("http://nsclient.mydom.local/nsclient/nsclient.php?RootFolder=myhost/&Filename=nsclient.ini"); |
| 19 | + EXPECT_EQ(u.protocol, "http"); |
| 20 | + EXPECT_EQ(u.host, "nsclient.mydom.local"); |
| 21 | + // The path stops at the '?' ... |
| 22 | + EXPECT_EQ(u.path, "/nsclient/nsclient.php"); |
| 23 | + // ... and the parameters land in query, '&' and '/' included. |
| 24 | + EXPECT_EQ(u.query, "RootFolder=myhost/&Filename=nsclient.ini"); |
| 25 | +} |
| 26 | + |
| 27 | +TEST(net_url, get_request_path_reassembles_path_and_query) { |
| 28 | + // What a downloader has to put on the request line (issue #460). |
| 29 | + const net::url u = net::parse("https://host/cfg.php?a=1&b=2"); |
| 30 | + EXPECT_EQ(u.get_request_path(), "/cfg.php?a=1&b=2"); |
| 31 | +} |
| 32 | + |
| 33 | +TEST(net_url, get_request_path_without_a_query_is_just_the_path) { |
| 34 | + const net::url u = net::parse("https://host/settings.ini"); |
| 35 | + EXPECT_EQ(u.get_request_path(), "/settings.ini"); |
| 36 | + // No trailing '?' when there is nothing to pass. |
| 37 | + EXPECT_EQ(u.get_request_path().find('?'), std::string::npos); |
| 38 | +} |
| 39 | + |
| 40 | +TEST(net_url, get_request_path_keeps_an_empty_query_marker_out) { |
| 41 | + // "?" with nothing after it parses to an empty query and must not be |
| 42 | + // re-emitted, otherwise every plain url would grow a stray '?'. |
| 43 | + const net::url u = net::parse("http://host/f.ini?"); |
| 44 | + EXPECT_EQ(u.path, "/f.ini"); |
| 45 | + EXPECT_TRUE(u.query.empty()); |
| 46 | + EXPECT_EQ(u.get_request_path(), "/f.ini"); |
| 47 | +} |
| 48 | + |
| 49 | +TEST(net_url, to_string_round_trips_a_url_with_parameters) { |
| 50 | + const std::string raw = "http://example.com:8080/cfg.php?host=a&mode=b"; |
| 51 | + EXPECT_EQ(net::parse(raw).to_string(), raw); |
| 52 | +} |
| 53 | + |
| 54 | +TEST(net_url, to_string_includes_port_only_when_set) { |
| 55 | + EXPECT_EQ(net::parse("http://example.com/x.ini").to_string(), "http://example.com/x.ini"); |
| 56 | + EXPECT_EQ(net::parse("http://example.com:81/x.ini").to_string(), "http://example.com:81/x.ini"); |
| 57 | +} |
| 58 | + |
| 59 | +TEST(net_url, ini_paths_keep_their_windows_drive_letter) { |
| 60 | + // "ini://C:/foo" must not read "C" as a host and ":/foo" as a port; the |
| 61 | + // parser skips port handling for the ini and registry protocols. |
| 62 | + const net::url u = net::parse("ini://${shared-path}/nsclient.ini"); |
| 63 | + EXPECT_EQ(u.protocol, "ini"); |
| 64 | + EXPECT_TRUE(u.query.empty()); |
| 65 | + EXPECT_EQ(u.get_request_path(), u.path); |
| 66 | +} |
| 67 | + |
| 68 | +// --- request-line safety ---------------------------------------------------- |
| 69 | + |
| 70 | +TEST(net_url, encode_query_leaves_legal_characters_alone) { |
| 71 | + // Everything RFC 3986 permits in a query has to survive verbatim, or the |
| 72 | + // parameters stop meaning what the operator wrote. |
| 73 | + const std::string legal = "RootFolder=myhost/&Filename=nsclient.ini"; |
| 74 | + EXPECT_EQ(net::encode_query(legal), legal); |
| 75 | + EXPECT_EQ(net::encode_query("a=1&b=2;c=3,d=4+5:6@7?8*9!$'()~-._"), "a=1&b=2;c=3,d=4+5:6@7?8*9!$'()~-._"); |
| 76 | +} |
| 77 | + |
| 78 | +TEST(net_url, encode_query_escapes_a_space) { |
| 79 | + EXPECT_EQ(net::encode_query("Folder=my host"), "Folder=my%20host"); |
| 80 | +} |
| 81 | + |
| 82 | +TEST(net_url, encode_query_escapes_crlf) { |
| 83 | + // The interesting one: unescaped, this splits the request line in two. |
| 84 | + EXPECT_EQ(net::encode_query("a=1\r\nX-Evil: yes"), "a=1%0D%0AX-Evil:%20yes"); |
| 85 | +} |
| 86 | + |
| 87 | +TEST(net_url, encode_query_does_not_double_encode) { |
| 88 | + // A query written already-encoded must not turn "%20" into "%2520". |
| 89 | + EXPECT_EQ(net::encode_query("Folder=my%20host"), "Folder=my%20host"); |
| 90 | + EXPECT_EQ(net::encode_query("a=%2F%2f"), "a=%2F%2f"); |
| 91 | +} |
| 92 | + |
| 93 | +TEST(net_url, encode_query_escapes_a_stray_percent) { |
| 94 | + // A '%' that introduces no valid pair is not an escape. |
| 95 | + EXPECT_EQ(net::encode_query("a=100%"), "a=100%25"); |
| 96 | + EXPECT_EQ(net::encode_query("a=%zz"), "a=%25zz"); |
| 97 | + EXPECT_EQ(net::encode_query("a=%2"), "a=%252"); |
| 98 | +} |
| 99 | + |
| 100 | +TEST(net_url, encode_query_escapes_high_bytes_and_controls) { |
| 101 | + EXPECT_EQ(net::encode_query(std::string("a=\x01")), "a=%01"); |
| 102 | + EXPECT_EQ(net::encode_query(std::string("a=\xc3\xa5")), "a=%C3%A5"); |
| 103 | +} |
| 104 | + |
| 105 | +TEST(net_url, get_request_path_encodes_the_query) { |
| 106 | + const net::url u = net::parse("http://host/cfg.php?Folder=my host"); |
| 107 | + EXPECT_EQ(u.get_request_path(), "/cfg.php?Folder=my%20host"); |
| 108 | +} |
| 109 | + |
| 110 | +// --- log-safe rendering (keeps parameters out of the log) ------------------- |
| 111 | + |
| 112 | +TEST(net_url, to_log_safe_string_drops_the_query) { |
| 113 | + const net::url u = net::parse("https://cfgsrv:8443/nsclient.php?token=s3cret&host=a"); |
| 114 | + EXPECT_EQ(u.to_log_safe_string(), "https://cfgsrv:8443/nsclient.php"); |
| 115 | + EXPECT_EQ(u.to_log_safe_string().find("s3cret"), std::string::npos); |
| 116 | + // to_string() is still the faithful rendering. |
| 117 | + EXPECT_NE(u.to_string().find("token=s3cret"), std::string::npos); |
| 118 | +} |
| 119 | + |
| 120 | +TEST(net_url, to_log_safe_string_equals_to_string_without_a_query) { |
| 121 | + const net::url u = net::parse("http://cfgsrv/settings.ini"); |
| 122 | + EXPECT_EQ(u.to_log_safe_string(), u.to_string()); |
| 123 | +} |
| 124 | + |
| 125 | +TEST(net_url, get_baseurl_and_get_path_split_the_url) { |
| 126 | + const net::url u = net::parse("https://cfgsrv:8443/dir/nsclient.php?token=s3cret"); |
| 127 | + EXPECT_EQ(u.get_baseurl(), "https://cfgsrv:8443"); |
| 128 | + EXPECT_EQ(u.get_path(), "/dir/nsclient.php"); |
| 129 | + EXPECT_EQ(u.get_baseurl() + u.get_path(), u.to_log_safe_string()); |
| 130 | +} |
| 131 | + |
| 132 | +TEST(net_url, get_baseurl_omits_an_unset_port) { |
| 133 | + EXPECT_EQ(net::parse("http://cfgsrv/x.ini").get_baseurl(), "http://cfgsrv"); |
| 134 | +} |
| 135 | + |
| 136 | +TEST(net_url, apply_and_import_carry_the_query) { |
| 137 | + net::url base = net::parse("http://host/a.ini"); |
| 138 | + const net::url with_query = net::parse("http://host/b.ini?k=v"); |
| 139 | + |
| 140 | + net::url applied = base; |
| 141 | + applied.apply(with_query); |
| 142 | + EXPECT_EQ(applied.get_request_path(), "/b.ini?k=v"); |
| 143 | + |
| 144 | + net::url imported = net::parse("http://host/"); |
| 145 | + imported.path.clear(); |
| 146 | + imported.import(with_query); |
| 147 | + EXPECT_EQ(imported.get_request_path(), "/b.ini?k=v"); |
| 148 | +} |
0 commit comments