/src/ada-url/fuzz/ada_c.c
Line | Count | Source |
1 | | #include "ada_c.h" |
2 | | |
3 | | #include <stdbool.h> |
4 | | #include <stdio.h> |
5 | | #include <stdlib.h> |
6 | | #include <string.h> |
7 | | |
8 | 8.50k | int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
9 | 8.50k | if (size == 0) return 0; |
10 | | |
11 | | /** |
12 | | * Split input: use first half as URL input, second half as base URL |
13 | | */ |
14 | 8.50k | size_t half = size / 2; |
15 | 8.50k | const char* input = (const char*)data; |
16 | 8.50k | size_t input_len = half; |
17 | 8.50k | const char* base = (const char*)(data + half); |
18 | 8.50k | size_t base_len = size - half; |
19 | | |
20 | | /** |
21 | | * ada_parse and ada_can_parse |
22 | | */ |
23 | 8.50k | ada_url out = ada_parse(input, input_len); |
24 | 8.50k | bool is_valid = ada_is_valid(out); |
25 | | |
26 | 8.50k | if (is_valid) { |
27 | 4.69k | ada_set_href(out, input, input_len); |
28 | 4.69k | ada_set_host(out, input, input_len); |
29 | 4.69k | ada_set_hostname(out, input, input_len); |
30 | 4.69k | ada_set_protocol(out, input, input_len); |
31 | 4.69k | ada_set_username(out, input, input_len); |
32 | 4.69k | ada_set_password(out, input, input_len); |
33 | 4.69k | ada_set_port(out, input, input_len); |
34 | 4.69k | ada_set_pathname(out, input, input_len); |
35 | 4.69k | ada_set_search(out, input, input_len); |
36 | 4.69k | ada_set_hash(out, input, input_len); |
37 | | |
38 | 4.69k | ada_get_hash(out); |
39 | 4.69k | ada_get_host(out); |
40 | 4.69k | uint8_t host_type = ada_get_host_type(out); |
41 | | /* host_type must be in [0, 2]: DEFAULT=0, IPV4=1, IPV6=2 */ |
42 | 4.69k | if (host_type > 2) { |
43 | 0 | printf("ada_get_host_type returned out-of-range value: %u\n", |
44 | 0 | (unsigned)host_type); |
45 | 0 | abort(); |
46 | 0 | } |
47 | 4.69k | ada_get_hostname(out); |
48 | 4.69k | ada_get_href(out); |
49 | 4.69k | ada_owned_string out_get_origin = ada_get_origin(out); |
50 | 4.69k | ada_get_pathname(out); |
51 | 4.69k | ada_get_username(out); |
52 | 4.69k | ada_get_password(out); |
53 | 4.69k | ada_get_protocol(out); |
54 | 4.69k | ada_get_port(out); |
55 | 4.69k | ada_get_search(out); |
56 | | |
57 | 4.69k | uint8_t scheme_type = ada_get_scheme_type(out); |
58 | | /* scheme_type must be in [0, 6]: HTTP=0, NOT_SPECIAL=1, HTTPS=2, |
59 | | * WS=3, FTP=4, WSS=5, FILE=6 */ |
60 | 4.69k | if (scheme_type > 6) { |
61 | 0 | printf("ada_get_scheme_type returned out-of-range value: %u\n", |
62 | 0 | (unsigned)scheme_type); |
63 | 0 | abort(); |
64 | 0 | } |
65 | | |
66 | 4.69k | ada_has_credentials(out); |
67 | 4.69k | ada_has_empty_hostname(out); |
68 | 4.69k | ada_has_hostname(out); |
69 | 4.69k | ada_has_non_empty_username(out); |
70 | 4.69k | ada_has_non_empty_password(out); |
71 | 4.69k | ada_has_port(out); |
72 | 4.69k | ada_has_password(out); |
73 | 4.69k | ada_has_hash(out); |
74 | 4.69k | ada_has_search(out); |
75 | | |
76 | | /* Validate ada_url_components offsets: every non-omitted field offset |
77 | | * must be <= href.length, so the component sits within the href. */ |
78 | 4.69k | ada_string href_for_comps = ada_get_href(out); |
79 | 4.69k | const ada_url_components* comps = ada_get_components(out); |
80 | 4.69k | if (comps->protocol_end != ada_url_omitted) |
81 | 4.69k | if (comps->protocol_end > href_for_comps.length) { |
82 | 0 | printf("ada_url_components.protocol_end out of bounds\n"); |
83 | 0 | abort(); |
84 | 0 | } |
85 | 4.69k | if (comps->username_end != ada_url_omitted) |
86 | 4.69k | if (comps->username_end > href_for_comps.length) { |
87 | 0 | printf("ada_url_components.username_end out of bounds\n"); |
88 | 0 | abort(); |
89 | 0 | } |
90 | 4.69k | if (comps->host_start != ada_url_omitted) |
91 | 4.69k | if (comps->host_start > href_for_comps.length) { |
92 | 0 | printf("ada_url_components.host_start out of bounds\n"); |
93 | 0 | abort(); |
94 | 0 | } |
95 | 4.69k | if (comps->host_end != ada_url_omitted) |
96 | 4.69k | if (comps->host_end > href_for_comps.length) { |
97 | 0 | printf("ada_url_components.host_end out of bounds\n"); |
98 | 0 | abort(); |
99 | 0 | } |
100 | | /* NOTE: comps->port is a port NUMBER (0-65535), not an offset. Skip. */ |
101 | 4.69k | if (comps->pathname_start != ada_url_omitted) |
102 | 4.69k | if (comps->pathname_start > href_for_comps.length) { |
103 | 0 | printf("ada_url_components.pathname_start out of bounds\n"); |
104 | 0 | abort(); |
105 | 0 | } |
106 | 4.69k | if (comps->search_start != ada_url_omitted) |
107 | 4.69k | if (comps->search_start > href_for_comps.length) { |
108 | 0 | printf("ada_url_components.search_start out of bounds\n"); |
109 | 0 | abort(); |
110 | 0 | } |
111 | 4.69k | if (comps->hash_start != ada_url_omitted) |
112 | 4.69k | if (comps->hash_start > href_for_comps.length) { |
113 | 0 | printf("ada_url_components.hash_start out of bounds\n"); |
114 | 0 | abort(); |
115 | 0 | } |
116 | | |
117 | 4.69k | ada_clear_port(out); |
118 | 4.69k | ada_clear_hash(out); |
119 | 4.69k | ada_clear_search(out); |
120 | | |
121 | 4.69k | ada_free_owned_string(out_get_origin); |
122 | | |
123 | | /* Test ada_copy */ |
124 | 4.69k | ada_url out_copy = ada_copy(out); |
125 | 4.69k | bool copy_is_valid = ada_is_valid(out_copy); |
126 | 4.69k | if (copy_is_valid) { |
127 | 4.69k | ada_string href_orig = ada_get_href(out); |
128 | 4.69k | ada_string href_copy = ada_get_href(out_copy); |
129 | | /* The copy should have the same href (after our setters above) */ |
130 | 4.69k | (void)href_orig; |
131 | 4.69k | (void)href_copy; |
132 | 4.69k | } |
133 | 4.69k | ada_free(out_copy); |
134 | | |
135 | | /* Re-parse idempotency via C API. |
136 | | * |
137 | | * After all setter mutations the URL must still be in a consistent state. |
138 | | * Whatever href it has now should be its own fixed point: parsing it |
139 | | * again must succeed and produce the same href. |
140 | | * |
141 | | * We read the href before creating the second URL so that the pointer |
142 | | * remains valid (we do not call any setter on 'out' after this point). */ |
143 | 4.69k | { |
144 | 4.69k | ada_string final_href = ada_get_href(out); |
145 | 4.69k | ada_url reparsed = ada_parse(final_href.data, final_href.length); |
146 | 4.69k | if (ada_is_valid(reparsed)) { |
147 | 4.69k | ada_string reparsed_href = ada_get_href(reparsed); |
148 | 4.69k | if (reparsed_href.length != final_href.length || |
149 | 4.69k | memcmp(reparsed_href.data, final_href.data, final_href.length) != |
150 | 4.69k | 0) { |
151 | 0 | printf( |
152 | 0 | "C API href idempotency failure!\n" |
153 | 0 | " final: %.*s\n reparsed: %.*s\n", |
154 | 0 | (int)final_href.length, final_href.data, |
155 | 0 | (int)reparsed_href.length, reparsed_href.data); |
156 | 0 | ada_free(reparsed); |
157 | 0 | abort(); |
158 | 0 | } |
159 | 4.69k | } else { |
160 | | /* After only valid setter calls the URL must remain parseable. */ |
161 | 0 | printf("C API re-parse of href failed: %.*s\n", (int)final_href.length, |
162 | 0 | final_href.data); |
163 | 0 | ada_free(reparsed); |
164 | 0 | abort(); |
165 | 0 | } |
166 | 4.69k | ada_free(reparsed); |
167 | 4.69k | } |
168 | 4.69k | } |
169 | | |
170 | 8.50k | bool can_parse_result = ada_can_parse(input, input_len); |
171 | 8.50k | (void)can_parse_result; |
172 | | |
173 | 8.50k | ada_free(out); |
174 | | |
175 | | /** |
176 | | * ada_parse_with_base and ada_can_parse_with_base |
177 | | */ |
178 | 8.50k | ada_url out_with_base = ada_parse_with_base(input, input_len, base, base_len); |
179 | 8.50k | bool with_base_valid = ada_is_valid(out_with_base); |
180 | | |
181 | 8.50k | if (with_base_valid) { |
182 | 524 | ada_string href = ada_get_href(out_with_base); |
183 | 524 | volatile size_t len = href.length; |
184 | 524 | (void)len; |
185 | | |
186 | 524 | ada_owned_string origin = ada_get_origin(out_with_base); |
187 | 524 | ada_free_owned_string(origin); |
188 | | |
189 | 524 | ada_get_hostname(out_with_base); |
190 | 524 | ada_get_pathname(out_with_base); |
191 | 524 | ada_get_search(out_with_base); |
192 | 524 | ada_get_hash(out_with_base); |
193 | 524 | ada_get_protocol(out_with_base); |
194 | 524 | ada_get_port(out_with_base); |
195 | 524 | ada_get_username(out_with_base); |
196 | 524 | ada_get_password(out_with_base); |
197 | | |
198 | 524 | ada_has_credentials(out_with_base); |
199 | 524 | ada_has_port(out_with_base); |
200 | 524 | ada_has_hash(out_with_base); |
201 | 524 | ada_has_search(out_with_base); |
202 | 524 | ada_get_components(out_with_base); |
203 | 524 | } |
204 | | |
205 | 8.50k | bool can_parse_with_base = |
206 | 8.50k | ada_can_parse_with_base(input, input_len, base, base_len); |
207 | | |
208 | | /* Consistency check: can_parse_with_base should match |
209 | | * ada_is_valid(ada_parse_with_base(...)) */ |
210 | 8.50k | if (can_parse_with_base != with_base_valid) { |
211 | 0 | printf("ada_can_parse_with_base inconsistency: can_parse=%d is_valid=%d\n", |
212 | 0 | can_parse_with_base, with_base_valid); |
213 | 0 | abort(); |
214 | 0 | } |
215 | | |
216 | 8.50k | ada_free(out_with_base); |
217 | | |
218 | | /** |
219 | | * IDNA C API |
220 | | */ |
221 | 8.50k | { |
222 | 8.50k | ada_owned_string unicode_result = ada_idna_to_unicode(input, input_len); |
223 | 8.50k | volatile size_t ulen = unicode_result.length; |
224 | 8.50k | (void)ulen; |
225 | 8.50k | ada_free_owned_string(unicode_result); |
226 | | |
227 | 8.50k | ada_owned_string ascii_result = ada_idna_to_ascii(input, input_len); |
228 | 8.50k | volatile size_t alen = ascii_result.length; |
229 | 8.50k | (void)alen; |
230 | 8.50k | ada_free_owned_string(ascii_result); |
231 | 8.50k | } |
232 | | |
233 | | /** |
234 | | * Version API |
235 | | */ |
236 | 8.50k | { |
237 | 8.50k | const char* version = ada_get_version(); |
238 | 8.50k | volatile size_t vlen = strlen(version); |
239 | 8.50k | (void)vlen; |
240 | | |
241 | 8.50k | ada_version_components ver_comps = ada_get_version_components(); |
242 | 8.50k | volatile int major = ver_comps.major; |
243 | 8.50k | (void)major; |
244 | 8.50k | } |
245 | | |
246 | | /** |
247 | | * Search params C API - comprehensive coverage |
248 | | */ |
249 | 8.50k | { |
250 | 8.50k | ada_url_search_params sp = ada_parse_search_params(input, input_len); |
251 | | |
252 | | /* Size */ |
253 | 8.50k | volatile size_t sp_size = ada_search_params_size(sp); |
254 | 8.50k | (void)sp_size; |
255 | | |
256 | | /* Append */ |
257 | 8.50k | ada_search_params_append(sp, input, input_len, base, base_len); |
258 | | |
259 | | /* Set (replaces first match) */ |
260 | 8.50k | ada_search_params_set(sp, input, input_len, base, base_len); |
261 | | |
262 | | /* has */ |
263 | 8.50k | volatile bool has_key = ada_search_params_has(sp, input, input_len); |
264 | 8.50k | (void)has_key; |
265 | | |
266 | | /* has_value */ |
267 | 8.50k | volatile bool has_kv = |
268 | 8.50k | ada_search_params_has_value(sp, input, input_len, base, base_len); |
269 | 8.50k | (void)has_kv; |
270 | | |
271 | | /* get - returns ada_string (may have data=NULL if not found) */ |
272 | 8.50k | ada_string got = ada_search_params_get(sp, input, input_len); |
273 | 8.50k | volatile size_t got_len = got.length; |
274 | 8.50k | (void)got_len; |
275 | | |
276 | | /* get_all */ |
277 | 8.50k | ada_strings all_vals = ada_search_params_get_all(sp, input, input_len); |
278 | 8.50k | volatile size_t all_size = ada_strings_size(all_vals); |
279 | 17.0k | for (size_t i = 0; i < all_size; i++) { |
280 | 8.50k | ada_string s = ada_strings_get(all_vals, i); |
281 | 8.50k | volatile size_t slen = s.length; |
282 | 8.50k | (void)slen; |
283 | 8.50k | } |
284 | 8.50k | ada_free_strings(all_vals); |
285 | | |
286 | | /* sort */ |
287 | 8.50k | ada_search_params_sort(sp); |
288 | | |
289 | | /* to_string */ |
290 | 8.50k | ada_owned_string sp_str = ada_search_params_to_string(sp); |
291 | 8.50k | volatile size_t str_len = sp_str.length; |
292 | 8.50k | (void)str_len; |
293 | 8.50k | ada_free_owned_string(sp_str); |
294 | | |
295 | | /* keys iterator */ |
296 | 8.50k | ada_url_search_params_keys_iter keys_iter = ada_search_params_get_keys(sp); |
297 | 17.5k | while (ada_search_params_keys_iter_has_next(keys_iter)) { |
298 | 9.07k | ada_string k = ada_search_params_keys_iter_next(keys_iter); |
299 | 9.07k | volatile size_t klen = k.length; |
300 | 9.07k | (void)klen; |
301 | 9.07k | } |
302 | 8.50k | ada_free_search_params_keys_iter(keys_iter); |
303 | | |
304 | | /* values iterator */ |
305 | 8.50k | ada_url_search_params_values_iter values_iter = |
306 | 8.50k | ada_search_params_get_values(sp); |
307 | 17.5k | while (ada_search_params_values_iter_has_next(values_iter)) { |
308 | 9.07k | ada_string v = ada_search_params_values_iter_next(values_iter); |
309 | 9.07k | volatile size_t vlen = v.length; |
310 | 9.07k | (void)vlen; |
311 | 9.07k | } |
312 | 8.50k | ada_free_search_params_values_iter(values_iter); |
313 | | |
314 | | /* entries iterator */ |
315 | 8.50k | ada_url_search_params_entries_iter entries_iter = |
316 | 8.50k | ada_search_params_get_entries(sp); |
317 | 17.5k | while (ada_search_params_entries_iter_has_next(entries_iter)) { |
318 | 9.07k | ada_string_pair entry = ada_search_params_entries_iter_next(entries_iter); |
319 | 9.07k | volatile size_t ek = entry.key.length; |
320 | 9.07k | volatile size_t ev = entry.value.length; |
321 | 9.07k | (void)ek; |
322 | 9.07k | (void)ev; |
323 | 9.07k | } |
324 | 8.50k | ada_free_search_params_entries_iter(entries_iter); |
325 | | |
326 | | /* remove */ |
327 | 8.50k | ada_search_params_remove(sp, input, input_len); |
328 | | |
329 | | /* remove_value */ |
330 | 8.50k | ada_search_params_remove_value(sp, input, input_len, base, base_len); |
331 | | |
332 | | /* reset */ |
333 | 8.50k | ada_search_params_reset(sp, base, base_len); |
334 | | |
335 | | /* Verify size after reset */ |
336 | 8.50k | volatile size_t sp_size_after = ada_search_params_size(sp); |
337 | 8.50k | (void)sp_size_after; |
338 | | |
339 | 8.50k | ada_free_search_params(sp); |
340 | 8.50k | } |
341 | | |
342 | 8.50k | return 0; |
343 | 8.50k | } |