/src/ada-url/fuzz/parse.cc
Line | Count | Source |
1 | | #include <fuzzer/FuzzedDataProvider.h> |
2 | | |
3 | | #include <cassert> |
4 | | #include <cstdio> |
5 | | #include <memory> |
6 | | #include <string> |
7 | | |
8 | | #include "ada.cpp" |
9 | | #include "ada.h" |
10 | | |
11 | 56.5k | bool is_valid_utf8_string(const char* buf, size_t len) { |
12 | 56.5k | const uint8_t* data = reinterpret_cast<const uint8_t*>(buf); |
13 | 56.5k | uint64_t pos = 0; |
14 | 56.5k | uint32_t code_point = 0; |
15 | 112k | while (pos < len) { |
16 | 89.1k | uint64_t next_pos = pos + 16; |
17 | 89.1k | if (next_pos <= len) { // if it is safe to read 16 more bytes, check that |
18 | | // they are ascii |
19 | 47.3k | uint64_t v1; |
20 | 47.3k | std::memcpy(&v1, data + pos, sizeof(uint64_t)); |
21 | 47.3k | uint64_t v2; |
22 | 47.3k | std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t)); |
23 | 47.3k | uint64_t v{v1 | v2}; |
24 | 47.3k | if ((v & 0x8080808080808080) == 0) { |
25 | 19.1k | pos = next_pos; |
26 | 19.1k | continue; |
27 | 19.1k | } |
28 | 47.3k | } |
29 | 70.0k | unsigned char byte = data[pos]; |
30 | 265k | while (byte < 0b10000000) { |
31 | 221k | if (++pos == len) { |
32 | 26.2k | return true; |
33 | 26.2k | } |
34 | 195k | byte = data[pos]; |
35 | 195k | } |
36 | | |
37 | 43.7k | if ((byte & 0b11100000) == 0b11000000) { |
38 | 8.25k | next_pos = pos + 2; |
39 | 8.25k | if (next_pos > len) { |
40 | 484 | return false; |
41 | 484 | } |
42 | 7.77k | if ((data[pos + 1] & 0b11000000) != 0b10000000) { |
43 | 920 | return false; |
44 | 920 | } |
45 | 6.85k | code_point = (byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111); |
46 | 6.85k | if ((code_point < 0x80) || (0x7ff < code_point)) { |
47 | 51 | return false; |
48 | 51 | } |
49 | 35.4k | } else if ((byte & 0b11110000) == 0b11100000) { |
50 | 29.3k | next_pos = pos + 3; |
51 | 29.3k | if (next_pos > len) { |
52 | 211 | return false; |
53 | 211 | } |
54 | 29.1k | if ((data[pos + 1] & 0b11000000) != 0b10000000) { |
55 | 417 | return false; |
56 | 417 | } |
57 | 28.7k | if ((data[pos + 2] & 0b11000000) != 0b10000000) { |
58 | 87 | return false; |
59 | 87 | } |
60 | 28.6k | code_point = (byte & 0b00001111) << 12 | |
61 | 28.6k | (data[pos + 1] & 0b00111111) << 6 | |
62 | 28.6k | (data[pos + 2] & 0b00111111); |
63 | 28.6k | if ((code_point < 0x800) || (0xffff < code_point) || |
64 | 28.6k | (0xd7ff < code_point && code_point < 0xe000)) { |
65 | 40 | return false; |
66 | 40 | } |
67 | 28.6k | } else if ((byte & 0b11111000) == 0b11110000) { // 0b11110000 |
68 | 1.69k | next_pos = pos + 4; |
69 | 1.69k | if (next_pos > len) { |
70 | 181 | return false; |
71 | 181 | } |
72 | 1.51k | if ((data[pos + 1] & 0b11000000) != 0b10000000) { |
73 | 225 | return false; |
74 | 225 | } |
75 | 1.29k | if ((data[pos + 2] & 0b11000000) != 0b10000000) { |
76 | 46 | return false; |
77 | 46 | } |
78 | 1.24k | if ((data[pos + 3] & 0b11000000) != 0b10000000) { |
79 | 77 | return false; |
80 | 77 | } |
81 | 1.16k | code_point = |
82 | 1.16k | (byte & 0b00000111) << 18 | (data[pos + 1] & 0b00111111) << 12 | |
83 | 1.16k | (data[pos + 2] & 0b00111111) << 6 | (data[pos + 3] & 0b00111111); |
84 | 1.16k | if (code_point <= 0xffff || 0x10ffff < code_point) { |
85 | 52 | return false; |
86 | 52 | } |
87 | 4.41k | } else { |
88 | 4.41k | return false; |
89 | 4.41k | } |
90 | 36.5k | pos = next_pos; |
91 | 36.5k | } |
92 | 23.1k | return true; |
93 | 56.5k | } |
94 | | |
95 | | // Exercise all getters and boolean predicates on ada::url |
96 | 11.4k | static void exercise_url_predicates(const ada::url& u) { |
97 | 11.4k | volatile size_t length = 0; |
98 | 11.4k | length += u.get_href().size(); |
99 | 11.4k | length += u.get_origin().size(); |
100 | 11.4k | length += u.get_protocol().size(); |
101 | 11.4k | length += u.get_username().size(); |
102 | 11.4k | length += u.get_password().size(); |
103 | 11.4k | length += u.get_host().size(); |
104 | 11.4k | length += u.get_hostname().size(); |
105 | 11.4k | length += u.get_pathname().size(); |
106 | 11.4k | length += u.get_search().size(); |
107 | 11.4k | length += u.get_hash().size(); |
108 | 11.4k | length += u.get_port().size(); |
109 | 11.4k | length += u.to_string().size(); |
110 | 11.4k | length += u.get_pathname_length(); |
111 | 11.4k | (void)u.has_valid_domain(); |
112 | 11.4k | (void)u.has_credentials(); |
113 | 11.4k | (void)u.has_empty_hostname(); |
114 | 11.4k | (void)u.has_hostname(); |
115 | 11.4k | (void)u.has_port(); |
116 | 11.4k | (void)u.has_hash(); |
117 | 11.4k | (void)u.has_search(); |
118 | 11.4k | (void)u.get_components(); |
119 | 11.4k | } |
120 | | |
121 | | // Exercise all getters and boolean predicates on ada::url_aggregator |
122 | 29.7k | static void exercise_aggregator_predicates(const ada::url_aggregator& u) { |
123 | 29.7k | volatile size_t length = 0; |
124 | 29.7k | length += u.get_href().size(); |
125 | 29.7k | length += u.get_origin().size(); |
126 | 29.7k | length += u.get_protocol().size(); |
127 | 29.7k | length += u.get_username().size(); |
128 | 29.7k | length += u.get_password().size(); |
129 | 29.7k | length += u.get_host().size(); |
130 | 29.7k | length += u.get_hostname().size(); |
131 | 29.7k | length += u.get_pathname().size(); |
132 | 29.7k | length += u.get_search().size(); |
133 | 29.7k | length += u.get_hash().size(); |
134 | 29.7k | length += u.get_port().size(); |
135 | 29.7k | length += u.to_string().size(); |
136 | 29.7k | length += u.get_pathname_length(); |
137 | 29.7k | (void)u.has_valid_domain(); |
138 | 29.7k | (void)u.has_credentials(); |
139 | 29.7k | (void)u.has_empty_hostname(); |
140 | 29.7k | (void)u.has_hostname(); |
141 | 29.7k | (void)u.has_non_empty_username(); |
142 | 29.7k | (void)u.has_non_empty_password(); |
143 | 29.7k | (void)u.has_password(); |
144 | 29.7k | (void)u.has_port(); |
145 | 29.7k | (void)u.has_hash(); |
146 | 29.7k | (void)u.has_search(); |
147 | 29.7k | (void)u.get_components(); |
148 | 29.7k | volatile bool is_valid = u.validate(); |
149 | 29.7k | (void)is_valid; |
150 | 29.7k | (void)u.to_diagram(); |
151 | 29.7k | } |
152 | | |
153 | 19.9k | static std::string make_simple_absolute_candidate(FuzzedDataProvider& fdp) { |
154 | 19.9k | static constexpr const char* kSchemes[] = {"http://", "https://", "HTTP://", |
155 | 19.9k | "Https://", "http:", "https:"}; |
156 | 19.9k | static constexpr const char* kHosts[] = { |
157 | 19.9k | "example.com", |
158 | 19.9k | "www.example.com", |
159 | 19.9k | "WWW.Example.COM", |
160 | 19.9k | "a", |
161 | 19.9k | "a.b.c", |
162 | 19.9k | "192.168.0.1", |
163 | 19.9k | "0x7f.1", |
164 | 19.9k | "127.1", |
165 | 19.9k | "xn--nxasmq6b.com", |
166 | 19.9k | "xn--a", |
167 | 19.9k | "user@example.com", |
168 | 19.9k | "user:pass@example.com", |
169 | 19.9k | "example.com:8080", |
170 | 19.9k | "example.com:0", |
171 | 19.9k | "", |
172 | 19.9k | }; |
173 | 19.9k | static constexpr const char* kPaths[] = { |
174 | 19.9k | "", |
175 | 19.9k | "/", |
176 | 19.9k | "/path", |
177 | 19.9k | "/path/file.js", |
178 | 19.9k | "/a/./b/../c", |
179 | 19.9k | "/foo/%2e", |
180 | 19.9k | "/foo/%2e%2e", |
181 | 19.9k | "/path with space", |
182 | 19.9k | "/path\twith\ttab", |
183 | 19.9k | "\\path", |
184 | 19.9k | "/continue=https%3A%2F%2Fexample.com%2F", |
185 | 19.9k | }; |
186 | 19.9k | static constexpr const char* kQueries[] = { |
187 | 19.9k | "", |
188 | 19.9k | "?", |
189 | 19.9k | "?q=1", |
190 | 19.9k | "?hl=en&tab=wi", |
191 | 19.9k | "?continue=https%3A%2F%2Fexample.com%2F", |
192 | 19.9k | }; |
193 | 19.9k | static constexpr const char* kFrags[] = {"", "#", "#frag", "#x%20y"}; |
194 | | |
195 | 19.9k | std::string out; |
196 | 19.9k | out += kSchemes[fdp.ConsumeIntegralInRange<size_t>( |
197 | 19.9k | 0, sizeof(kSchemes) / sizeof(kSchemes[0]) - 1)]; |
198 | 19.9k | out += kHosts[fdp.ConsumeIntegralInRange<size_t>( |
199 | 19.9k | 0, sizeof(kHosts) / sizeof(kHosts[0]) - 1)]; |
200 | 19.9k | out += kPaths[fdp.ConsumeIntegralInRange<size_t>( |
201 | 19.9k | 0, sizeof(kPaths) / sizeof(kPaths[0]) - 1)]; |
202 | 19.9k | out += kQueries[fdp.ConsumeIntegralInRange<size_t>( |
203 | 19.9k | 0, sizeof(kQueries) / sizeof(kQueries[0]) - 1)]; |
204 | 19.9k | out += kFrags[fdp.ConsumeIntegralInRange<size_t>( |
205 | 19.9k | 0, sizeof(kFrags) / sizeof(kFrags[0]) - 1)]; |
206 | | |
207 | 19.9k | if (fdp.ConsumeBool() && !out.empty()) { |
208 | 838 | std::string mid = fdp.ConsumeRandomLengthString(16); |
209 | 838 | size_t pos = fdp.ConsumeIntegralInRange<size_t>(0, out.size()); |
210 | 838 | out.insert(pos, mid); |
211 | 838 | } |
212 | 19.9k | return out; |
213 | 19.9k | } |
214 | | |
215 | 26.7k | static void check_href_size_invariant(const ada::url& u) { |
216 | 26.7k | if (u.get_href_size() != u.get_href().size()) { |
217 | 0 | printf("get_href_size mismatch (url): size=%zu href.size=%zu href=%s\n", |
218 | 0 | u.get_href_size(), u.get_href().size(), u.get_href().c_str()); |
219 | 0 | abort(); |
220 | 0 | } |
221 | 26.7k | } |
222 | | |
223 | 26.7k | static void check_href_size_invariant(const ada::url_aggregator& u) { |
224 | 26.7k | if (u.get_href_size() != u.get_href().size()) { |
225 | 0 | printf( |
226 | 0 | "get_href_size mismatch (aggregator): size=%zu href.size=%zu href=%s\n", |
227 | 0 | u.get_href_size(), u.get_href().size(), |
228 | 0 | std::string(u.get_href()).c_str()); |
229 | 0 | abort(); |
230 | 0 | } |
231 | 26.7k | } |
232 | | |
233 | 19.9k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
234 | 19.9k | FuzzedDataProvider fdp(data, size); |
235 | 19.9k | std::string source = fdp.ConsumeRandomLengthString(256); |
236 | 19.9k | std::string base = fdp.ConsumeRandomLengthString(256); |
237 | | |
238 | | // volatile forces the compiler to store the results without undue |
239 | | // optimizations |
240 | 19.9k | volatile size_t length = 0; |
241 | | |
242 | 19.9k | std::string simple_abs = make_simple_absolute_candidate(fdp); |
243 | 19.9k | { |
244 | 19.9k | auto su = ada::parse<ada::url>(simple_abs); |
245 | 19.9k | auto sa = ada::parse<ada::url_aggregator>(simple_abs); |
246 | 19.9k | if (su.has_value() ^ sa.has_value()) { |
247 | 0 | printf("simple_abs parse agreement fail: %s\n", simple_abs.c_str()); |
248 | 0 | abort(); |
249 | 0 | } |
250 | 19.9k | if (su) { |
251 | 19.7k | if (su->get_href() != std::string(sa->get_href())) { |
252 | 0 | printf("simple_abs href mismatch:\n in=%s\n url=%s\n agg=%s\n", |
253 | 0 | simple_abs.c_str(), su->get_href().c_str(), |
254 | 0 | std::string(sa->get_href()).c_str()); |
255 | 0 | abort(); |
256 | 0 | } |
257 | 19.7k | check_href_size_invariant(*su); |
258 | 19.7k | check_href_size_invariant(*sa); |
259 | 19.7k | length += su->get_href().size(); |
260 | 19.7k | } |
261 | 19.9k | } |
262 | | |
263 | 19.9k | auto parse_url = ada::parse<ada::url>(source); |
264 | 19.9k | auto parse_url_aggregator = ada::parse<ada::url_aggregator>(source); |
265 | | |
266 | 19.9k | if (is_valid_utf8_string(source.data(), source.length())) { |
267 | 16.6k | if (parse_url.has_value() ^ parse_url_aggregator.has_value()) { |
268 | 0 | printf("Source used to parse: %s", source.c_str()); |
269 | 0 | abort(); |
270 | 0 | } |
271 | 16.6k | } |
272 | | |
273 | 19.9k | if (parse_url) { |
274 | 6.95k | length += parse_url->get_href().size(); |
275 | 6.95k | length += parse_url->get_origin().size(); |
276 | 6.95k | check_href_size_invariant(*parse_url); |
277 | 6.95k | } |
278 | | |
279 | 19.9k | if (parse_url_aggregator) { |
280 | 6.95k | length += parse_url_aggregator->get_href().size(); |
281 | 6.95k | length += parse_url_aggregator->get_origin().size(); |
282 | 6.95k | check_href_size_invariant(*parse_url_aggregator); |
283 | | |
284 | 6.95k | volatile bool is_parse_url_aggregator_output_valid = false; |
285 | 6.95k | is_parse_url_aggregator_output_valid = parse_url_aggregator->validate(); |
286 | | |
287 | 6.95k | assert(parse_url->get_protocol() == parse_url_aggregator->get_protocol()); |
288 | 6.95k | assert(parse_url->get_href() == parse_url_aggregator->get_href()); |
289 | 6.95k | assert(std::string(parse_url->get_hostname()) == |
290 | 6.95k | std::string(parse_url_aggregator->get_hostname())); |
291 | 6.95k | assert(std::string(parse_url->get_pathname()) == |
292 | 6.95k | std::string(parse_url_aggregator->get_pathname())); |
293 | 6.95k | assert(std::string(parse_url->get_search()) == |
294 | 6.95k | std::string(parse_url_aggregator->get_search())); |
295 | 6.95k | assert(std::string(parse_url->get_hash()) == |
296 | 6.95k | std::string(parse_url_aggregator->get_hash())); |
297 | 6.95k | assert(std::string(parse_url->get_port()) == |
298 | 6.95k | std::string(parse_url_aggregator->get_port())); |
299 | 6.95k | assert(parse_url->get_username() == |
300 | 6.95k | std::string(parse_url_aggregator->get_username())); |
301 | 6.95k | assert(parse_url->get_password() == |
302 | 6.95k | std::string(parse_url_aggregator->get_password())); |
303 | 6.95k | assert(std::string(parse_url->get_host()) == |
304 | 6.95k | std::string(parse_url_aggregator->get_host())); |
305 | | |
306 | | // Exercise all predicates on both types |
307 | 6.95k | exercise_url_predicates(*parse_url); |
308 | 6.95k | exercise_aggregator_predicates(*parse_url_aggregator); |
309 | | |
310 | | // Test set_href consistency |
311 | 6.95k | parse_url->set_href(source); |
312 | 6.95k | parse_url_aggregator->set_href(source); |
313 | 6.95k | assert(parse_url->get_href() == parse_url_aggregator->get_href()); |
314 | 6.95k | } |
315 | | |
316 | | /** |
317 | | * Test copy and move semantics |
318 | | */ |
319 | 19.9k | if (parse_url) { |
320 | | // Copy constructor |
321 | 6.95k | ada::url copied_url = *parse_url; |
322 | 6.95k | assert(copied_url.get_href() == parse_url->get_href()); |
323 | | |
324 | | // Copy assignment |
325 | 6.95k | ada::url assigned_url; |
326 | 6.95k | assigned_url = *parse_url; |
327 | 6.95k | assert(assigned_url.get_href() == parse_url->get_href()); |
328 | | |
329 | | // Move constructor |
330 | 6.95k | ada::url moved_url = std::move(copied_url); |
331 | 6.95k | assert(moved_url.get_href() == parse_url->get_href()); |
332 | 6.95k | } |
333 | | |
334 | 19.9k | if (parse_url_aggregator) { |
335 | | // Copy constructor |
336 | 6.95k | ada::url_aggregator copied_agg = *parse_url_aggregator; |
337 | 6.95k | assert(std::string(copied_agg.get_href()) == |
338 | 6.95k | std::string(parse_url_aggregator->get_href())); |
339 | | |
340 | | // Copy assignment |
341 | 6.95k | ada::url_aggregator assigned_agg; |
342 | 6.95k | assigned_agg = *parse_url_aggregator; |
343 | 6.95k | assert(std::string(assigned_agg.get_href()) == |
344 | 6.95k | std::string(parse_url_aggregator->get_href())); |
345 | | |
346 | | // Move constructor |
347 | 6.95k | ada::url_aggregator moved_agg = std::move(copied_agg); |
348 | 6.95k | assert(std::string(moved_agg.get_href()) == |
349 | 6.95k | std::string(parse_url_aggregator->get_href())); |
350 | | |
351 | | // Move assignment |
352 | 6.95k | ada::url_aggregator move_assigned_agg; |
353 | 6.95k | move_assigned_agg = std::move(assigned_agg); |
354 | 6.95k | assert(std::string(move_assigned_agg.get_href()) == |
355 | 6.95k | std::string(parse_url_aggregator->get_href())); |
356 | 6.95k | } |
357 | | |
358 | | /** |
359 | | * ada::parse<ada::url> |
360 | | */ |
361 | 19.9k | auto out_url = ada::parse<ada::url>("https://www.ada-url.com"); |
362 | | |
363 | 19.9k | if (out_url) { |
364 | 19.9k | out_url->set_protocol(source); |
365 | 19.9k | out_url->set_username(source); |
366 | 19.9k | out_url->set_password(source); |
367 | 19.9k | out_url->set_hostname(source); |
368 | 19.9k | out_url->set_host(source); |
369 | 19.9k | out_url->set_pathname(source); |
370 | 19.9k | out_url->set_search(source); |
371 | 19.9k | out_url->set_hash(source); |
372 | 19.9k | out_url->set_port(source); |
373 | | |
374 | | // getters |
375 | 19.9k | length += out_url->get_protocol().size(); |
376 | 19.9k | length += out_url->get_username().size(); |
377 | 19.9k | length += out_url->get_password().size(); |
378 | 19.9k | length += out_url->get_hostname().size(); |
379 | 19.9k | length += out_url->get_host().size(); |
380 | 19.9k | length += out_url->get_pathname().size(); |
381 | 19.9k | length += out_url->get_search().size(); |
382 | 19.9k | length += out_url->get_hash().size(); |
383 | 19.9k | length += out_url->get_origin().size(); |
384 | 19.9k | length += out_url->get_port().size(); |
385 | 19.9k | length += out_url->get_pathname_length(); |
386 | | |
387 | 19.9k | length += out_url->to_string().size(); |
388 | | |
389 | | // boolean predicates after setters |
390 | 19.9k | (void)out_url->has_valid_domain(); |
391 | 19.9k | (void)out_url->has_credentials(); |
392 | 19.9k | (void)out_url->has_empty_hostname(); |
393 | 19.9k | (void)out_url->has_hostname(); |
394 | 19.9k | (void)out_url->has_port(); |
395 | 19.9k | (void)out_url->has_hash(); |
396 | 19.9k | (void)out_url->has_search(); |
397 | 19.9k | (void)out_url->get_components(); |
398 | 19.9k | } |
399 | | |
400 | | /** |
401 | | * ada::parse<ada::url_aggregator> |
402 | | */ |
403 | 19.9k | auto out_aggregator = |
404 | 19.9k | ada::parse<ada::url_aggregator>("https://www.ada-url.com"); |
405 | | |
406 | 19.9k | if (out_aggregator) { |
407 | 19.9k | out_aggregator->set_protocol(source); |
408 | 19.9k | out_aggregator->set_username(source); |
409 | 19.9k | out_aggregator->set_password(source); |
410 | 19.9k | out_aggregator->set_hostname(source); |
411 | 19.9k | out_aggregator->set_host(source); |
412 | 19.9k | out_aggregator->set_pathname(source); |
413 | 19.9k | out_aggregator->set_search(source); |
414 | 19.9k | out_aggregator->set_hash(source); |
415 | 19.9k | out_aggregator->set_port(source); |
416 | | |
417 | | // getters |
418 | 19.9k | length += out_aggregator->get_protocol().size(); |
419 | 19.9k | length += out_aggregator->get_username().size(); |
420 | 19.9k | length += out_aggregator->get_password().size(); |
421 | 19.9k | length += out_aggregator->get_hostname().size(); |
422 | 19.9k | length += out_aggregator->get_host().size(); |
423 | 19.9k | length += out_aggregator->get_pathname().size(); |
424 | 19.9k | length += out_aggregator->get_search().size(); |
425 | 19.9k | length += out_aggregator->get_hash().size(); |
426 | 19.9k | length += out_aggregator->get_origin().size(); |
427 | 19.9k | length += out_aggregator->get_port().size(); |
428 | 19.9k | length += out_aggregator->get_pathname_length(); |
429 | | |
430 | 19.9k | length += out_aggregator->to_string().size(); |
431 | | |
432 | 19.9k | volatile bool is_output_valid = false; |
433 | 19.9k | is_output_valid = out_aggregator->validate(); |
434 | | |
435 | 19.9k | (void)out_aggregator->to_diagram(); |
436 | | |
437 | | // boolean predicates after setters |
438 | 19.9k | (void)out_aggregator->has_valid_domain(); |
439 | 19.9k | (void)out_aggregator->has_credentials(); |
440 | 19.9k | (void)out_aggregator->has_empty_hostname(); |
441 | 19.9k | (void)out_aggregator->has_hostname(); |
442 | 19.9k | (void)out_aggregator->has_non_empty_username(); |
443 | 19.9k | (void)out_aggregator->has_non_empty_password(); |
444 | 19.9k | (void)out_aggregator->has_password(); |
445 | 19.9k | (void)out_aggregator->has_port(); |
446 | 19.9k | (void)out_aggregator->has_hash(); |
447 | 19.9k | (void)out_aggregator->has_search(); |
448 | 19.9k | (void)out_aggregator->get_components(); |
449 | | |
450 | | // clear methods + postcondition assertions |
451 | 19.9k | out_aggregator->clear_port(); |
452 | 19.9k | if (out_aggregator->has_port()) { |
453 | 0 | printf("clear_port() did not clear has_port()\n"); |
454 | 0 | abort(); |
455 | 0 | } |
456 | 19.9k | if (!out_aggregator->get_port().empty()) { |
457 | 0 | printf("clear_port() left non-empty get_port()\n"); |
458 | 0 | abort(); |
459 | 0 | } |
460 | | |
461 | 19.9k | out_aggregator->clear_search(); |
462 | 19.9k | if (out_aggregator->has_search()) { |
463 | 0 | printf("clear_search() did not clear has_search()\n"); |
464 | 0 | abort(); |
465 | 0 | } |
466 | 19.9k | if (!out_aggregator->get_search().empty()) { |
467 | 0 | printf("clear_search() left non-empty get_search()\n"); |
468 | 0 | abort(); |
469 | 0 | } |
470 | | |
471 | 19.9k | out_aggregator->clear_hash(); |
472 | 19.9k | if (out_aggregator->has_hash()) { |
473 | 0 | printf("clear_hash() did not clear has_hash()\n"); |
474 | 0 | abort(); |
475 | 0 | } |
476 | 19.9k | if (!out_aggregator->get_hash().empty()) { |
477 | 0 | printf("clear_hash() left non-empty get_hash()\n"); |
478 | 0 | abort(); |
479 | 0 | } |
480 | 19.9k | } |
481 | | |
482 | | /** |
483 | | * Relative URL parsing with base (tests the base URL resolution code path) |
484 | | */ |
485 | 19.9k | auto base_url = ada::parse<ada::url>(base); |
486 | 19.9k | auto base_agg = ada::parse<ada::url_aggregator>(base); |
487 | | |
488 | 19.9k | if (base_url) { |
489 | 5.03k | auto result = ada::parse<ada::url>(source, &*base_url); |
490 | 5.03k | if (result) { |
491 | 4.47k | length += result->get_href().size(); |
492 | 4.47k | length += result->get_origin().size(); |
493 | 4.47k | exercise_url_predicates(*result); |
494 | 4.47k | } |
495 | 5.03k | } |
496 | | |
497 | 19.9k | if (base_agg) { |
498 | 5.03k | auto result = ada::parse<ada::url_aggregator>(source, &*base_agg); |
499 | 5.03k | if (result) { |
500 | 4.47k | length += result->get_href().size(); |
501 | 4.47k | length += result->get_origin().size(); |
502 | 4.47k | exercise_aggregator_predicates(*result); |
503 | 4.47k | } |
504 | 5.03k | } |
505 | | |
506 | | // Cross-type consistency: relative URL parsing with a base should agree |
507 | | // between url and url_aggregator representations for valid UTF-8 inputs. |
508 | 19.9k | if (is_valid_utf8_string(source.data(), source.length()) && |
509 | 16.6k | is_valid_utf8_string(base.data(), base.length()) && base_url && |
510 | 3.81k | base_agg) { |
511 | 3.81k | auto res_url = ada::parse<ada::url>(source, &*base_url); |
512 | 3.81k | auto res_agg = ada::parse<ada::url_aggregator>(source, &*base_agg); |
513 | 3.81k | if (res_url.has_value() ^ res_agg.has_value()) { |
514 | 0 | printf("Relative parse inconsistency for source=%s base=%s\n", |
515 | 0 | source.c_str(), base.c_str()); |
516 | 0 | abort(); |
517 | 0 | } |
518 | 3.81k | if (res_url && res_agg) { |
519 | 3.29k | if (res_url->get_href() != std::string(res_agg->get_href())) { |
520 | 0 | printf("Relative parse href mismatch for source=%s base=%s\n", |
521 | 0 | source.c_str(), base.c_str()); |
522 | 0 | abort(); |
523 | 0 | } |
524 | 3.29k | } |
525 | 3.81k | } |
526 | | |
527 | | /** |
528 | | * Chained relative URL resolution: parse source against base, then use the |
529 | | * result as the base for a second parse. Exercises multi-level inheritance. |
530 | | */ |
531 | 19.9k | if (base_agg) { |
532 | 5.03k | auto level1 = ada::parse<ada::url_aggregator>(source, &*base_agg); |
533 | 5.03k | if (level1) { |
534 | 4.47k | std::string input2 = fdp.ConsumeRandomLengthString(128); |
535 | 4.47k | auto level2 = ada::parse<ada::url_aggregator>(input2, &*level1); |
536 | 4.47k | if (level2) { |
537 | 3.45k | length += level2->get_href().size(); |
538 | 3.45k | volatile bool v = level2->validate(); |
539 | 3.45k | (void)v; |
540 | 3.45k | } |
541 | 4.47k | } |
542 | 5.03k | } |
543 | | |
544 | | /** |
545 | | * Known-good base URL with fuzzed relative input. Using a fixed valid base |
546 | | * lets the fuzzer focus entropy entirely on the relative-input code paths |
547 | | * (path resolution, query/fragment inheritance, scheme-relative URLs, etc.) |
548 | | */ |
549 | 19.9k | { |
550 | 19.9k | auto known_base = |
551 | 19.9k | ada::parse<ada::url_aggregator>("https://example.com/a/b/c?query#hash"); |
552 | 19.9k | if (known_base) { |
553 | 19.9k | auto result = ada::parse<ada::url_aggregator>(source, &*known_base); |
554 | 19.9k | if (result) { |
555 | 18.2k | length += result->get_href().size(); |
556 | 18.2k | exercise_aggregator_predicates(*result); |
557 | 18.2k | } |
558 | 19.9k | } |
559 | 19.9k | } |
560 | | |
561 | | /** |
562 | | * Node.js specific |
563 | | */ |
564 | 19.9k | length += ada::href_from_file(source).size(); |
565 | | |
566 | | /** |
567 | | * Others |
568 | | */ |
569 | 19.9k | bool is_valid = ada::checkers::verify_dns_length(source); |
570 | | |
571 | 19.9k | (void)is_valid; |
572 | | |
573 | | /** |
574 | | * Sequential setter interactions with FDP-controlled ordering. |
575 | | * |
576 | | * The existing code calls every setter with the same `source` value in a |
577 | | * fixed order. Here we let the fuzzer choose an arbitrary sequence of |
578 | | * setter/value pairs, checking that url and url_aggregator stay in sync |
579 | | * after every step. This exercises setter-interaction state bugs that |
580 | | * fixed-order testing would miss. |
581 | | */ |
582 | 19.9k | { |
583 | 19.9k | auto url_seq = ada::parse<ada::url>( |
584 | 19.9k | "https://user:pass@example.com:8080/path?query=1#hash"); |
585 | 19.9k | auto agg_seq = ada::parse<ada::url_aggregator>( |
586 | 19.9k | "https://user:pass@example.com:8080/path?query=1#hash"); |
587 | 19.9k | if (url_seq && agg_seq) { |
588 | 19.9k | int steps = fdp.ConsumeIntegralInRange(1, 8); |
589 | 63.0k | for (int i = 0; i < steps; ++i) { |
590 | 43.1k | std::string val = fdp.ConsumeRandomLengthString(64); |
591 | 43.1k | int which = fdp.ConsumeIntegralInRange(0, 8); |
592 | 43.1k | switch (which) { |
593 | 27.2k | case 0: |
594 | 27.2k | url_seq->set_protocol(val); |
595 | 27.2k | agg_seq->set_protocol(val); |
596 | 27.2k | break; |
597 | 1.84k | case 1: |
598 | 1.84k | url_seq->set_username(val); |
599 | 1.84k | agg_seq->set_username(val); |
600 | 1.84k | break; |
601 | 2.00k | case 2: |
602 | 2.00k | url_seq->set_password(val); |
603 | 2.00k | agg_seq->set_password(val); |
604 | 2.00k | break; |
605 | 3.23k | case 3: |
606 | 3.23k | url_seq->set_hostname(val); |
607 | 3.23k | agg_seq->set_hostname(val); |
608 | 3.23k | break; |
609 | 4.28k | case 4: |
610 | 4.28k | url_seq->set_host(val); |
611 | 4.28k | agg_seq->set_host(val); |
612 | 4.28k | break; |
613 | 1.69k | case 5: |
614 | 1.69k | url_seq->set_pathname(val); |
615 | 1.69k | agg_seq->set_pathname(val); |
616 | 1.69k | break; |
617 | 659 | case 6: |
618 | 659 | url_seq->set_search(val); |
619 | 659 | agg_seq->set_search(val); |
620 | 659 | break; |
621 | 414 | case 7: |
622 | 414 | url_seq->set_hash(val); |
623 | 414 | agg_seq->set_hash(val); |
624 | 414 | break; |
625 | 1.72k | case 8: |
626 | 1.72k | url_seq->set_port(val); |
627 | 1.72k | agg_seq->set_port(val); |
628 | 1.72k | break; |
629 | 43.1k | } |
630 | | // After every setter both representations must agree on href. |
631 | 43.1k | if (url_seq->get_href() != std::string(agg_seq->get_href())) { |
632 | 0 | printf( |
633 | 0 | "Sequential setter href mismatch after setter=%d val='%s'\n" |
634 | 0 | " url: %s\n agg: %s\n", |
635 | 0 | which, val.c_str(), url_seq->get_href().c_str(), |
636 | 0 | std::string(agg_seq->get_href()).c_str()); |
637 | 0 | abort(); |
638 | 0 | } |
639 | | // url_aggregator internal invariant must still hold. |
640 | 43.1k | volatile bool v = agg_seq->validate(); |
641 | 43.1k | (void)v; |
642 | 43.1k | } |
643 | 19.9k | } |
644 | 19.9k | } |
645 | | |
646 | | /** |
647 | | * Re-parse idempotency. |
648 | | * |
649 | | * If parse(source) succeeds, then parse(href) must also succeed and |
650 | | * produce the same href. Serialization and parsing must be consistent: |
651 | | * a normalized URL is always its own fixed point. |
652 | | */ |
653 | 19.9k | if (parse_url_aggregator) { |
654 | 6.95k | std::string href1 = std::string(parse_url_aggregator->get_href()); |
655 | 6.95k | auto reparsed = ada::parse<ada::url_aggregator>(href1); |
656 | 6.95k | if (!reparsed) { |
657 | 0 | printf("Re-parse of href failed unexpectedly: '%s'\n", href1.c_str()); |
658 | 0 | abort(); |
659 | 0 | } |
660 | 6.95k | std::string href2 = std::string(reparsed->get_href()); |
661 | 6.95k | if (href1 != href2) { |
662 | 0 | printf( |
663 | 0 | "Re-parse idempotency failure!\n" |
664 | 0 | " href1: %s\n href2: %s\n", |
665 | 0 | href1.c_str(), href2.c_str()); |
666 | 0 | abort(); |
667 | 0 | } |
668 | 6.95k | } |
669 | | |
670 | | /** |
671 | | * URL search params round-trip via URL integration. |
672 | | * |
673 | | * Construct a URL whose query is the fuzz source, extract the search |
674 | | * component as a url_search_params, mutate it, serialise it back, and |
675 | | * set it on the URL. Exercises the interaction between URL objects and |
676 | | * url_search_params and verifies that the combined pipeline doesn't crash. |
677 | | * |
678 | | * Also verifies the url_search_params serialisation idempotency property: |
679 | | * url_search_params(sp.to_string()).to_string() == sp.to_string() |
680 | | */ |
681 | 19.9k | { |
682 | 19.9k | std::string search_url = "https://example.com/?" + source; |
683 | 19.9k | auto url_with_search = ada::parse<ada::url_aggregator>(search_url); |
684 | 19.9k | if (url_with_search) { |
685 | | // Extract the search string (may include leading '?'). |
686 | 19.9k | std::string search_raw = std::string(url_with_search->get_search()); |
687 | 19.9k | std::string_view search_view = search_raw; |
688 | 19.9k | if (!search_view.empty() && search_view[0] == '?') { |
689 | 16.3k | search_view = search_view.substr(1); |
690 | 16.3k | } |
691 | | |
692 | 19.9k | ada::url_search_params sp(search_view); |
693 | | |
694 | | // Mutate with additional entries from the fuzz corpus. |
695 | 19.9k | sp.append(source, base); |
696 | | |
697 | 19.9k | std::string serialized = sp.to_string(); |
698 | | |
699 | | // Idempotency: re-parsing the serialised form must yield the same string. |
700 | 19.9k | ada::url_search_params sp2(serialized); |
701 | 19.9k | std::string serialized2 = sp2.to_string(); |
702 | 19.9k | if (serialized2 != serialized) { |
703 | 0 | printf( |
704 | 0 | "url_search_params serialisation not idempotent!\n" |
705 | 0 | " first: %s\n second: %s\n", |
706 | 0 | serialized.c_str(), serialized2.c_str()); |
707 | 0 | abort(); |
708 | 0 | } |
709 | | |
710 | | // Set the serialised params back on the URL. |
711 | 19.9k | url_with_search->set_search(serialized); |
712 | 19.9k | volatile bool v = url_with_search->validate(); |
713 | 19.9k | (void)v; |
714 | 19.9k | } |
715 | 19.9k | } |
716 | | |
717 | 19.9k | return 0; |
718 | 19.9k | } // extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { |