155 lines
4.7 KiB
OCaml
155 lines
4.7 KiB
OCaml
open Crowbar
|
|
|
|
exception Encode_error of string
|
|
|
|
exception Decode_error of string
|
|
|
|
(** Pretty printers *)
|
|
|
|
let register_printer () =
|
|
Printexc.register_printer (function
|
|
| Encode_error err -> Some (Fmt.str "(Encoding error: %s)" err)
|
|
| Decode_error err -> Some (Fmt.str "(Decoding error: %s)" err)
|
|
| _ -> None)
|
|
|
|
let pp_chr =
|
|
let escaped = function ' ' .. '~' as c -> String.make 1 c | _ -> "." in
|
|
Fmt.using escaped Fmt.string
|
|
|
|
let pp_scalar :
|
|
type buffer.
|
|
get:(buffer -> int -> char) -> length:(buffer -> int) -> buffer Fmt.t =
|
|
fun ~get ~length ppf b ->
|
|
let l = length b in
|
|
for i = 0 to l / 16 do
|
|
Fmt.pf ppf "%08x: " (i * 16) ;
|
|
let j = ref 0 in
|
|
while !j < 16 do
|
|
if (i * 16) + !j < l
|
|
then Fmt.pf ppf "%02x" (Char.code @@ get b ((i * 16) + !j))
|
|
else Fmt.pf ppf " " ;
|
|
if !j mod 2 <> 0 then Fmt.pf ppf " " ;
|
|
incr j
|
|
done ;
|
|
Fmt.pf ppf " " ;
|
|
j := 0 ;
|
|
while !j < 16 do
|
|
if (i * 16) + !j < l
|
|
then Fmt.pf ppf "%a" pp_chr (get b ((i * 16) + !j))
|
|
else Fmt.pf ppf " " ;
|
|
incr j
|
|
done ;
|
|
Fmt.pf ppf "@\n"
|
|
done
|
|
|
|
let pp = pp_scalar ~get:String.get ~length:String.length
|
|
|
|
(** Encoding and decoding *)
|
|
|
|
let check_encode str =
|
|
let subs = Astring.String.cuts ~sep:"\r\n" str in
|
|
let check str =
|
|
if String.length str > 78
|
|
then raise (Encode_error "too long string returned") in
|
|
List.iter check subs ;
|
|
str
|
|
|
|
let encode input =
|
|
let buf = Buffer.create 80 in
|
|
let encoder = Base64_rfc2045.encoder (`Buffer buf) in
|
|
String.iter
|
|
(fun c ->
|
|
let ret = Base64_rfc2045.encode encoder (`Char c) in
|
|
match ret with `Ok -> () | _ -> assert false)
|
|
(* XXX(dinosaure): [`Partial] can never occur. *)
|
|
input ;
|
|
let encode = Base64_rfc2045.encode encoder `End in
|
|
match encode with
|
|
| `Ok -> Buffer.contents buf |> check_encode
|
|
| _ -> (* XXX(dinosaure): [`Partial] can never occur. *) assert false
|
|
|
|
let decode input =
|
|
let decoder = Base64_rfc2045.decoder (`String input) in
|
|
let rec go acc =
|
|
if Base64_rfc2045.decoder_dangerous decoder
|
|
then raise (Decode_error "Dangerous input") ;
|
|
match Base64_rfc2045.decode decoder with
|
|
| `End -> List.rev acc
|
|
| `Flush output -> go (output :: acc)
|
|
| `Malformed _ -> raise (Decode_error "Malformed")
|
|
| `Wrong_padding -> raise (Decode_error "Wrong padding")
|
|
| _ -> (* XXX(dinosaure): [`Await] can never occur. *) assert false in
|
|
String.concat "" (go [])
|
|
|
|
(** String generators *)
|
|
|
|
let bytes_fixed_range : string gen = dynamic_bind (range 78) bytes_fixed
|
|
|
|
let char_from_alpha alpha : string gen =
|
|
map [ range (String.length alpha) ] (fun i -> alpha.[i] |> String.make 1)
|
|
|
|
let string_from_alpha n =
|
|
let acc = const "" in
|
|
let alpha = Base64_rfc2045.default_alphabet in
|
|
let rec add_char_from_alpha alpha acc = function
|
|
| 0 -> acc
|
|
| n ->
|
|
add_char_from_alpha alpha
|
|
(concat_gen_list (const "") [ acc; char_from_alpha alpha ])
|
|
(n - 1) in
|
|
add_char_from_alpha alpha acc n
|
|
|
|
let random_string_from_alpha n = dynamic_bind (range n) string_from_alpha
|
|
|
|
let bytes_fixed_range_from_alpha : string gen =
|
|
dynamic_bind (range 78) bytes_fixed
|
|
|
|
let set_canonic str =
|
|
let l = String.length str in
|
|
let to_drop = l * 6 mod 8 in
|
|
if to_drop = 6
|
|
(* XXX(clecat): Case when we need to drop 6 bits which means a whole letter *)
|
|
then String.sub str 0 (l - 1)
|
|
else if to_drop <> 0
|
|
(* XXX(clecat): Case when we need to drop 2 or 4 bits: we apply a mask droping the bits *)
|
|
then (
|
|
let buf = Bytes.of_string str in
|
|
let value =
|
|
String.index Base64_rfc2045.default_alphabet (Bytes.get buf (l - 1)) in
|
|
let canonic =
|
|
Base64_rfc2045.default_alphabet.[value land lnot ((1 lsl to_drop) - 1)]
|
|
in
|
|
Bytes.set buf (l - 1) canonic ;
|
|
Bytes.unsafe_to_string buf)
|
|
else str
|
|
|
|
let add_padding str =
|
|
let str = set_canonic str in
|
|
let str = str ^ "===" in
|
|
String.sub str 0 (String.length str / 4 * 4)
|
|
|
|
(** Tests *)
|
|
|
|
let e2d inputs =
|
|
let input = String.concat "\r\n" inputs in
|
|
let encode = encode input in
|
|
let decode = decode encode in
|
|
check_eq ~pp ~cmp:String.compare ~eq:String.equal input decode
|
|
|
|
let d2e inputs end_input =
|
|
let end_input = add_padding end_input in
|
|
let inputs = inputs @ [ end_input ] in
|
|
let input =
|
|
List.fold_left
|
|
(fun acc s -> if String.length s <> 0 then acc ^ "\r\n" ^ s else acc)
|
|
(List.hd inputs) (List.tl inputs) in
|
|
let decode = decode input in
|
|
let encode = encode decode in
|
|
check_eq ~pp ~cmp:String.compare ~eq:String.equal input encode
|
|
|
|
let () =
|
|
register_printer () ;
|
|
add_test ~name:"rfc2045: encode -> decode" [ list bytes_fixed_range ] e2d ;
|
|
add_test ~name:"rfc2045: decode -> encode"
|
|
[ list (string_from_alpha 76); random_string_from_alpha 76 ]
|
|
d2e
|