(* * Copyright (c) 2016 Anil Madhavapeddy * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * *) open Printf open Rresult (* Test vectors from RFC4648 BASE64("") = "" BASE64("f") = "Zg==" BASE64("fo") = "Zm8=" BASE64("foo") = "Zm9v" BASE64("foob") = "Zm9vYg==" BASE64("fooba") = "Zm9vYmE=" BASE64("foobar") = "Zm9vYmFy" *) let rfc4648_tests = [ ("", ""); ("f", "Zg=="); ("fo", "Zm8="); ("foo", "Zm9v"); ("foob", "Zm9vYg=="); ("fooba", "Zm9vYmE="); ("foobar", "Zm9vYmFy"); ] let hannes_tests = [ ("dummy", "ZHVtbXk="); ("dummy", "ZHVtbXk"); ("dummy", "ZHVtbXk=="); ("dummy", "ZHVtbXk==="); ("dummy", "ZHVtbXk===="); ("dummy", "ZHVtbXk====="); ("dummy", "ZHVtbXk======"); ] let php_tests = [ ( "πάντα χωρεῖ καὶ οὐδὲν μένει …", "z4DOrM69z4TOsSDPh8-Jz4HOteG_liDOus6x4b22IM6_4b2QzrThvbLOvSDOvM6tzr3Otc65IOKApg" ); ] let rfc3548_tests = [ ("\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+"); ("\x14\xfb\x9c\x03\xd9", "FPucA9k="); ("\x14\xfb\x9c\x03", "FPucAw=="); ] let cfcs_tests = [ (0, 2, "\004", "BB"); (1, 2, "\004", "ABB"); (1, 2, "\004", "ABBA"); (2, 2, "\004", "AABBA"); (2, 2, "\004", "AABBAA"); (0, 0, "", "BB"); (1, 0, "", "BB"); (2, 0, "", "BB"); ] let nocrypto_tests = [ ("\x00\x5a\x6d\x39\x76", None); ("\x5a\x6d\x39\x76", Some "\x66\x6f\x6f"); ("\x5a\x6d\x39\x76\x76", None); ("\x5a\x6d\x39\x76\x76\x76", None); ("\x5a\x6d\x39\x76\x76\x76\x76", None); ("\x5a\x6d\x39\x76\x00", None); ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d", Some "\x66\x6f\x6f\x6f"); ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00", None); ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01", None); ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01\x02", None); ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01\x02\x03", None); ("\x5a\x6d\x39\x76\x62\x32\x38\x3d", Some "\x66\x6f\x6f\x6f\x6f"); ("\x5a\x6d\x39\x76\x62\x32\x39\x76", Some "\x66\x6f\x6f\x6f\x6f\x6f"); ("YWE=", Some "aa"); ("YWE==", None); ("YWE===", None); ("YWE=====", None); ("YWE======", None); ] let alphabet_size () = List.iter (fun (name, alphabet) -> Alcotest.(check int) (sprintf "Alphabet size %s = 64" name) 64 (Base64.length_alphabet alphabet)) [ ("default", Base64.default_alphabet); ("uri_safe", Base64.uri_safe_alphabet); ] (* Encode using OpenSSL `base64` utility *) let openssl_encode buf = Bos.( OS.Cmd.in_string buf |> OS.Cmd.run_io (Cmd.v "base64") |> OS.Cmd.to_string ~trim:true) |> function | Ok r -> prerr_endline r ; r | Error (`Msg e) -> raise (Failure (sprintf "OpenSSL decode: %s" e)) (* Encode using this library *) let lib_encode buf = Base64.encode_exn ~pad:true buf let test_rfc4648 () = List.iter (fun (c, r) -> (* Base64 vs openssl *) Alcotest.(check string) (sprintf "encode %s" c) (openssl_encode c) (lib_encode c) ; (* Base64 vs test cases above *) Alcotest.(check string) (sprintf "encode rfc4648 %s" c) r (lib_encode c) ; (* Base64 decode vs library *) Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn r)) rfc4648_tests let test_rfc3548 () = List.iter (fun (c, r) -> (* Base64 vs openssl *) Alcotest.(check string) (sprintf "encode %s" c) (openssl_encode c) (lib_encode c) ; (* Base64 vs test cases above *) Alcotest.(check string) (sprintf "encode rfc3548 %s" c) r (lib_encode c) ; (* Base64 decode vs library *) Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn r)) rfc3548_tests let test_hannes () = List.iter (fun (c, r) -> (* Base64 vs test cases above *) Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn ~pad:false r)) hannes_tests let test_php () = List.iter (fun (c, r) -> Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn ~pad:false ~alphabet:Base64.uri_safe_alphabet r)) php_tests let test_cfcs () = List.iter (fun (off, len, c, r) -> Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn ~pad:false ~off ~len r)) cfcs_tests let test_nocrypto () = List.iter (fun (input, res) -> let res' = match Base64.decode ~pad:true input with | Ok v -> Some v | Error _ -> None in Alcotest.(check (option string)) (sprintf "decode %S" input) res' res) nocrypto_tests exception Malformed exception Wrong_padding let strict_base64_rfc2045_of_string x = let decoder = Base64_rfc2045.decoder (`String x) in let res = Buffer.create 16 in let rec go () = match Base64_rfc2045.decode decoder with | `End -> () | `Wrong_padding -> raise Wrong_padding | `Malformed _ -> raise Malformed | `Flush x -> Buffer.add_string res x ; go () | `Await -> Alcotest.failf "Retrieve impossible case: `Await" in Base64_rfc2045.src decoder (Bytes.unsafe_of_string x) 0 (String.length x) ; go () ; Buffer.contents res let relaxed_base64_rfc2045_of_string x = let decoder = Base64_rfc2045.decoder (`String x) in let res = Buffer.create 16 in let rec go () = match Base64_rfc2045.decode decoder with | `End -> () | `Wrong_padding -> go () | `Malformed _ -> go () | `Flush x -> Buffer.add_string res x ; go () | `Await -> Alcotest.failf "Retrieve impossible case: `Await" in Base64_rfc2045.src decoder (Bytes.unsafe_of_string x) 0 (String.length x) ; go () ; Buffer.contents res let test_strict_rfc2045 = [ ( "c2FsdXQgbGVzIGNvcGFpbnMgZmF1dCBhYnNvbHVtZW50IHF1ZSBqZSBkw6lwYXNzZSBsZXMgODAg\r\n\ Y2hhcmFjdGVycyBwb3VyIHZvaXIgc2kgbW9uIGVuY29kZXIgZml0cyBiaWVuIGRhbnMgbGVzIGxp\r\n\ bWl0ZXMgZGUgbGEgUkZDIDIwNDUgLi4u", "salut les copains faut absolument que je dépasse les 80 characters \ pour voir si mon encoder fits bien dans les limites de la RFC 2045 ..." ); ("", ""); ("Zg==", "f"); ("Zm8=", "fo"); ("Zm9v", "foo"); ("Zm9vYg==", "foob"); ("Zm9vYmE=", "fooba"); ("Zm9vYmFy", "foobar"); ] let test_relaxed_rfc2045 = [ ("Zg", "f"); ("Zm\n8", "fo"); ("Zm\r9v", "foo"); ("Zm9 vYg", "foob"); ("Zm9\r\n vYmE", "fooba"); ("Zm9évYmFy", "foobar"); ] let strict_base64_rfc2045_to_string x = let res = Buffer.create 16 in let encoder = Base64_rfc2045.encoder (`Buffer res) in String.iter (fun chr -> match Base64_rfc2045.encode encoder (`Char chr) with | `Ok -> () | `Partial -> Alcotest.failf "Retrieve impossible case for (`Char %02x): `Partial" (Char.code chr)) x ; match Base64_rfc2045.encode encoder `End with | `Ok -> Buffer.contents res | `Partial -> Alcotest.fail "Retrieve impossible case for `End: `Partial" let test_strict_with_malformed_input_rfc2045 = List.mapi (fun i (has, _) -> Alcotest.test_case (Fmt.str "strict rfc2045 - %02d" i) `Quick @@ fun () -> try let _ = strict_base64_rfc2045_of_string has in Alcotest.failf "Strict parser valids malformed input: %S" has with Malformed | Wrong_padding -> ()) test_relaxed_rfc2045 let test_strict_rfc2045 = List.mapi (fun i (has, expect) -> Alcotest.test_case (Fmt.str "strict rfc2045 - %02d" i) `Quick @@ fun () -> try let res0 = strict_base64_rfc2045_of_string has in let res1 = strict_base64_rfc2045_to_string res0 in Alcotest.(check string) "encode(decode(x)) = x" res1 has ; Alcotest.(check string) "decode(x)" res0 expect with Malformed | Wrong_padding -> Alcotest.failf "Invalid input %S" has) test_strict_rfc2045 let test_relaxed_rfc2045 = List.mapi (fun i (has, expect) -> Alcotest.test_case (Fmt.str "relaxed rfc2045 - %02d" i) `Quick @@ fun () -> let res0 = relaxed_base64_rfc2045_of_string has in Alcotest.(check string) "decode(x)" res0 expect) test_relaxed_rfc2045 let test_invariants = [ ("Alphabet size", `Quick, alphabet_size) ] let test_codec = [ ("RFC4648 test vectors", `Quick, test_rfc4648); ("RFC3548 test vectors", `Quick, test_rfc3548); ("Hannes test vectors", `Quick, test_hannes); ("Cfcs test vectors", `Quick, test_cfcs); ("PHP test vectors", `Quick, test_php); ("Nocrypto test vectors", `Quick, test_nocrypto); ] let () = Alcotest.run "Base64" [ ("invariants", test_invariants); ("codec", test_codec); ("rfc2045 (0)", test_strict_rfc2045); ("rfc2045 (1)", test_strict_with_malformed_input_rfc2045); ("rfc2045 (2)", test_relaxed_rfc2045); ]