1717struct test_case {
1818 std::string input;
1919 std::string description;
20- bool should_parse;
2120};
2221
2322int main () {
2423 // Test cases covering a wide range of URL patterns to stress-test with ASan
2524 const std::vector<test_case> test_cases = {
2625 // Basic URLs
27- {" http://example.com" , " Simple HTTP URL" , true },
28- {" https://example.com" , " Simple HTTPS URL" , true },
29- {" ftp://ftp.example.com" , " FTP URL" , true },
30- {" file:///path/to/file" , " File URL" , true },
26+ {" http://example.com" , " Simple HTTP URL" },
27+ {" https://example.com" , " Simple HTTPS URL" },
28+ {" ftp://ftp.example.com" , " FTP URL" },
29+ {" file:///path/to/file" , " File URL" },
3130
3231 // URLs with ports
33- {" http://example.com:8080" , " HTTP with port" , true },
34- {" https://example.com:443" , " HTTPS with default port" , true },
35- {" http://example.com:0" , " Port 0 (edge case)" , true },
36- {" http://example.com:65535" , " Maximum valid port" , true },
37- {" http://example.com:65536" , " Port overflow" , true },
38- {" http://example.com:99999" , " Port out of range" , true },
32+ {" http://example.com:8080" , " HTTP with port" },
33+ {" https://example.com:443" , " HTTPS with default port" },
34+ {" http://example.com:0" , " Port 0 (edge case)" },
35+ {" http://example.com:65535" , " Maximum valid port" },
36+ {" http://example.com:65536" , " Port overflow" },
37+ {" http://example.com:99999" , " Port out of range" },
3938
4039 // URLs with authentication
41- {" http://user:pass@example.com" , " URL with credentials" , true },
42- {" http://user@example.com" , " URL with username only" , true },
43- {" http://:pass@example.com" , " URL with password only" , true },
44- {" http://user%20name:pass%20word@example.com" , " Encoded credentials" , true },
40+ {" http://user:pass@example.com" , " URL with credentials" },
41+ {" http://user@example.com" , " URL with username only" },
42+ {" http://:pass@example.com" , " URL with password only" },
43+ {" http://user%20name:pass%20word@example.com" , " Encoded credentials" },
4544
4645 // IPv4 addresses
47- {" http://192.168.1.1" , " IPv4 address" , true },
48- {" http://127.0.0.1:8080" , " Localhost with port" , true },
49- {" http://255.255.255.255" , " Max IPv4 address" , true },
50- {" http://256.1.1.1" , " Invalid IPv4 (overflow)" , true },
51- {" http://0.0.0.0" , " Zero IPv4 address" , true },
46+ {" http://192.168.1.1" , " IPv4 address" },
47+ {" http://127.0.0.1:8080" , " Localhost with port" },
48+ {" http://255.255.255.255" , " Max IPv4 address" },
49+ {" http://256.1.1.1" , " Invalid IPv4 (overflow)" },
50+ {" http://0.0.0.0" , " Zero IPv4 address" },
5251
5352 // IPv6 addresses
54- {" http://[::1]" , " IPv6 loopback" , true },
55- {" http://[2001:db8::1]" , " IPv6 address" , true },
56- {" http://[::ffff:192.0.2.1]" , " IPv4-mapped IPv6" , true },
57- {" http://[2001:db8::1]:8080" , " IPv6 with port" , true },
58- {" http://[::1]:65536" , " IPv6 with invalid port" , true },
53+ {" http://[::1]" , " IPv6 loopback" },
54+ {" http://[2001:db8::1]" , " IPv6 address" },
55+ {" http://[::ffff:192.0.2.1]" , " IPv4-mapped IPv6" },
56+ {" http://[2001:db8::1]:8080" , " IPv6 with port" },
57+ {" http://[::1]:65536" , " IPv6 with invalid port" },
5958
6059 // Path components
61- {" http://example.com/path/to/resource" , " URL with path" , true },
62- {" http://example.com/path/../other" , " URL with dot segments" , true },
63- {" http://example.com/./path" , " URL with single dot" , true },
64- {" http://example.com/../path" , " URL starting with .." , true },
65- {" http://example.com//double//slash" , " Double slashes in path" , true },
60+ {" http://example.com/path/to/resource" , " URL with path" },
61+ {" http://example.com/path/../other" , " URL with dot segments" },
62+ {" http://example.com/./path" , " URL with single dot" },
63+ {" http://example.com/../path" , " URL starting with .." },
64+ {" http://example.com//double//slash" , " Double slashes in path" },
6665
6766 // Query strings
68- {" http://example.com?key=value" , " URL with query" , true },
69- {" http://example.com?key1=value1&key2=value2" , " Multiple query params" , true },
70- {" http://example.com?key=" , " Empty query value" , true },
71- {" http://example.com?=value" , " Empty query key" , true },
72- {" http://example.com?" , " Empty query string" , true },
73- {" http://example.com?key=value%20with%20spaces" , " Encoded query" , true },
67+ {" http://example.com?key=value" , " URL with query" },
68+ {" http://example.com?key1=value1&key2=value2" , " Multiple query params" },
69+ {" http://example.com?key=" , " Empty query value" },
70+ {" http://example.com?=value" , " Empty query key" },
71+ {" http://example.com?" , " Empty query string" },
72+ {" http://example.com?key=value%20with%20spaces" , " Encoded query" },
7473
7574 // Fragments
76- {" http://example.com#fragment" , " URL with fragment" , true },
77- {" http://example.com#" , " Empty fragment" , true },
78- {" http://example.com#fragment%20with%20spaces" , " Encoded fragment" , true },
79- {" http://example.com?query=1#fragment" , " Query and fragment" , true },
75+ {" http://example.com#fragment" , " URL with fragment" },
76+ {" http://example.com#" , " Empty fragment" },
77+ {" http://example.com#fragment%20with%20spaces" , " Encoded fragment" },
78+ {" http://example.com?query=1#fragment" , " Query and fragment" },
8079
8180 // Percent encoding edge cases (potential for buffer issues)
82- {" http://example.com/%20" , " Encoded space" , true },
83- {" http://example.com/%00" , " Null byte encoded" , true },
84- {" http://example.com/%" , " Incomplete encoding" , true },
85- {" http://example.com/%2" , " Incomplete encoding 2" , true },
86- {" http://example.com/%GG" , " Invalid hex encoding" , true },
87- {" http://example.com/%C3%A9" , " UTF-8 encoded character" , true },
81+ {" http://example.com/%20" , " Encoded space" },
82+ {" http://example.com/%00" , " Null byte encoded" },
83+ {" http://example.com/%" , " Incomplete encoding" },
84+ {" http://example.com/%2" , " Incomplete encoding 2" },
85+ {" http://example.com/%GG" , " Invalid hex encoding" },
86+ {" http://example.com/%C3%A9" , " UTF-8 encoded character" },
8887
8988 // Unicode and internationalized domains (potential encoding issues)
90- {" http://\xE2\x98\x83 .example.com" , " Snowman in domain" , true },
91- {" http://\xF0\x9F\x92\xA9 .example.com" , " Emoji in domain" , true },
92- {" http://münchen.de" , " German umlaut domain" , true },
93- {" http://\xE4\xB8\xAD\xE5\x9B\xBD .cn" , " Chinese domain" , true },
94- {" http://example.com/\xF0\x9F\x92\xA9 " , " Emoji in path" , true },
89+ {" http://\xE2\x98\x83 .example.com" , " Snowman in domain" },
90+ {" http://\xF0\x9F\x92\xA9 .example.com" , " Emoji in domain" },
91+ {" http://münchen.de" , " German umlaut domain" },
92+ {" http://\xE4\xB8\xAD\xE5\x9B\xBD .cn" , " Chinese domain" },
93+ {" http://example.com/\xF0\x9F\x92\xA9 " , " Emoji in path" },
9594
9695 // Special schemes
97- {" data:text/plain,Hello" , " Data URL" , true },
98- {" mailto:user@example.com" , " Mailto URL" , true },
99- {" tel:+1-234-567-8900" , " Tel URL" , true },
100- {" javascript:alert('xss')" , " JavaScript URL" , true },
101- {" about:blank" , " About URL" , true },
96+ {" data:text/plain,Hello" , " Data URL" },
97+ {" mailto:user@example.com" , " Mailto URL" },
98+ {" tel:+1-234-567-8900" , " Tel URL" },
99+ {" javascript:alert('xss')" , " JavaScript URL" },
100+ {" about:blank" , " About URL" },
102101
103102 // Edge cases and malformed URLs (boundary conditions)
104- {" http://" , " No host" , true },
105- {" http:///path" , " Empty host" , true },
106- {" //example.com" , " Protocol-relative URL" , true },
107- {" /path/to/resource" , " Path-only URL" , true },
108- {" http://example.com:abc" , " Non-numeric port" , true },
109- {" http://exam ple.com" , " Space in host" , true },
110- {" http://example..com" , " Double dot in domain" , true },
111- {" http://.example.com" , " Leading dot in domain" , true },
112- {" http://example.com." , " Trailing dot in domain" , true },
103+ {" http://" , " No host" },
104+ {" http:///path" , " Empty host" },
105+ {" //example.com" , " Protocol-relative URL" },
106+ {" /path/to/resource" , " Path-only URL" },
107+ {" http://example.com:abc" , " Non-numeric port" },
108+ {" http://exam ple.com" , " Space in host" },
109+ {" http://example..com" , " Double dot in domain" },
110+ {" http://.example.com" , " Leading dot in domain" },
111+ {" http://example.com." , " Trailing dot in domain" },
113112
114113 // Very long URLs (stress test buffers)
115- {" http://example.com/" + std::string (1000 , ' a' ), " Very long path (1KB)" , true },
116- {" http://example.com/" + std::string (10000 , ' x' ), " Very long path (10KB)" , true },
117- {" http://" + std::string (253 , ' a' ) + " .com" , " Very long domain (253 chars)" , true },
118- {" http://example.com?" + std::string (1000 , ' q' ), " Very long query (1KB)" , true },
119- {" http://example.com#" + std::string (1000 , ' f' ), " Very long fragment (1KB)" , true },
114+ {" http://example.com/" + std::string (1000 , ' a' ), " Very long path (1KB)" },
115+ {" http://example.com/" + std::string (10000 , ' x' ), " Very long path (10KB)" },
116+ {" http://" + std::string (253 , ' a' ) + " .com" , " Very long domain (253 chars)" },
117+ {" http://example.com?" + std::string (1000 , ' q' ), " Very long query (1KB)" },
118+ {" http://example.com#" + std::string (1000 , ' f' ), " Very long fragment (1KB)" },
120119
121120 // Special characters (potential for injection or buffer issues)
122- {" http://example.com/path?key=<script>" , " HTML in query" , true },
123- {" http://example.com/path?key=\xF0\x9F\x92\xA9 " , " Emoji in query" , true },
124- {" http://example.com/\x00\x01\x02 " , " Control characters" , true },
125- {" http://example.com/\xFF\xFE " , " Invalid UTF-8" , true },
121+ {" http://example.com/path?key=<script>" , " HTML in query" },
122+ {" http://example.com/path?key=\xF0\x9F\x92\xA9 " , " Emoji in query" },
123+ {" http://example.com/\x00\x01\x02 " , " Control characters" },
124+ {" http://example.com/\xFF\xFE " , " Invalid UTF-8" },
126125
127126 // Backslash handling (Windows path edge cases)
128- {" http://example.com\\ path" , " Backslash in path" , true },
129- {" http:\\\\ example.com" , " Backslashes instead of slashes" , true },
127+ {" http://example.com\\ path" , " Backslash in path" },
128+ {" http:\\\\ example.com" , " Backslashes instead of slashes" },
130129
131130 // Empty and whitespace (boundary conditions)
132- {" " , " Empty string" , false },
133- {" " , " Single space" , true },
134- {" \t " , " Tab character" , true },
135- {" \n " , " Newline character" , true },
136- {" http://example.com " , " URL with surrounding whitespace" , true },
131+ {" " , " Empty string" },
132+ {" " , " Single space" },
133+ {" \t " , " Tab character" },
134+ {" \n " , " Newline character" },
135+ {" http://example.com " , " URL with surrounding whitespace" },
137136
138137 // Case sensitivity
139- {" HTTP://EXAMPLE.COM" , " Uppercase scheme and host" , true },
140- {" hTtP://eXaMpLe.CoM" , " Mixed case" , true },
138+ {" HTTP://EXAMPLE.COM" , " Uppercase scheme and host" },
139+ {" hTtP://eXaMpLe.CoM" , " Mixed case" },
141140
142141 // Punycode (IDNA encoding)
143- {" http://xn--nxasmq6b.example.com" , " Punycode domain" , true },
144- {" http://xn--ls8h.example.com" , " Punycode emoji" , true },
142+ {" http://xn--nxasmq6b.example.com" , " Punycode domain" },
143+ {" http://xn--ls8h.example.com" , " Punycode emoji" },
145144
146145 // Multiple encoding/decoding rounds (potential for bugs)
147- {" http://example.com/%252F" , " Double-encoded slash" , true },
148- {" http://example.com/%25%32%46" , " Triple-encoded slash" , true },
146+ {" http://example.com/%252F" , " Double-encoded slash" },
147+ {" http://example.com/%25%32%46" , " Triple-encoded slash" },
149148
150149 // Null and boundary values
151- {" http://example.com/" + std::string (1 , ' \0 ' ) + " path" , " Embedded null byte" , true },
152- {" http://example.com/\x7F " , " DEL character" , true },
150+ {" http://example.com/" + std::string (1 , ' \0 ' ) + " path" , " Embedded null byte" },
151+ {" http://example.com/\x7F " , " DEL character" },
153152
154153 // Repeated characters (stress test for buffer operations)
155- {" http://example.com/" + std::string (100 , ' /' ), " Many slashes" , true },
156- {" http://example.com?" + std::string (100 , ' &' ), " Many ampersands" , true },
157- {" http://example.com#" + std::string (100 , ' #' ), " Many hashes" , true },
154+ {" http://example.com/" + std::string (100 , ' /' ), " Many slashes" },
155+ {" http://example.com?" + std::string (100 , ' &' ), " Many ampersands" },
156+ {" http://example.com#" + std::string (100 , ' #' ), " Many hashes" },
158157 };
159158
160- std::cout << std::format (" Running {} URL test cases with AddressSanitizer + UndefinedBehaviorSanitizer\n\n " ,
159+ std::cout << std::format (" Running {} URL test cases with AddressSanitizer + UndefinedBehaviorSanitizer\n " ,
161160 test_cases.size ());
161+ std::cout << " The goal is to detect memory safety issues, not validate parsing correctness.\n " ;
162+ std::cout << " If sanitizers detect issues (buffer overflow, use-after-free, etc.), the program will abort.\n\n " ;
162163
163- size_t total_count = 0 ;
164- size_t pass_count = 0 ;
165- size_t fail_count = 0 ;
166-
164+ size_t test_number = 0 ;
167165 for (const auto & tc : test_cases) {
168- total_count++;
169- bool test_passed = false ;
166+ test_number++;
170167
171168 try {
172169 auto url_result = skyr::url (tc.input );
173-
174- if (tc.should_parse ) {
175- // Expected to parse successfully
176- test_passed = true ;
177- std::cout << std::format (" [PASS] Test {}: {}\n " , total_count, tc.description );
178- } else {
179- // Expected to fail but parsed successfully
180- test_passed = false ;
181- std::cout << std::format (" [FAIL] Test {}: {} - Expected parse failure\n " , total_count, tc.description );
182- std::cout << std::format (" Input: {}\n " , tc.input );
183- std::cout << std::format (" Got: {}\n " , url_result.href ());
184- }
185- } catch (const std::exception& e) {
186- if (!tc.should_parse ) {
187- // Expected to fail and did fail
188- test_passed = true ;
189- std::cout << std::format (" [PASS] Test {}: {} (correctly rejected)\n " , total_count, tc.description );
190- } else {
191- // Expected to parse but failed
192- test_passed = false ;
193- std::cout << std::format (" [FAIL] Test {}: {} - Parse error\n " , total_count, tc.description );
194- std::cout << std::format (" Input: {}\n " , tc.input );
195- std::cout << std::format (" Error: {}\n " , e.what ());
196- }
197- }
198-
199- if (test_passed) {
200- pass_count++;
201- } else {
202- fail_count++;
170+ // URL parsed successfully - sanitizers checked for memory issues
171+ std::cout << std::format (" [{:3}] {} - parsed\n " , test_number, tc.description );
172+ } catch (const std::exception&) {
173+ // URL parsing failed (rejected as invalid) - sanitizers still checked for memory issues
174+ std::cout << std::format (" [{:3}] {} - rejected\n " , test_number, tc.description );
203175 }
204176 }
205177
206178 std::cout << std::format (" \n {}\n " , std::string (80 , ' =' ));
207- std::cout << std::format (" AddressSanitizer Test Summary:\n " );
208- std::cout << std::format (" Total: {}\n " , total_count);
209- std::cout << std::format (" Passed: {} ({:.1f}%)\n " , pass_count, 100.0 * pass_count / total_count);
210- std::cout << std::format (" Failed: {} ({:.1f}%)\n " , fail_count, 100.0 * fail_count / total_count);
179+ std::cout << std::format (" ✓ All {} tests completed successfully!\n " , test_number);
180+ std::cout << " No memory safety issues detected by AddressSanitizer or UndefinedBehaviorSanitizer.\n " ;
211181 std::cout << std::format (" {}\n " , std::string (80 , ' =' ));
212182
213- if (fail_count == 0 ) {
214- std::cout << " \n No memory safety issues detected by sanitizers!\n " ;
215- }
216-
217- return fail_count == 0 ? 0 : 1 ;
183+ return 0 ;
218184}
0 commit comments