Punycode (RFC3492) in OCaml

Add fuzz tests for ax25, cookeio, hostname, punycode, xff, x509

+90
+15
fuzz/dune
··· 1 + ; Crowbar fuzz testing for punycode 2 + ; 3 + ; To run: dune exec ocaml-punycode/fuzz/fuzz_punycode.exe 4 + ; With AFL: afl-fuzz -i fuzz/corpus -o fuzz/findings -- ./_build/default/ocaml-punycode/fuzz/fuzz_punycode.exe @@ 5 + 6 + (executable 7 + (name fuzz_punycode) 8 + (modules fuzz_punycode) 9 + (libraries punycode crowbar)) 10 + 11 + (rule 12 + (alias fuzz) 13 + (deps fuzz_punycode.exe) 14 + (action 15 + (run %{exe:fuzz_punycode.exe})))
+75
fuzz/fuzz_punycode.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 + SPDX-License-Identifier: MIT 4 + ---------------------------------------------------------------------------*) 5 + 6 + (* Crowbar-based fuzz testing for Punycode encoding/decoding *) 7 + 8 + open Crowbar 9 + 10 + (* Test that encode_utf8 never crashes on arbitrary input *) 11 + let test_encode_no_crash input = 12 + let _ = Punycode.encode_utf8 input in 13 + () 14 + 15 + (* Test that decode_utf8 never crashes on arbitrary input *) 16 + let test_decode_no_crash input = 17 + let _ = Punycode.decode_utf8 input in 18 + () 19 + 20 + (* Test roundtrip: encode then decode should give back original *) 21 + let test_roundtrip input = 22 + match Punycode.encode_utf8 input with 23 + | Ok encoded -> ( 24 + match Punycode.decode_utf8 encoded with 25 + | Ok decoded -> check_eq ~pp:Format.pp_print_string input decoded 26 + | Error _ -> 27 + (* Some encoded values might not decode, that's ok for fuzz testing *) 28 + ()) 29 + | Error _ -> 30 + (* Some inputs might not encode, that's ok *) 31 + () 32 + 33 + (* Test that error printing never crashes *) 34 + let () = 35 + let pos = { Punycode.byte_offset = 0; char_index = 0 } in 36 + let errors = 37 + [ 38 + Punycode.Overflow pos; 39 + Punycode.Invalid_character (pos, Uchar.of_int 0x1F600); 40 + Punycode.Invalid_digit (pos, 'x'); 41 + Punycode.Unexpected_end pos; 42 + Punycode.Invalid_utf8 pos; 43 + Punycode.Label_too_long 100; 44 + Punycode.Empty_label; 45 + ] 46 + in 47 + List.iter 48 + (fun e -> 49 + let _ = Format.asprintf "%a" Punycode.pp_error e in 50 + let _ = Punycode.error_to_string e in 51 + ()) 52 + errors 53 + 54 + (* Test ASCII-only strings (should pass through mostly unchanged) *) 55 + let test_ascii_string input = 56 + let ascii_only = 57 + String.init 58 + (String.length input mod 64) 59 + (fun i -> Char.chr (Char.code input.[i mod String.length input] mod 128)) 60 + in 61 + if String.length ascii_only > 0 then 62 + match Punycode.encode ascii_only with Ok _ -> () | Error _ -> () 63 + 64 + (* Test inputs starting with ACE prefix "xn--" *) 65 + let test_ace_prefix input = 66 + let ace_input = "xn--" ^ input in 67 + let _ = Punycode.decode_utf8 ace_input in 68 + () 69 + 70 + let () = 71 + add_test ~name:"punycode: encode no crash" [ bytes ] test_encode_no_crash; 72 + add_test ~name:"punycode: decode no crash" [ bytes ] test_decode_no_crash; 73 + add_test ~name:"punycode: roundtrip" [ bytes ] test_roundtrip; 74 + add_test ~name:"punycode: ascii string" [ bytes ] test_ascii_string; 75 + add_test ~name:"punycode: ace prefix" [ bytes ] test_ace_prefix