···15 let decode ?alphabet input =
16 let length = String.length input in
17 let input =
18- if length mod 4 = 0 then input
19- else input ^ String.make (4 - (length mod 4)) padding
20- in
21 let length = String.length input in
22 let words = length / 4 in
23 let padding =
···25 | 0 -> 0
26 | _ when input.[length - 2] = padding -> 2
27 | _ when input.[length - 1] = padding -> 1
28- | _ -> 0
29- in
30 let output = Bytes.make ((words * 3) - padding) '\000' in
31 for i = 0 to words - 1 do
32 let a = of_char ?alphabet input.[(4 * i) + 0]
···38 and y = (n lsr 8) land 255
39 and z = n land 255 in
40 Bytes.set output ((3 * i) + 0) (char_of_int x) ;
41- if i <> words - 1 || padding < 2 then
42- Bytes.set output ((3 * i) + 1) (char_of_int y) ;
43- if i <> words - 1 || padding < 1 then
44- Bytes.set output ((3 * i) + 2) (char_of_int z)
45 done ;
46 Bytes.unsafe_to_string output
47···71 for i = 1 to padding_len do
72 Bytes.set output (Bytes.length output - i) padding
73 done ;
74- if pad then Bytes.unsafe_to_string output
075 else Bytes.sub_string output 0 (Bytes.length output - padding_len)
76end
77···101102let args = [ 0; 10; 50; 100; 500; 1000; 2500; 5000 ]
103104-let test_b64 =
105- Test.create_indexed ~name:"Base64"
106- ~args b64_encode_and_decode
107108-let test_old =
109- Test.create_indexed ~name:"Old"
110- ~args old_encode_and_decode
111112-let command =
113- Bench.make_command [ test_b64; test_old ]
114115let () = Command.run command
···15 let decode ?alphabet input =
16 let length = String.length input in
17 let input =
18+ if length mod 4 = 0
19+ then input
20+ else input ^ String.make (4 - (length mod 4)) padding in
21 let length = String.length input in
22 let words = length / 4 in
23 let padding =
···25 | 0 -> 0
26 | _ when input.[length - 2] = padding -> 2
27 | _ when input.[length - 1] = padding -> 1
28+ | _ -> 0 in
029 let output = Bytes.make ((words * 3) - padding) '\000' in
30 for i = 0 to words - 1 do
31 let a = of_char ?alphabet input.[(4 * i) + 0]
···37 and y = (n lsr 8) land 255
38 and z = n land 255 in
39 Bytes.set output ((3 * i) + 0) (char_of_int x) ;
40+ if i <> words - 1 || padding < 2
41+ then Bytes.set output ((3 * i) + 1) (char_of_int y) ;
42+ if i <> words - 1 || padding < 1
43+ then Bytes.set output ((3 * i) + 2) (char_of_int z)
44 done ;
45 Bytes.unsafe_to_string output
46···70 for i = 1 to padding_len do
71 Bytes.set output (Bytes.length output - i) padding
72 done ;
73+ if pad
74+ then Bytes.unsafe_to_string output
75 else Bytes.sub_string output 0 (Bytes.length output - padding_len)
76end
77···101102let args = [ 0; 10; 50; 100; 500; 1000; 2500; 5000 ]
103104+let test_b64 = Test.create_indexed ~name:"Base64" ~args b64_encode_and_decode
00105106+let test_old = Test.create_indexed ~name:"Old" ~args old_encode_and_decode
00107108+let command = Bench.make_command [ test_b64; test_old ]
0109110let () = Command.run command
+31-25
config/config.ml
···1module Config = Configurator.V1
23-let pre407 = {ocaml|external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_string_set16u" [@@noalloc]|ocaml}
4-let standard = {ocaml|external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_bytes_set16u" [@@noalloc]|ocaml}
56-type t =
7- { major : int
8- ; minor : int
9- ; patch : int option
10- ; extra : string option }
1112-let v ?patch ?extra major minor = { major; minor; patch; extra; }
001314let parse s =
15- try Scanf.sscanf s "%d.%d.%d+%s" (fun major minor patch extra -> v ~patch ~extra major minor)
16- with End_of_file | Scanf.Scan_failure _ ->
17- ( try Scanf.sscanf s "%d.%d+%s" (fun major minor extra -> v ~extra major minor)
000000018 with End_of_file | Scanf.Scan_failure _ ->
19- ( try Scanf.sscanf s "%d.%d.%d" (fun major minor patch -> v ~patch major minor)
20- with End_of_file | Scanf.Scan_failure _ ->
21- Scanf.sscanf s "%d.%d" (fun major minor -> v major minor) ) )
2223-let ( >|= ) x f = match x with
24- | Some x -> Some (f x )
25- | None -> None
2627let ocaml_cp ~src ~dst =
28 let ic = open_in src in
29 let oc = open_out dst in
30 let bf = Bytes.create 0x1000 in
31- let rec go () = match input ic bf 0 (Bytes.length bf) with
032 | 0 -> ()
33- | len -> output oc bf 0 len ; go ()
0034 | exception End_of_file -> () in
35- go () ; close_in ic ; close_out oc
36-;;
03738let () =
39 Config.main ~name:"config-base64" @@ fun t ->
40 match Config.ocaml_config_var t "version" >|= parse with
41 | Some version ->
42- let dst = "unsafe.ml" in
4344- if (version.major, version.minor) >= (4, 7)
45- then ocaml_cp ~src:"unsafe_stable.ml" ~dst
46- else ocaml_cp ~src:"unsafe_pre407.ml" ~dst
47 | None -> Config.die "OCaml version is not available"
48 | exception exn -> Config.die "Got an exception: %s" (Printexc.to_string exn)
···1module Config = Configurator.V1
23+let pre407 =
4+ {ocaml|external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_string_set16u" [@@noalloc]|ocaml}
56+let standard =
7+ {ocaml|external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_bytes_set16u" [@@noalloc]|ocaml}
00089+type t = { major : int; minor : int; patch : int option; extra : string option }
10+11+let v ?patch ?extra major minor = { major; minor; patch; extra }
1213let parse s =
14+ try
15+ Scanf.sscanf s "%d.%d.%d+%s" (fun major minor patch extra ->
16+ v ~patch ~extra major minor)
17+ with End_of_file | Scanf.Scan_failure _ -> (
18+ try
19+ Scanf.sscanf s "%d.%d+%s" (fun major minor extra -> v ~extra major minor)
20+ with End_of_file | Scanf.Scan_failure _ -> (
21+ try
22+ Scanf.sscanf s "%d.%d.%d" (fun major minor patch ->
23+ v ~patch major minor)
24 with End_of_file | Scanf.Scan_failure _ ->
25+ Scanf.sscanf s "%d.%d" (fun major minor -> v major minor)))
002627+let ( >|= ) x f = match x with Some x -> Some (f x) | None -> None
002829let ocaml_cp ~src ~dst =
30 let ic = open_in src in
31 let oc = open_out dst in
32 let bf = Bytes.create 0x1000 in
33+ let rec go () =
34+ match input ic bf 0 (Bytes.length bf) with
35 | 0 -> ()
36+ | len ->
37+ output oc bf 0 len ;
38+ go ()
39 | exception End_of_file -> () in
40+ go () ;
41+ close_in ic ;
42+ close_out oc
4344let () =
45 Config.main ~name:"config-base64" @@ fun t ->
46 match Config.ocaml_config_var t "version" >|= parse with
47 | Some version ->
48+ let dst = "unsafe.ml" in
4950+ if (version.major, version.minor) >= (4, 7)
51+ then ocaml_cp ~src:"unsafe_stable.ml" ~dst
52+ else ocaml_cp ~src:"unsafe_pre407.ml" ~dst
53 | None -> Config.die "OCaml version is not available"
54 | exception exn -> Config.die "Got an exception: %s" (Printexc.to_string exn)
+29-32
fuzz/fuzz_rfc2045.ml
···1open Crowbar
23exception Encode_error of string
04exception Decode_error of string
56(** Pretty printers *)
···9 Printexc.register_printer (function
10 | Encode_error err -> Some (Fmt.strf "(Encoding error: %s)" err)
11 | Decode_error err -> Some (Fmt.strf "(Decoding error: %s)" err)
12- | _ -> None )
1314let pp_chr =
15 let escaped = function ' ' .. '~' as c -> String.make 1 c | _ -> "." in
16 Fmt.using escaped Fmt.string
1718-let pp_scalar : type buffer.
019 get:(buffer -> int -> char) -> length:(buffer -> int) -> buffer Fmt.t =
20 fun ~get ~length ppf b ->
21 let l = length b in
···23 Fmt.pf ppf "%08x: " (i * 16) ;
24 let j = ref 0 in
25 while !j < 16 do
26- if (i * 16) + !j < l then
27- Fmt.pf ppf "%02x" (Char.code @@ get b ((i * 16) + !j))
28 else Fmt.pf ppf " " ;
29 if !j mod 2 <> 0 then Fmt.pf ppf " " ;
30 incr j
···32 Fmt.pf ppf " " ;
33 j := 0 ;
34 while !j < 16 do
35- if (i * 16) + !j < l then Fmt.pf ppf "%a" pp_chr (get b ((i * 16) + !j))
036 else Fmt.pf ppf " " ;
37 incr j
38 done ;
···46let check_encode str =
47 let subs = Astring.String.cuts ~sep:"\r\n" str in
48 let check str =
49- if String.length str > 78 then
50- raise (Encode_error "too long string returned")
51- in
52- List.iter check subs ; str
5354let encode input =
55 let buf = Buffer.create 80 in
···57 String.iter
58 (fun c ->
59 let ret = Base64_rfc2045.encode encoder (`Char c) in
60- match ret with `Ok -> () | _ -> assert false )
61 (* XXX(dinosaure): [`Partial] can never occur. *)
62 input ;
63 let encode = Base64_rfc2045.encode encoder `End in
···68let decode input =
69 let decoder = Base64_rfc2045.decoder (`String input) in
70 let rec go acc =
71- if Base64_rfc2045.decoder_dangerous decoder then
72- raise (Decode_error "Dangerous input") ;
73 match Base64_rfc2045.decode decoder with
74 | `End -> List.rev acc
75 | `Flush output -> go (output :: acc)
76 | `Malformed _ -> raise (Decode_error "Malformed")
77 | `Wrong_padding -> raise (Decode_error "Wrong padding")
78- | _ -> (* XXX(dinosaure): [`Await] can never occur. *) assert false
79- in
80 String.concat "" (go [])
8182(** String generators *)
···84let bytes_fixed_range : string gen = dynamic_bind (range 78) bytes_fixed
8586let char_from_alpha alpha : string gen =
87- map [range (String.length alpha)] (fun i -> alpha.[i] |> String.make 1)
8889let string_from_alpha n =
90 let acc = const "" in
···93 | 0 -> acc
94 | n ->
95 add_char_from_alpha alpha
96- (concat_gen_list (const "") [acc; char_from_alpha alpha])
97- (n - 1)
98- in
99 add_char_from_alpha alpha acc n
100101let random_string_from_alpha n = dynamic_bind (range n) string_from_alpha
···106let set_canonic str =
107 let l = String.length str in
108 let to_drop = l * 6 mod 8 in
109- if
110- to_drop = 6
111- (* XXX(clecat): Case when we need to drop 6 bits which means a whole letter *)
112 then String.sub str 0 (l - 1)
113- else if
114- to_drop <> 0
115- (* XXX(clecat): Case when we need to drop 2 or 4 bits: we apply a mask droping the bits *)
116 then (
117 let buf = Bytes.of_string str in
118 let value =
119- String.index Base64_rfc2045.default_alphabet (Bytes.get buf (l - 1))
120- in
121 let canonic =
122 Base64_rfc2045.default_alphabet.[value land lnot ((1 lsl to_drop) - 1)]
123 in
124 Bytes.set buf (l - 1) canonic ;
125- Bytes.unsafe_to_string buf )
126 else str
127128let add_padding str =
···140141let d2e inputs end_input =
142 let end_input = add_padding end_input in
143- let inputs = inputs @ [end_input] in
144 let input =
145 List.fold_left
146 (fun acc s -> if String.length s <> 0 then acc ^ "\r\n" ^ s else acc)
147- (List.hd inputs) (List.tl inputs)
148- in
149 let decode = decode input in
150 let encode = encode decode in
151 check_eq ~pp ~cmp:String.compare ~eq:String.equal input encode
152153let () =
154 register_printer () ;
155- add_test ~name:"rfc2045: encode -> decode" [list bytes_fixed_range] e2d ;
156 add_test ~name:"rfc2045: decode -> encode"
157- [list (string_from_alpha 76); random_string_from_alpha 76]
158 d2e
···1open Crowbar
23exception Encode_error of string
4+5exception Decode_error of string
67(** Pretty printers *)
···10 Printexc.register_printer (function
11 | Encode_error err -> Some (Fmt.strf "(Encoding error: %s)" err)
12 | Decode_error err -> Some (Fmt.strf "(Decoding error: %s)" err)
13+ | _ -> None)
1415let pp_chr =
16 let escaped = function ' ' .. '~' as c -> String.make 1 c | _ -> "." in
17 Fmt.using escaped Fmt.string
1819+let pp_scalar :
20+ type buffer.
21 get:(buffer -> int -> char) -> length:(buffer -> int) -> buffer Fmt.t =
22 fun ~get ~length ppf b ->
23 let l = length b in
···25 Fmt.pf ppf "%08x: " (i * 16) ;
26 let j = ref 0 in
27 while !j < 16 do
28+ if (i * 16) + !j < l
29+ then Fmt.pf ppf "%02x" (Char.code @@ get b ((i * 16) + !j))
30 else Fmt.pf ppf " " ;
31 if !j mod 2 <> 0 then Fmt.pf ppf " " ;
32 incr j
···34 Fmt.pf ppf " " ;
35 j := 0 ;
36 while !j < 16 do
37+ if (i * 16) + !j < l
38+ then Fmt.pf ppf "%a" pp_chr (get b ((i * 16) + !j))
39 else Fmt.pf ppf " " ;
40 incr j
41 done ;
···49let check_encode str =
50 let subs = Astring.String.cuts ~sep:"\r\n" str in
51 let check str =
52+ if String.length str > 78
53+ then raise (Encode_error "too long string returned") in
54+ List.iter check subs ;
55+ str
5657let encode input =
58 let buf = Buffer.create 80 in
···60 String.iter
61 (fun c ->
62 let ret = Base64_rfc2045.encode encoder (`Char c) in
63+ match ret with `Ok -> () | _ -> assert false)
64 (* XXX(dinosaure): [`Partial] can never occur. *)
65 input ;
66 let encode = Base64_rfc2045.encode encoder `End in
···71let decode input =
72 let decoder = Base64_rfc2045.decoder (`String input) in
73 let rec go acc =
74+ if Base64_rfc2045.decoder_dangerous decoder
75+ then raise (Decode_error "Dangerous input") ;
76 match Base64_rfc2045.decode decoder with
77 | `End -> List.rev acc
78 | `Flush output -> go (output :: acc)
79 | `Malformed _ -> raise (Decode_error "Malformed")
80 | `Wrong_padding -> raise (Decode_error "Wrong padding")
81+ | _ -> (* XXX(dinosaure): [`Await] can never occur. *) assert false in
082 String.concat "" (go [])
8384(** String generators *)
···86let bytes_fixed_range : string gen = dynamic_bind (range 78) bytes_fixed
8788let char_from_alpha alpha : string gen =
89+ map [ range (String.length alpha) ] (fun i -> alpha.[i] |> String.make 1)
9091let string_from_alpha n =
92 let acc = const "" in
···95 | 0 -> acc
96 | n ->
97 add_char_from_alpha alpha
98+ (concat_gen_list (const "") [ acc; char_from_alpha alpha ])
99+ (n - 1) in
0100 add_char_from_alpha alpha acc n
101102let random_string_from_alpha n = dynamic_bind (range n) string_from_alpha
···107let set_canonic str =
108 let l = String.length str in
109 let to_drop = l * 6 mod 8 in
110+ if to_drop = 6
111+ (* XXX(clecat): Case when we need to drop 6 bits which means a whole letter *)
0112 then String.sub str 0 (l - 1)
113+ else if to_drop <> 0
114+ (* XXX(clecat): Case when we need to drop 2 or 4 bits: we apply a mask droping the bits *)
0115 then (
116 let buf = Bytes.of_string str in
117 let value =
118+ String.index Base64_rfc2045.default_alphabet (Bytes.get buf (l - 1)) in
0119 let canonic =
120 Base64_rfc2045.default_alphabet.[value land lnot ((1 lsl to_drop) - 1)]
121 in
122 Bytes.set buf (l - 1) canonic ;
123+ Bytes.unsafe_to_string buf)
124 else str
125126let add_padding str =
···138139let d2e inputs end_input =
140 let end_input = add_padding end_input in
141+ let inputs = inputs @ [ end_input ] in
142 let input =
143 List.fold_left
144 (fun acc s -> if String.length s <> 0 then acc ^ "\r\n" ^ s else acc)
145+ (List.hd inputs) (List.tl inputs) in
0146 let decode = decode input in
147 let encode = encode decode in
148 check_eq ~pp ~cmp:String.compare ~eq:String.equal input encode
149150let () =
151 register_printer () ;
152+ add_test ~name:"rfc2045: encode -> decode" [ list bytes_fixed_range ] e2d ;
153 add_test ~name:"rfc2045: decode -> encode"
154+ [ list (string_from_alpha 76); random_string_from_alpha 76 ]
155 d2e
+80-65
fuzz/fuzz_rfc4648.ml
···4 let escaped = function ' ' .. '~' as c -> String.make 1 c | _ -> "." in
5 Fmt.using escaped Fmt.string
67-let pp_scalar : type buffer.
08 get:(buffer -> int -> char) -> length:(buffer -> int) -> buffer Fmt.t =
9 fun ~get ~length ppf b ->
10 let l = length b in
···12 Fmt.pf ppf "%08x: " (i * 16) ;
13 let j = ref 0 in
14 while !j < 16 do
15- if (i * 16) + !j < l then
16- Fmt.pf ppf "%02x" (Char.code @@ get b ((i * 16) + !j))
17 else Fmt.pf ppf " " ;
18 if !j mod 2 <> 0 then Fmt.pf ppf " " ;
19 incr j
···21 Fmt.pf ppf " " ;
22 j := 0 ;
23 while !j < 16 do
24- if (i * 16) + !j < l then Fmt.pf ppf "%a" pp_chr (get b ((i * 16) + !j))
025 else Fmt.pf ppf " " ;
26 incr j
27 done ;
···3031let pp = pp_scalar ~get:String.get ~length:String.length
3233-let (<.>) f g x = f (g x)
3435let char_from_alphabet alphabet : string gen =
36- map [ range 64 ] (String.make 1 <.> Char.chr <.> Array.unsafe_get (Base64.alphabet alphabet))
03738let random_string_from_alphabet alphabet len : string gen =
39 let rec add_char_from_alphabet acc = function
40- | 0 -> acc
41- | n ->
42- add_char_from_alphabet
43- (concat_gen_list (const "") [ acc ; char_from_alphabet alphabet ])
44- (n - 1) in
45 add_char_from_alphabet (const "") len
4647let random_string_from_alphabet ~max alphabet =
48- dynamic_bind (range max)
49- @@ fun real_len ->
50- dynamic_bind (random_string_from_alphabet alphabet real_len)
51- @@ fun input ->
52- if real_len <= 1 then const (input, 0, real_len)
53- else dynamic_bind (range (real_len / 2))
54- @@ fun off -> map [ range (real_len - off) ] (fun len -> (input, off, len))
5556let encode_and_decode (input, off, len) =
57 match Base64.encode ~pad:true ~off ~len input with
58 | Error (`Msg err) -> fail err
59 | Ok result ->
60- match Base64.decode ~pad:true result with
61- | Error (`Msg err) -> fail err
62- | Ok result ->
63- check_eq ~pp ~cmp:String.compare ~eq:String.equal result (String.sub input off len)
06465let decode_and_encode (input, off, len) =
66 match Base64.decode ~pad:true ~off ~len input with
67- | Error (`Msg err) ->
68- fail err
69 | Ok result ->
70- match Base64.encode ~pad:true result with
71- | Error (`Msg err) -> fail err
72- | Ok result ->
73- check_eq ~pp:Fmt.string ~cmp:String.compare ~eq:String.equal result (String.sub input off len)
07475-let (//) x y =
76 if y < 1 then raise Division_by_zero ;
77 if x > 0 then 1 + ((x - 1) / y) else 0
78-[@@inline]
7980let canonic alphabet =
81 let dmap = Array.make 256 (-1) in
82- Array.iteri (fun i x -> Array.set dmap x i) (Base64.alphabet alphabet) ;
83 fun (input, off, len) ->
84 let real_len = String.length input in
85 let input_len = len in
86- let normalized_len = (input_len // 4) * 4 in
87- if normalized_len = input_len then (input, off, input_len)
88- else if normalized_len - input_len = 3 then (input, off, input_len - 1)
89- else begin
0090 let remainder_len = normalized_len - input_len in
91- let last = String.get input (off + input_len - 1) in
92 let output = Bytes.make (max real_len (off + normalized_len)) '=' in
9394- Bytes.blit_string input 0 output 0 (off + input_len);
95 if off + normalized_len < real_len
96- then Bytes.blit_string input (off + normalized_len) output (off + normalized_len) (real_len - (off + normalized_len)) ;
0009798- let mask = match remainder_len with
99- | 1 -> 0x3c
100- | 2 -> 0x30
101- | _ -> assert false in
102- let decoded = Array.get dmap (Char.code last) in
103- let canonic = (decoded land mask) in
104- let encoded = Array.get (Base64.alphabet alphabet) canonic in
105 Bytes.set output (off + input_len - 1) (Char.chr encoded) ;
106 (Bytes.unsafe_to_string output, off, normalized_len)
107- end
108109let isomorphism0 (input, off, len) =
110 (* x0 = decode(input) && x1 = decode(encode(x0)) && x0 = x1 *)
111 match Base64.decode ~pad:false ~off ~len input with
112- | Error (`Msg err) ->
113- fail err
114- | Ok result0 ->
115 let result1 = Base64.encode_exn result0 in
116 match Base64.decode ~pad:true result1 with
117- | Error (`Msg err) ->
118- fail err
119 | Ok result2 ->
120- check_eq ~pp ~cmp:String.compare ~eq:String.equal result0 result2
121122let isomorphism1 (input, off, len) =
123 let result0 = Base64.encode_exn ~off ~len input in
···125 | Error (`Msg err) -> fail err
126 | Ok result1 ->
127 let result2 = Base64.encode_exn result1 in
128- check_eq ~pp:Fmt.string ~cmp:String.compare ~eq:String.equal result0 result2
0129130let bytes_and_range : (string * int * int) gen =
131- dynamic_bind bytes
132- @@ fun t ->
133 let real_length = String.length t in
134 if real_length <= 1
135 then const (t, 0, real_length)
136- else dynamic_bind (range (real_length / 2))
137- @@ fun off ->
138 map [ range (real_length - off) ] (fun len -> (t, off, len))
139140let range_of_max max : (int * int) gen =
141- dynamic_bind (range (max / 2))
142- @@ fun off -> map [ range (max - off) ] (fun len -> (off, len))
143144let failf fmt = Fmt.kstrf fail fmt
145146let no_exception pad off len input =
147- try let _ = Base64.decode ?pad ?off ?len ~alphabet:Base64.default_alphabet input in ()
000148 with exn -> failf "decode fails with: %s." (Printexc.to_string exn)
149150let () =
151- add_test ~name:"rfc4648: encode -> decode" [ bytes_and_range ] encode_and_decode ;
152- add_test ~name:"rfc4648: decode -> encode" [ random_string_from_alphabet ~max:1000 Base64.default_alphabet ] (decode_and_encode <.> canonic Base64.default_alphabet) ;
153- add_test ~name:"rfc4648: x = decode(encode(x))" [ random_string_from_alphabet ~max:1000 Base64.default_alphabet ] isomorphism0 ;
154- add_test ~name:"rfc4648: x = encode(decode(x))" [ bytes_and_range ] isomorphism1 ;
155- add_test ~name:"rfc4648: no exception leak" [ option bool; option int; option int; bytes ] no_exception
00000000
···4 let escaped = function ' ' .. '~' as c -> String.make 1 c | _ -> "." in
5 Fmt.using escaped Fmt.string
67+let pp_scalar :
8+ type buffer.
9 get:(buffer -> int -> char) -> length:(buffer -> int) -> buffer Fmt.t =
10 fun ~get ~length ppf b ->
11 let l = length b in
···13 Fmt.pf ppf "%08x: " (i * 16) ;
14 let j = ref 0 in
15 while !j < 16 do
16+ if (i * 16) + !j < l
17+ then Fmt.pf ppf "%02x" (Char.code @@ get b ((i * 16) + !j))
18 else Fmt.pf ppf " " ;
19 if !j mod 2 <> 0 then Fmt.pf ppf " " ;
20 incr j
···22 Fmt.pf ppf " " ;
23 j := 0 ;
24 while !j < 16 do
25+ if (i * 16) + !j < l
26+ then Fmt.pf ppf "%a" pp_chr (get b ((i * 16) + !j))
27 else Fmt.pf ppf " " ;
28 incr j
29 done ;
···3233let pp = pp_scalar ~get:String.get ~length:String.length
3435+let ( <.> ) f g x = f (g x)
3637let char_from_alphabet alphabet : string gen =
38+ map [ range 64 ]
39+ (String.make 1 <.> Char.chr <.> Array.unsafe_get (Base64.alphabet alphabet))
4041let random_string_from_alphabet alphabet len : string gen =
42 let rec add_char_from_alphabet acc = function
43+ | 0 -> acc
44+ | n ->
45+ add_char_from_alphabet
46+ (concat_gen_list (const "") [ acc; char_from_alphabet alphabet ])
47+ (n - 1) in
48 add_char_from_alphabet (const "") len
4950let random_string_from_alphabet ~max alphabet =
51+ dynamic_bind (range max) @@ fun real_len ->
52+ dynamic_bind (random_string_from_alphabet alphabet real_len) @@ fun input ->
53+ if real_len <= 1
54+ then const (input, 0, real_len)
55+ else
56+ dynamic_bind (range (real_len / 2)) @@ fun off ->
57+ map [ range (real_len - off) ] (fun len -> (input, off, len))
5859let encode_and_decode (input, off, len) =
60 match Base64.encode ~pad:true ~off ~len input with
61 | Error (`Msg err) -> fail err
62 | Ok result ->
63+ match Base64.decode ~pad:true result with
64+ | Error (`Msg err) -> fail err
65+ | Ok result ->
66+ check_eq ~pp ~cmp:String.compare ~eq:String.equal result
67+ (String.sub input off len)
6869let decode_and_encode (input, off, len) =
70 match Base64.decode ~pad:true ~off ~len input with
71+ | Error (`Msg err) -> fail err
072 | Ok result ->
73+ match Base64.encode ~pad:true result with
74+ | Error (`Msg err) -> fail err
75+ | Ok result ->
76+ check_eq ~pp:Fmt.string ~cmp:String.compare ~eq:String.equal result
77+ (String.sub input off len)
7879+let ( // ) x y =
80 if y < 1 then raise Division_by_zero ;
81 if x > 0 then 1 + ((x - 1) / y) else 0
82+ [@@inline]
8384let canonic alphabet =
85 let dmap = Array.make 256 (-1) in
86+ Array.iteri (fun i x -> dmap.(x) <- i) (Base64.alphabet alphabet) ;
87 fun (input, off, len) ->
88 let real_len = String.length input in
89 let input_len = len in
90+ let normalized_len = input_len // 4 * 4 in
91+ if normalized_len = input_len
92+ then (input, off, input_len)
93+ else if normalized_len - input_len = 3
94+ then (input, off, input_len - 1)
95+ else
96 let remainder_len = normalized_len - input_len in
97+ let last = input.[off + input_len - 1] in
98 let output = Bytes.make (max real_len (off + normalized_len)) '=' in
99100+ Bytes.blit_string input 0 output 0 (off + input_len) ;
101 if off + normalized_len < real_len
102+ then
103+ Bytes.blit_string input (off + normalized_len) output
104+ (off + normalized_len)
105+ (real_len - (off + normalized_len)) ;
106107+ let mask =
108+ match remainder_len with 1 -> 0x3c | 2 -> 0x30 | _ -> assert false in
109+ let decoded = dmap.(Char.code last) in
110+ let canonic = decoded land mask in
111+ let encoded = (Base64.alphabet alphabet).(canonic) in
00112 Bytes.set output (off + input_len - 1) (Char.chr encoded) ;
113 (Bytes.unsafe_to_string output, off, normalized_len)
0114115let isomorphism0 (input, off, len) =
116 (* x0 = decode(input) && x1 = decode(encode(x0)) && x0 = x1 *)
117 match Base64.decode ~pad:false ~off ~len input with
118+ | Error (`Msg err) -> fail err
119+ | Ok result0 -> (
0120 let result1 = Base64.encode_exn result0 in
121 match Base64.decode ~pad:true result1 with
122+ | Error (`Msg err) -> fail err
0123 | Ok result2 ->
124+ check_eq ~pp ~cmp:String.compare ~eq:String.equal result0 result2)
125126let isomorphism1 (input, off, len) =
127 let result0 = Base64.encode_exn ~off ~len input in
···129 | Error (`Msg err) -> fail err
130 | Ok result1 ->
131 let result2 = Base64.encode_exn result1 in
132+ check_eq ~pp:Fmt.string ~cmp:String.compare ~eq:String.equal result0
133+ result2
134135let bytes_and_range : (string * int * int) gen =
136+ dynamic_bind bytes @@ fun t ->
0137 let real_length = String.length t in
138 if real_length <= 1
139 then const (t, 0, real_length)
140+ else
141+ dynamic_bind (range (real_length / 2)) @@ fun off ->
142 map [ range (real_length - off) ] (fun len -> (t, off, len))
143144let range_of_max max : (int * int) gen =
145+ dynamic_bind (range (max / 2)) @@ fun off ->
146+ map [ range (max - off) ] (fun len -> (off, len))
147148let failf fmt = Fmt.kstrf fail fmt
149150let no_exception pad off len input =
151+ try
152+ let _ =
153+ Base64.decode ?pad ?off ?len ~alphabet:Base64.default_alphabet input in
154+ ()
155 with exn -> failf "decode fails with: %s." (Printexc.to_string exn)
156157let () =
158+ add_test ~name:"rfc4648: encode -> decode" [ bytes_and_range ]
159+ encode_and_decode ;
160+ add_test ~name:"rfc4648: decode -> encode"
161+ [ random_string_from_alphabet ~max:1000 Base64.default_alphabet ]
162+ (decode_and_encode <.> canonic Base64.default_alphabet) ;
163+ add_test ~name:"rfc4648: x = decode(encode(x))"
164+ [ random_string_from_alphabet ~max:1000 Base64.default_alphabet ]
165+ isomorphism0 ;
166+ add_test ~name:"rfc4648: x = encode(decode(x))" [ bytes_and_range ]
167+ isomorphism1 ;
168+ add_test ~name:"rfc4648: no exception leak"
169+ [ option bool; option int; option int; bytes ]
170+ no_exception
+182-155
src/base64.ml
···19 *
20 *)
2122-type alphabet =
23- { emap : int array
24- ; dmap : int array }
2526type sub = string * int * int
2728-let (//) x y =
29 if y < 1 then raise Division_by_zero ;
30 if x > 0 then 1 + ((x - 1) / y) else 0
31-[@@inline]
3233let unsafe_get_uint8 t off = Char.code (String.unsafe_get t off)
034let unsafe_set_uint8 t off v = Bytes.unsafe_set t off (Char.chr v)
035let unsafe_set_uint16 = Unsafe.unsafe_set_uint16
3637-external unsafe_get_uint16 : string -> int -> int = "%caml_string_get16u" [@@noalloc]
0038external swap16 : int -> int = "%bswap16" [@@noalloc]
3940-let none = (-1)
4142(* We mostly want to have an optional array for [dmap] (e.g. [int option
43 array]). So we consider the [none] value as [-1]. *)
4445let make_alphabet alphabet =
46- if String.length alphabet <> 64 then invalid_arg "Length of alphabet must be 64" ;
47- if String.contains alphabet '=' then invalid_arg "Alphabet can not contain padding character" ;
48- let emap = Array.init (String.length alphabet) (fun i -> Char.code (String.get alphabet i)) in
00049 let dmap = Array.make 256 none in
50- String.iteri (fun idx chr -> Array.set dmap (Char.code chr) idx) alphabet ;
51- { emap; dmap; }
5253let length_alphabet { emap; _ } = Array.length emap
054let alphabet { emap; _ } = emap
5556-let default_alphabet = make_alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
57-let uri_safe_alphabet = make_alphabet "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
000005859let unsafe_set_be_uint16 =
60 if Sys.big_endian
···65 can raise and avoid appearance of unknown exceptions like an ex-nihilo
66 magic rabbit (or magic money?). *)
67exception Out_of_bounds
068exception Too_much_input
6970let get_uint8 t off =
···76let error_msgf fmt = Format.ksprintf (fun err -> Error (`Msg err)) fmt
7778let encode_sub pad { emap; _ } ?(off = 0) ?len input =
79- let len = match len with
80- | Some len -> len
81- | None -> String.length input - off in
8283 if len < 0 || off < 0 || off > String.length input - len
84 then error_msgf "Invalid bounds"
85 else
86- let n = len in
87- let n' = n // 3 * 4 in
88- let res = Bytes.create n' in
8990- let emap i = Array.unsafe_get emap i in
9192- let emit b1 b2 b3 i =
93- unsafe_set_be_uint16 res i
94- ((emap (b1 lsr 2 land 0x3f) lsl 8)
95- lor (emap ((b1 lsl 4) lor (b2 lsr 4) land 0x3f))) ;
96- unsafe_set_be_uint16 res (i + 2)
97- ((emap ((b2 lsl 2) lor (b3 lsr 6) land 0x3f) lsl 8)
98- lor (emap (b3 land 0x3f))) in
000000000000000000099100- let rec enc j i =
101- if i = n then ()
102- else if i = n - 1
103- then emit (unsafe_get_uint8 input (off + i)) 0 0 j
104- else if i = n - 2
105- then emit (unsafe_get_uint8 input (off + i)) (unsafe_get_uint8 input (off + i + 1)) 0 j
106- else
107- (emit
108- (unsafe_get_uint8 input (off + i))
109- (unsafe_get_uint8 input (off + i + 1))
110- (unsafe_get_uint8 input (off + i + 2))
111- j ;
112- enc (j + 4) (i + 3)) in
113114- let rec unsafe_fix = function
115- | 0 -> ()
116- | i -> unsafe_set_uint8 res (n' - i) padding ; unsafe_fix (i - 1) in
117118- enc 0 0 ;
119120- let pad_to_write = ((3 - n mod 3) mod 3) in
0000121122- if pad
123- then begin unsafe_fix pad_to_write ; Ok (Bytes.unsafe_to_string res, 0, n') end
124- else Ok (Bytes.unsafe_to_string res, 0, (n' - pad_to_write))
125(* [pad = false], we don't want to write them. *)
126127let encode ?(pad = true) ?(alphabet = default_alphabet) ?off ?len input =
···143 | Error (`Msg err) -> invalid_arg err
144145let decode_sub ?(pad = true) { dmap; _ } ?(off = 0) ?len input =
146- let len = match len with
147- | Some len -> len
148- | None -> String.length input - off in
149150 if len < 0 || off < 0 || off > String.length input - len
151 then error_msgf "Invalid bounds"
152 else
0000153154- let n = (len // 4) * 4 in
155- let n' = (n // 4) * 3 in
156- let res = Bytes.create n' in
157- let invalid_pad_overflow = pad in
00000158159- let get_uint8_or_padding =
160- if pad then (fun t i -> if i >= len then raise Out_of_bounds ; get_uint8 t (off + i) )
161- else (fun t i -> try if i < len then get_uint8 t (off + i) else padding with Out_of_bounds -> padding ) in
0000162163- let set_be_uint16 t off v =
164- (* can not write 2 bytes. *)
165- if off < 0 || off + 1 > Bytes.length t then ()
166- (* can not write 1 byte but can write 1 byte *)
167- else if off < 0 || off + 2 > Bytes.length t then unsafe_set_uint8 t off (v lsr 8)
168- (* can write 2 bytes. *)
169- else unsafe_set_be_uint16 t off v in
170171- let set_uint8 t off v =
172- if off < 0 || off >= Bytes.length t then ()
173- else unsafe_set_uint8 t off v in
0174175- let emit a b c d j =
176- let x = (a lsl 18) lor (b lsl 12) lor (c lsl 6) lor d in
177- set_be_uint16 res j (x lsr 8) ;
178- set_uint8 res (j + 2) (x land 0xff) in
179180- let dmap i =
181- let x = Array.unsafe_get dmap i in
182- if x = none then raise Not_found ; x in
183-184- let only_padding pad idx =
185-186- (* because we round length of [res] to the upper bound of how many
187- characters we should have from [input], we got at this stage only padding
188- characters and we need to delete them, so for each [====], we delete 3
189- bytes. *)
190-191- let pad = ref (pad + 3) in
192- let idx = ref idx in
193194- while !idx + 4 < len do
195- (* use [unsafe_get_uint16] instead [unsafe_get_uint32] to avoid allocation
196- of [int32]. Of course, [3d3d3d3d] is [====]. *)
197- if unsafe_get_uint16 input (off + !idx) <> 0x3d3d
198- || unsafe_get_uint16 input (off + !idx + 2) <> 0x3d3d
199- then raise Not_found ;
200- (* We got something bad, should be a valid character according to
201- [alphabet] but outside the scope. *)
202203- idx := !idx + 4 ;
204- pad := !pad + 3 ;
205- done ;
206- while !idx < len do
207- if unsafe_get_uint8 input (off + !idx) <> padding
208- then raise Not_found ;
0209210- incr idx ;
211- done ; !pad in
0212213- let rec dec j i =
214- if i = n then 0
215- else begin
216- let (d, pad) =
217- let x = get_uint8_or_padding input (i + 3) in
218- try (dmap x, 0) with Not_found when x = padding -> (0, 1) in
219- (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
220- let (c, pad) =
221- let x = get_uint8_or_padding input (i + 2) in
222- try (dmap x, pad) with Not_found when x = padding && pad = 1 -> (0, 2) in
223- (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
224- let (b, pad) =
225- let x = get_uint8_or_padding input (i + 1) in
226- try (dmap x, pad) with Not_found when x = padding && pad = 2 -> (0, 3) in
227- (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
228- let (a, pad) =
229- let x = get_uint8_or_padding input i in
230- try (dmap x, pad) with Not_found when x = padding && pad = 3 -> (0, 4) in
231- (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
000232233- emit a b c d j ;
0234235- if i + 4 = n
236- (* end of input in anyway *)
237- then match pad with
238- | 0 ->
239- 0
240- | 4 ->
241- (* assert (invalid_pad_overflow = false) ; *)
242- 3
243- (* [get_uint8] lies and if we get [4], that mean we got one or more (at
244- most 4) padding character. In this situation, because we round length
245- of [res] (see [n // 4]), we need to delete 3 bytes. *)
246- | pad ->
247- pad
248- else match pad with
249- | 0 -> dec (j + 3) (i + 4)
250- | 4 ->
251- (* assert (invalid_pad_overflow = false) ; *)
252- only_padding 3 (i + 4)
253- (* Same situation than above but we should get only more padding
254- characters then. *)
255- | pad ->
256- if invalid_pad_overflow = true then raise Too_much_input ;
257- only_padding pad (i + 4) end in
258259- match dec 0 0 with
260- | 0 -> Ok (Bytes.unsafe_to_string res, 0, n')
261- | pad -> Ok (Bytes.unsafe_to_string res, 0, (n' - pad))
262- | exception Out_of_bounds -> error_msgf "Wrong padding"
263- (* appear only when [pad = true] and when length of input is not a multiple of 4. *)
264- | exception Not_found ->
265- (* appear when one character of [input] ∉ [alphabet] and this character <> '=' *)
266- error_msgf "Malformed input"
267- | exception Too_much_input ->
268- error_msgf "Too much input"
269270let decode ?pad ?(alphabet = default_alphabet) ?off ?len input =
271 match decode_sub ?pad alphabet ?off ?len input with
···19 *
20 *)
2122+type alphabet = { emap : int array; dmap : int array }
002324type sub = string * int * int
2526+let ( // ) x y =
27 if y < 1 then raise Division_by_zero ;
28 if x > 0 then 1 + ((x - 1) / y) else 0
29+ [@@inline]
3031let unsafe_get_uint8 t off = Char.code (String.unsafe_get t off)
32+33let unsafe_set_uint8 t off v = Bytes.unsafe_set t off (Char.chr v)
34+35let unsafe_set_uint16 = Unsafe.unsafe_set_uint16
3637+external unsafe_get_uint16 : string -> int -> int = "%caml_string_get16u"
38+ [@@noalloc]
39+40external swap16 : int -> int = "%bswap16" [@@noalloc]
4142+let none = -1
4344(* We mostly want to have an optional array for [dmap] (e.g. [int option
45 array]). So we consider the [none] value as [-1]. *)
4647let make_alphabet alphabet =
48+ if String.length alphabet <> 64
49+ then invalid_arg "Length of alphabet must be 64" ;
50+ if String.contains alphabet '='
51+ then invalid_arg "Alphabet can not contain padding character" ;
52+ let emap =
53+ Array.init (String.length alphabet) (fun i -> Char.code alphabet.[i]) in
54 let dmap = Array.make 256 none in
55+ String.iteri (fun idx chr -> dmap.(Char.code chr) <- idx) alphabet ;
56+ { emap; dmap }
5758let length_alphabet { emap; _ } = Array.length emap
59+60let alphabet { emap; _ } = emap
6162+let default_alphabet =
63+ make_alphabet
64+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
65+66+let uri_safe_alphabet =
67+ make_alphabet
68+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
6970let unsafe_set_be_uint16 =
71 if Sys.big_endian
···76 can raise and avoid appearance of unknown exceptions like an ex-nihilo
77 magic rabbit (or magic money?). *)
78exception Out_of_bounds
79+80exception Too_much_input
8182let get_uint8 t off =
···88let error_msgf fmt = Format.ksprintf (fun err -> Error (`Msg err)) fmt
8990let encode_sub pad { emap; _ } ?(off = 0) ?len input =
91+ let len =
92+ match len with Some len -> len | None -> String.length input - off in
09394 if len < 0 || off < 0 || off > String.length input - len
95 then error_msgf "Invalid bounds"
96 else
97+ let n = len in
98+ let n' = n // 3 * 4 in
99+ let res = Bytes.create n' in
100101+ let emap i = Array.unsafe_get emap i in
102103+ let emit b1 b2 b3 i =
104+ unsafe_set_be_uint16 res i
105+ ((emap ((b1 lsr 2) land 0x3f) lsl 8)
106+ lor emap ((b1 lsl 4) lor (b2 lsr 4) land 0x3f)) ;
107+ unsafe_set_be_uint16 res (i + 2)
108+ ((emap ((b2 lsl 2) lor (b3 lsr 6) land 0x3f) lsl 8)
109+ lor emap (b3 land 0x3f)) in
110+111+ let rec enc j i =
112+ if i = n
113+ then ()
114+ else if i = n - 1
115+ then emit (unsafe_get_uint8 input (off + i)) 0 0 j
116+ else if i = n - 2
117+ then
118+ emit
119+ (unsafe_get_uint8 input (off + i))
120+ (unsafe_get_uint8 input (off + i + 1))
121+ 0 j
122+ else (
123+ emit
124+ (unsafe_get_uint8 input (off + i))
125+ (unsafe_get_uint8 input (off + i + 1))
126+ (unsafe_get_uint8 input (off + i + 2))
127+ j ;
128+ enc (j + 4) (i + 3)) in
129130+ let rec unsafe_fix = function
131+ | 0 -> ()
132+ | i ->
133+ unsafe_set_uint8 res (n' - i) padding ;
134+ unsafe_fix (i - 1) in
00000000135136+ enc 0 0 ;
00137138+ let pad_to_write = (3 - (n mod 3)) mod 3 in
139140+ if pad
141+ then (
142+ unsafe_fix pad_to_write ;
143+ Ok (Bytes.unsafe_to_string res, 0, n'))
144+ else Ok (Bytes.unsafe_to_string res, 0, n' - pad_to_write)
145000146(* [pad = false], we don't want to write them. *)
147148let encode ?(pad = true) ?(alphabet = default_alphabet) ?off ?len input =
···164 | Error (`Msg err) -> invalid_arg err
165166let decode_sub ?(pad = true) { dmap; _ } ?(off = 0) ?len input =
167+ let len =
168+ match len with Some len -> len | None -> String.length input - off in
0169170 if len < 0 || off < 0 || off > String.length input - len
171 then error_msgf "Invalid bounds"
172 else
173+ let n = len // 4 * 4 in
174+ let n' = n // 4 * 3 in
175+ let res = Bytes.create n' in
176+ let invalid_pad_overflow = pad in
177178+ let get_uint8_or_padding =
179+ if pad
180+ then (fun t i ->
181+ if i >= len then raise Out_of_bounds ;
182+ get_uint8 t (off + i))
183+ else
184+ fun t i ->
185+ try if i < len then get_uint8 t (off + i) else padding
186+ with Out_of_bounds -> padding in
187188+ let set_be_uint16 t off v =
189+ (* can not write 2 bytes. *)
190+ if off < 0 || off + 1 > Bytes.length t
191+ then () (* can not write 1 byte but can write 1 byte *)
192+ else if off < 0 || off + 2 > Bytes.length t
193+ then unsafe_set_uint8 t off (v lsr 8) (* can write 2 bytes. *)
194+ else unsafe_set_be_uint16 t off v in
195196+ let set_uint8 t off v =
197+ if off < 0 || off >= Bytes.length t then () else unsafe_set_uint8 t off v
198+ in
0000199200+ let emit a b c d j =
201+ let x = (a lsl 18) lor (b lsl 12) lor (c lsl 6) lor d in
202+ set_be_uint16 res j (x lsr 8) ;
203+ set_uint8 res (j + 2) (x land 0xff) in
204205+ let dmap i =
206+ let x = Array.unsafe_get dmap i in
207+ if x = none then raise Not_found ;
208+ x in
209210+ let only_padding pad idx =
211+ (* because we round length of [res] to the upper bound of how many
212+ characters we should have from [input], we got at this stage only padding
213+ characters and we need to delete them, so for each [====], we delete 3
214+ bytes. *)
215+ let pad = ref (pad + 3) in
216+ let idx = ref idx in
000000217218+ while !idx + 4 < len do
219+ (* use [unsafe_get_uint16] instead [unsafe_get_uint32] to avoid allocation
220+ of [int32]. Of course, [3d3d3d3d] is [====]. *)
221+ if unsafe_get_uint16 input (off + !idx) <> 0x3d3d
222+ || unsafe_get_uint16 input (off + !idx + 2) <> 0x3d3d
223+ then raise Not_found ;
00224225+ (* We got something bad, should be a valid character according to
226+ [alphabet] but outside the scope. *)
227+ idx := !idx + 4 ;
228+ pad := !pad + 3
229+ done ;
230+ while !idx < len do
231+ if unsafe_get_uint8 input (off + !idx) <> padding then raise Not_found ;
232233+ incr idx
234+ done ;
235+ !pad in
236237+ let rec dec j i =
238+ if i = n
239+ then 0
240+ else
241+ let d, pad =
242+ let x = get_uint8_or_padding input (i + 3) in
243+ try (dmap x, 0) with Not_found when x = padding -> (0, 1) in
244+ (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
245+ let c, pad =
246+ let x = get_uint8_or_padding input (i + 2) in
247+ try (dmap x, pad)
248+ with Not_found when x = padding && pad = 1 -> (0, 2) in
249+ (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
250+ let b, pad =
251+ let x = get_uint8_or_padding input (i + 1) in
252+ try (dmap x, pad)
253+ with Not_found when x = padding && pad = 2 -> (0, 3) in
254+ (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
255+ let a, pad =
256+ let x = get_uint8_or_padding input i in
257+ try (dmap x, pad)
258+ with Not_found when x = padding && pad = 3 -> (0, 4) in
259260+ (* [Not_found] iff [x ∉ alphabet and x <> '='] can leak. *)
261+ emit a b c d j ;
262263+ if i + 4 = n (* end of input in anyway *)
264+ then
265+ match pad with
266+ | 0 -> 0
267+ | 4 ->
268+ (* assert (invalid_pad_overflow = false) ; *)
269+ 3
270+ (* [get_uint8] lies and if we get [4], that mean we got one or more (at
271+ most 4) padding character. In this situation, because we round length
272+ of [res] (see [n // 4]), we need to delete 3 bytes. *)
273+ | pad -> pad
274+ else
275+ match pad with
276+ | 0 -> dec (j + 3) (i + 4)
277+ | 4 ->
278+ (* assert (invalid_pad_overflow = false) ; *)
279+ only_padding 3 (i + 4)
280+ (* Same situation than above but we should get only more padding
281+ characters then. *)
282+ | pad ->
283+ if invalid_pad_overflow = true then raise Too_much_input ;
284+ only_padding pad (i + 4) in
0285286+ match dec 0 0 with
287+ | 0 -> Ok (Bytes.unsafe_to_string res, 0, n')
288+ | pad -> Ok (Bytes.unsafe_to_string res, 0, n' - pad)
289+ | exception Out_of_bounds ->
290+ error_msgf "Wrong padding"
291+ (* appear only when [pad = true] and when length of input is not a multiple of 4. *)
292+ | exception Not_found ->
293+ (* appear when one character of [input] ∉ [alphabet] and this character <> '=' *)
294+ error_msgf "Malformed input"
295+ | exception Too_much_input -> error_msgf "Too much input"
296297let decode ?pad ?(alphabet = default_alphabet) ?off ?len input =
298 match decode_sub ?pad alphabet ?off ?len input with
+40-13
src/base64.mli
···48val alphabet : alphabet -> int array
49(** Returns the alphabet. *)
5051-val decode_exn : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> string
52-(** [decode_exn ?off ?len s] decodes [len] bytes (defaults to [String.length s -
53- off]) of the string [s] starting from [off] (defaults to [0]) that is encoded
54- in Base64 format. Will leave trailing NULLs on the string, padding it out to
55- a multiple of 3 characters. [alphabet] defaults to {!default_alphabet}. [pad
56- = true] specifies to check if [s] is padded or not, otherwise, it raises an
57- exception.
05859 Decoder can fail when character of [s] is not a part of [alphabet] or is not
60 [padding] character. If input is not padded correctly, decoder does the
···6263 @raise if Invalid_argument [s] is not a valid Base64 string. *)
6465-val decode_sub : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (sub, [> `Msg of string ]) result
00000066(** Same as {!decode_exn} but it returns a result type instead to raise an
67 exception. Then, it returns a {!sub} string. Decoded input [(str, off, len)]
68 will starting to [off] and will have [len] bytes - by this way, we ensure to
69 allocate only one time result. *)
7071-val decode : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (string, [> `Msg of string ]) result
72-(** Same as {!decode_exn}, but returns an explicit error message {!result} if it fails. *)
00000007374-val encode : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (string, [> `Msg of string]) result
00000075(** [encode s] encodes the string [s] into base64. If [pad] is false, no
76 trailing padding is added. [pad] defaults to [true], and [alphabet] to
77 {!default_alphabet}.
···83 trailing padding is added. [pad] defaults to [true], and [alphabet] to
84 {!default_alphabet}. *)
8586-val encode_sub : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> (sub, [> `Msg of string]) result
00000087(** Same as {!encode} but return a {!sub}-string instead a plain result. By this
88 way, we ensure to allocate only one time result. *)
8990-val encode_exn : ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> string
091(** Same as {!encode} but raises an invalid argument exception if we retrieve an
92 error. *)
···48val alphabet : alphabet -> int array
49(** Returns the alphabet. *)
5051+val decode_exn :
52+ ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> string
53+(** [decode_exn ?off ?len s] decodes [len] bytes (defaults to
54+ [String.length s - off]) of the string [s] starting from [off] (defaults to
55+ [0]) that is encoded in Base64 format. Will leave trailing NULLs on the
56+ string, padding it out to a multiple of 3 characters. [alphabet] defaults to
57+ {!default_alphabet}. [pad = true] specifies to check if [s] is padded or
58+ not, otherwise, it raises an exception.
5960 Decoder can fail when character of [s] is not a part of [alphabet] or is not
61 [padding] character. If input is not padded correctly, decoder does the
···6364 @raise if Invalid_argument [s] is not a valid Base64 string. *)
6566+val decode_sub :
67+ ?pad:bool ->
68+ ?alphabet:alphabet ->
69+ ?off:int ->
70+ ?len:int ->
71+ string ->
72+ (sub, [> `Msg of string ]) result
73(** Same as {!decode_exn} but it returns a result type instead to raise an
74 exception. Then, it returns a {!sub} string. Decoded input [(str, off, len)]
75 will starting to [off] and will have [len] bytes - by this way, we ensure to
76 allocate only one time result. *)
7778+val decode :
79+ ?pad:bool ->
80+ ?alphabet:alphabet ->
81+ ?off:int ->
82+ ?len:int ->
83+ string ->
84+ (string, [> `Msg of string ]) result
85+(** Same as {!decode_exn}, but returns an explicit error message {!result} if it
86+ fails. *)
8788+val encode :
89+ ?pad:bool ->
90+ ?alphabet:alphabet ->
91+ ?off:int ->
92+ ?len:int ->
93+ string ->
94+ (string, [> `Msg of string ]) result
95(** [encode s] encodes the string [s] into base64. If [pad] is false, no
96 trailing padding is added. [pad] defaults to [true], and [alphabet] to
97 {!default_alphabet}.
···103 trailing padding is added. [pad] defaults to [true], and [alphabet] to
104 {!default_alphabet}. *)
105106+val encode_sub :
107+ ?pad:bool ->
108+ ?alphabet:alphabet ->
109+ ?off:int ->
110+ ?len:int ->
111+ string ->
112+ (sub, [> `Msg of string ]) result
113(** Same as {!encode} but return a {!sub}-string instead a plain result. By this
114 way, we ensure to allocate only one time result. *)
115116+val encode_exn :
117+ ?pad:bool -> ?alphabet:alphabet -> ?off:int -> ?len:int -> string -> string
118(** Same as {!encode} but raises an invalid argument exception if we retrieve an
119 error. *)
+161-135
src/base64_rfc2045.ml
···19 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
2021let io_buffer_size = 65536
022let invalid_arg fmt = Format.ksprintf (fun s -> invalid_arg s) fmt
2324let invalid_bounds off len =
25 invalid_arg "Invalid bounds (off: %d, len: %d)" off len
2627-let malformed chr =
28- `Malformed (String.make 1 chr)
2930let unsafe_byte source off pos = Bytes.unsafe_get source (off + pos)
031let unsafe_blit = Bytes.unsafe_blit
032let unsafe_chr = Char.unsafe_chr
033let unsafe_set_chr source off chr = Bytes.unsafe_set source off chr
3435-type state = {quantum: int; size: int; buffer: Bytes.t}
003637-let continue state (quantum, size) = `Continue {state with quantum; size}
38-let flush state = `Flush {state with quantum= 0; size= 0}
3940let table =
41 "\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\062\255\255\255\063\052\053\054\055\056\057\058\059\060\061\255\255\255\255\255\255\255\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014\015\016\017\018\019\020\021\022\023\024\025\255\255\255\255\255\255\026\027\028\029\030\031\032\033\034\035\036\037\038\039\040\041\042\043\044\045\046\047\048\049\050\051\255\255\255\255\255"
4243-let r_repr ({quantum; size; _} as state) chr =
44 (* assert (0 <= off && 0 <= len && off + len <= String.length source); *)
45 (* assert (len >= 1); *)
46 let code = Char.code table.[Char.code chr] in
···56 flush state
57 | _ -> malformed chr
5859-type src = [`Channel of in_channel | `String of string | `Manual]
6061type decode =
62- [`Await | `End | `Wrong_padding | `Malformed of string | `Flush of string]
6364type input =
65- [`Line_break | `Wsp | `Padding | `Malformed of string | `Flush of state]
6667-type decoder =
68- { src: src
69- ; mutable i: Bytes.t
70- ; mutable i_off: int
71- ; mutable i_pos: int
72- ; mutable i_len: int
73- ; mutable s: state
74- ; mutable padding: int
75- ; mutable unsafe: bool
76- ; mutable byte_count: int
77- ; mutable limit_count: int
78- ; mutable pp: decoder -> input -> decode
79- ; mutable k: decoder -> decode }
08081let i_rem decoder = decoder.i_len - decoder.i_pos + 1
82···87 decoder.i_len <- min_int
8889let src decoder source off len =
90- if off < 0 || len < 0 || off + len > Bytes.length source then
91- invalid_bounds off len
92- else if len = 0 then end_of_input decoder
093 else (
94 decoder.i <- source ;
95 decoder.i_off <- off ;
96 decoder.i_pos <- 0 ;
97- decoder.i_len <- len - 1 )
9899let refill k decoder =
100 match decoder.src with
101 | `Manual ->
102 decoder.k <- k ;
103 `Await
104- | `String _ -> end_of_input decoder ; k decoder
00105 | `Channel ic ->
106 let len = input ic decoder.i 0 (Bytes.length decoder.i) in
107 src decoder decoder.i 0 len ;
108 k decoder
109110let dangerous decoder v = decoder.unsafe <- v
0111let reset decoder = decoder.limit_count <- 0
112113let ret k v byte_count decoder =
···117 if decoder.limit_count > 78 then dangerous decoder true ;
118 decoder.pp decoder v
119120-type flush_and_malformed = [`Flush of state | `Malformed of string]
121122-let padding {size; _} padding =
123 match (size, padding) with
124 | 0, 0 -> true
125 | 1, _ -> false
···127 | 3, 1 -> true
128 | _ -> false
129130-let t_flush {quantum; size; buffer} =
131 match size with
132- | 0 | 1 -> `Flush {quantum; size; buffer= Bytes.empty}
133 | 2 ->
134 let quantum = quantum lsr 4 in
135 `Flush
136- { quantum
137- ; size
138- ; buffer= Bytes.make 1 (unsafe_chr (quantum land 255)) }
139 | 3 ->
140 let quantum = quantum lsr 2 in
141 unsafe_set_chr buffer 0 (unsafe_chr ((quantum lsr 8) land 255)) ;
142 unsafe_set_chr buffer 1 (unsafe_chr (quantum land 255)) ;
143- `Flush {quantum; size; buffer= Bytes.sub buffer 0 2}
144- | _ -> assert false (* this branch is impossible, size can only ever be in the range [0..3]. *)
00145146let wrong_padding decoder =
147 let k _ = `End in
148- decoder.k <- k ; `Wrong_padding
0149150let rec t_decode_base64 chr decoder =
151- if decoder.padding = 0 then
0152 let rec go pos = function
153 | `Continue state ->
154 if decoder.i_len - (decoder.i_pos + pos) + 1 > 0
155 then (
156 match unsafe_byte decoder.i decoder.i_off (decoder.i_pos + pos) with
157- | ('A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '+' | '/') as chr -> go (succ pos) (r_repr state chr)
0158 | '=' ->
159 decoder.padding <- decoder.padding + 1 ;
160 decoder.i_pos <- decoder.i_pos + pos + 1 ;
161 decoder.s <- state ;
162- ret decode_base64 `Padding (pos+1) decoder
163 | ' ' | '\t' ->
164 decoder.i_pos <- decoder.i_pos + pos + 1 ;
165 decoder.s <- state ;
···171 | chr ->
172 decoder.i_pos <- decoder.i_pos + pos + 1 ;
173 decoder.s <- state ;
174- ret decode_base64 (malformed chr) (pos+1) decoder
175- ) else (
176 decoder.i_pos <- decoder.i_pos + pos ;
177 decoder.byte_count <- decoder.byte_count + pos ;
178 decoder.limit_count <- decoder.limit_count + pos ;
179 decoder.s <- state ;
180- refill decode_base64 decoder )
181 | #flush_and_malformed as v ->
182 decoder.i_pos <- decoder.i_pos + pos ;
183- ret decode_base64 v pos decoder
184- in
185 go 1 (r_repr decoder.s chr)
186 else (
187 decoder.i_pos <- decoder.i_pos + 1 ;
···189190and decode_base64_lf_after_cr decoder =
191 let rem = i_rem decoder in
192- if rem < 0 then
193- ret decode_base64 (malformed '\r') 1 decoder
194- else if rem = 0 then refill decode_base64_lf_after_cr decoder
0195 else
196 match unsafe_byte decoder.i decoder.i_off decoder.i_pos with
197 | '\n' ->
198- decoder.i_pos <- decoder.i_pos + 1 ;
199- ret decode_base64 `Line_break 2 decoder
200- | _ ->
201- ret decode_base64 (malformed '\r') 1 decoder
202203and decode_base64 decoder =
204 let rem = i_rem decoder in
205- if rem <= 0 then
206- if rem < 0 then
00207 ret
208 (fun decoder ->
209- if padding decoder.s decoder.padding then `End else wrong_padding decoder )
00210 (t_flush decoder.s) 0 decoder
211 else refill decode_base64 decoder
212 else
···228 ret decode_base64 (malformed chr) 1 decoder
229230let pp_base64 decoder = function
231- | `Line_break -> reset decoder ; decoder.k decoder
00232 | `Wsp | `Padding -> decoder.k decoder
233 | `Flush state ->
234 decoder.s <- state ;
···242 match src with
243 | `Manual -> (Bytes.empty, 0, 1, 0)
244 | `Channel _ -> (Bytes.create io_buffer_size, 0, 1, 0)
245- | `String s -> (Bytes.unsafe_of_string s, 0, 0, String.length s - 1)
246- in
247- { src
248- ; i_off
249- ; i_pos
250- ; i_len
251- ; i
252- ; s= {quantum= 0; size= 0; buffer= Bytes.create 3}
253- ; padding= 0
254- ; unsafe= false
255- ; byte_count= 0
256- ; limit_count= 0
257- ; pp
258- ; k }
0259260let decode decoder = decoder.k decoder
0261let decoder_byte_count decoder = decoder.byte_count
0262let decoder_src decoder = decoder.src
0263let decoder_dangerous decoder = decoder.unsafe
264265(* / *)
266267let invalid_encode () = invalid_arg "Expected `Await encode"
268269-type dst = [`Channel of out_channel | `Buffer of Buffer.t | `Manual]
270-type encode = [`Await | `End | `Char of char]
271272-type encoder =
273- { dst: dst
274- ; mutable o: Bytes.t
275- ; mutable o_off: int
276- ; mutable o_pos: int
277- ; mutable o_len: int
278- ; mutable c_col: int
279- ; i: Bytes.t
280- ; mutable s: int
281- ; t: Bytes.t
282- ; mutable t_pos: int
283- ; mutable t_len: int
284- ; mutable k: encoder -> encode -> [`Ok | `Partial] }
000285286let o_rem encoder = encoder.o_len - encoder.o_pos + 1
287288let dst encoder source off len =
289- if off < 0 || len < 0 || off + len > Bytes.length source then
290- invalid_bounds off len ;
291 encoder.o <- source ;
292 encoder.o_off <- off ;
293 encoder.o_pos <- 0 ;
···322 let blit encoder len =
323 unsafe_blit encoder.t encoder.t_pos encoder.o encoder.o_pos len ;
324 encoder.o_pos <- encoder.o_pos + len ;
325- encoder.t_pos <- encoder.t_pos + len
326- in
327 let rem = o_rem encoder in
328 let len = encoder.t_len - encoder.t_pos + 1 in
329- if rem < len then (
0330 blit encoder rem ;
331- flush (t_flush k) encoder )
332- else ( blit encoder len ; k encoder )
00333334let rec encode_line_break k encoder =
335 let rem = o_rem encoder in
336 let s, j, k =
337- if rem < 2 then (
0338 t_range encoder 2 ;
339- (encoder.t, 0, t_flush k) )
340 else
341 let j = encoder.o_pos in
342 encoder.o_pos <- encoder.o_pos + 2 ;
343- (encoder.o, encoder.o_off + j, k)
344- in
345 unsafe_set_chr s j '\r' ;
346 unsafe_set_chr s (j + 1) '\n' ;
347 encoder.c_col <- 0 ;
348 k encoder
349350and encode_char chr k (encoder : encoder) =
351- if encoder.s >= 2 then (
352- let a, b, c =
353- (unsafe_byte encoder.i 0 0, unsafe_byte encoder.i 0 1, chr)
354- in
355 encoder.s <- 0 ;
356 let quantum = (Char.code a lsl 16) + (Char.code b lsl 8) + Char.code c in
357 let a = quantum lsr 18 in
···360 let d = quantum land 63 in
361 let rem = o_rem encoder in
362 let s, j, k =
363- if rem < 4 then (
0364 t_range encoder 4 ;
365- (encoder.t, 0, t_flush (k 4)) )
366 else
367 let j = encoder.o_pos in
368 encoder.o_pos <- encoder.o_pos + 4 ;
369- (encoder.o, encoder.o_off + j, k 4)
370- in
371 unsafe_set_chr s j default_alphabet.[a] ;
372 unsafe_set_chr s (j + 1) default_alphabet.[b] ;
373 unsafe_set_chr s (j + 2) default_alphabet.[c] ;
374 unsafe_set_chr s (j + 3) default_alphabet.[d] ;
375- flush k encoder )
376 else (
377 unsafe_set_chr encoder.i encoder.s chr ;
378 encoder.s <- encoder.s + 1 ;
379- k 0 encoder )
380381and encode_trailing k encoder =
382 match encoder.s with
···389 let d = quantum land 63 in
390 let rem = o_rem encoder in
391 let s, j, k =
392- if rem < 4 then (
0393 t_range encoder 4 ;
394- (encoder.t, 0, t_flush (k 4)) )
395 else
396 let j = encoder.o_pos in
397 encoder.o_pos <- encoder.o_pos + 4 ;
398- (encoder.o, encoder.o_off + j, k 4)
399- in
400 unsafe_set_chr s j default_alphabet.[b] ;
401 unsafe_set_chr s (j + 1) default_alphabet.[c] ;
402 unsafe_set_chr s (j + 2) default_alphabet.[d] ;
···410 let d = quantum land 63 in
411 let rem = o_rem encoder in
412 let s, j, k =
413- if rem < 4 then (
0414 t_range encoder 4 ;
415- (encoder.t, 0, t_flush (k 4)) )
416 else
417 let j = encoder.o_pos in
418 encoder.o_pos <- encoder.o_pos + 4 ;
419- (encoder.o, encoder.o_off + j, k 4)
420- in
421 unsafe_set_chr s j default_alphabet.[c] ;
422 unsafe_set_chr s (j + 1) default_alphabet.[d] ;
423 unsafe_set_chr s (j + 2) '=' ;
···430 let k col_count encoder =
431 encoder.c_col <- encoder.c_col + col_count ;
432 encoder.k <- encode_base64 ;
433- `Ok
434- in
435 match v with
436 | `Await -> k 0 encoder
437 | `End ->
438- if encoder.c_col = 76 then
439- encode_line_break (fun encoder -> encode_base64 encoder v) encoder
440 else encode_trailing k encoder
441 | `Char chr ->
442 let rem = o_rem encoder in
443- if rem < 1 then flush (fun encoder -> encode_base64 encoder v) encoder
444- else if encoder.c_col = 76 then
445- encode_line_break (fun encoder -> encode_base64 encoder v) encoder
0446 else encode_char chr k encoder
447448let encoder dst =
···450 match dst with
451 | `Manual -> (Bytes.empty, 1, 0, 0)
452 | `Buffer _ | `Channel _ ->
453- (Bytes.create io_buffer_size, 0, 0, io_buffer_size - 1)
454- in
455- { dst
456- ; o_off
457- ; o_pos
458- ; o_len
459- ; o
460- ; t= Bytes.create 4
461- ; t_pos= 1
462- ; t_len= 0
463- ; c_col= 0
464- ; i= Bytes.create 3
465- ; s= 0
466- ; k= encode_base64 }
0467468let encode encoder = encoder.k encoder
0469let encoder_dst encoder = encoder.dst
···19 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
2021let io_buffer_size = 65536
22+23let invalid_arg fmt = Format.ksprintf (fun s -> invalid_arg s) fmt
2425let invalid_bounds off len =
26 invalid_arg "Invalid bounds (off: %d, len: %d)" off len
2728+let malformed chr = `Malformed (String.make 1 chr)
02930let unsafe_byte source off pos = Bytes.unsafe_get source (off + pos)
31+32let unsafe_blit = Bytes.unsafe_blit
33+34let unsafe_chr = Char.unsafe_chr
35+36let unsafe_set_chr source off chr = Bytes.unsafe_set source off chr
3738+type state = { quantum : int; size : int; buffer : Bytes.t }
39+40+let continue state (quantum, size) = `Continue { state with quantum; size }
4142+let flush state = `Flush { state with quantum = 0; size = 0 }
04344let table =
45 "\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\255\062\255\255\255\063\052\053\054\055\056\057\058\059\060\061\255\255\255\255\255\255\255\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014\015\016\017\018\019\020\021\022\023\024\025\255\255\255\255\255\255\026\027\028\029\030\031\032\033\034\035\036\037\038\039\040\041\042\043\044\045\046\047\048\049\050\051\255\255\255\255\255"
4647+let r_repr ({ quantum; size; _ } as state) chr =
48 (* assert (0 <= off && 0 <= len && off + len <= String.length source); *)
49 (* assert (len >= 1); *)
50 let code = Char.code table.[Char.code chr] in
···60 flush state
61 | _ -> malformed chr
6263+type src = [ `Channel of in_channel | `String of string | `Manual ]
6465type decode =
66+ [ `Await | `End | `Wrong_padding | `Malformed of string | `Flush of string ]
6768type input =
69+ [ `Line_break | `Wsp | `Padding | `Malformed of string | `Flush of state ]
7071+type decoder = {
72+ src : src;
73+ mutable i : Bytes.t;
74+ mutable i_off : int;
75+ mutable i_pos : int;
76+ mutable i_len : int;
77+ mutable s : state;
78+ mutable padding : int;
79+ mutable unsafe : bool;
80+ mutable byte_count : int;
81+ mutable limit_count : int;
82+ mutable pp : decoder -> input -> decode;
83+ mutable k : decoder -> decode;
84+}
8586let i_rem decoder = decoder.i_len - decoder.i_pos + 1
87···92 decoder.i_len <- min_int
9394let src decoder source off len =
95+ if off < 0 || len < 0 || off + len > Bytes.length source
96+ then invalid_bounds off len
97+ else if len = 0
98+ then end_of_input decoder
99 else (
100 decoder.i <- source ;
101 decoder.i_off <- off ;
102 decoder.i_pos <- 0 ;
103+ decoder.i_len <- len - 1)
104105let refill k decoder =
106 match decoder.src with
107 | `Manual ->
108 decoder.k <- k ;
109 `Await
110+ | `String _ ->
111+ end_of_input decoder ;
112+ k decoder
113 | `Channel ic ->
114 let len = input ic decoder.i 0 (Bytes.length decoder.i) in
115 src decoder decoder.i 0 len ;
116 k decoder
117118let dangerous decoder v = decoder.unsafe <- v
119+120let reset decoder = decoder.limit_count <- 0
121122let ret k v byte_count decoder =
···126 if decoder.limit_count > 78 then dangerous decoder true ;
127 decoder.pp decoder v
128129+type flush_and_malformed = [ `Flush of state | `Malformed of string ]
130131+let padding { size; _ } padding =
132 match (size, padding) with
133 | 0, 0 -> true
134 | 1, _ -> false
···136 | 3, 1 -> true
137 | _ -> false
138139+let t_flush { quantum; size; buffer } =
140 match size with
141+ | 0 | 1 -> `Flush { quantum; size; buffer = Bytes.empty }
142 | 2 ->
143 let quantum = quantum lsr 4 in
144 `Flush
145+ { quantum; size; buffer = Bytes.make 1 (unsafe_chr (quantum land 255)) }
00146 | 3 ->
147 let quantum = quantum lsr 2 in
148 unsafe_set_chr buffer 0 (unsafe_chr ((quantum lsr 8) land 255)) ;
149 unsafe_set_chr buffer 1 (unsafe_chr (quantum land 255)) ;
150+ `Flush { quantum; size; buffer = Bytes.sub buffer 0 2 }
151+ | _ -> assert false
152+153+(* this branch is impossible, size can only ever be in the range [0..3]. *)
154155let wrong_padding decoder =
156 let k _ = `End in
157+ decoder.k <- k ;
158+ `Wrong_padding
159160let rec t_decode_base64 chr decoder =
161+ if decoder.padding = 0
162+ then
163 let rec go pos = function
164 | `Continue state ->
165 if decoder.i_len - (decoder.i_pos + pos) + 1 > 0
166 then (
167 match unsafe_byte decoder.i decoder.i_off (decoder.i_pos + pos) with
168+ | ('A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '+' | '/') as chr ->
169+ go (succ pos) (r_repr state chr)
170 | '=' ->
171 decoder.padding <- decoder.padding + 1 ;
172 decoder.i_pos <- decoder.i_pos + pos + 1 ;
173 decoder.s <- state ;
174+ ret decode_base64 `Padding (pos + 1) decoder
175 | ' ' | '\t' ->
176 decoder.i_pos <- decoder.i_pos + pos + 1 ;
177 decoder.s <- state ;
···183 | chr ->
184 decoder.i_pos <- decoder.i_pos + pos + 1 ;
185 decoder.s <- state ;
186+ ret decode_base64 (malformed chr) (pos + 1) decoder)
187+ else (
188 decoder.i_pos <- decoder.i_pos + pos ;
189 decoder.byte_count <- decoder.byte_count + pos ;
190 decoder.limit_count <- decoder.limit_count + pos ;
191 decoder.s <- state ;
192+ refill decode_base64 decoder)
193 | #flush_and_malformed as v ->
194 decoder.i_pos <- decoder.i_pos + pos ;
195+ ret decode_base64 v pos decoder in
0196 go 1 (r_repr decoder.s chr)
197 else (
198 decoder.i_pos <- decoder.i_pos + 1 ;
···200201and decode_base64_lf_after_cr decoder =
202 let rem = i_rem decoder in
203+ if rem < 0
204+ then ret decode_base64 (malformed '\r') 1 decoder
205+ else if rem = 0
206+ then refill decode_base64_lf_after_cr decoder
207 else
208 match unsafe_byte decoder.i decoder.i_off decoder.i_pos with
209 | '\n' ->
210+ decoder.i_pos <- decoder.i_pos + 1 ;
211+ ret decode_base64 `Line_break 2 decoder
212+ | _ -> ret decode_base64 (malformed '\r') 1 decoder
0213214and decode_base64 decoder =
215 let rem = i_rem decoder in
216+ if rem <= 0
217+ then
218+ if rem < 0
219+ then
220 ret
221 (fun decoder ->
222+ if padding decoder.s decoder.padding
223+ then `End
224+ else wrong_padding decoder)
225 (t_flush decoder.s) 0 decoder
226 else refill decode_base64 decoder
227 else
···243 ret decode_base64 (malformed chr) 1 decoder
244245let pp_base64 decoder = function
246+ | `Line_break ->
247+ reset decoder ;
248+ decoder.k decoder
249 | `Wsp | `Padding -> decoder.k decoder
250 | `Flush state ->
251 decoder.s <- state ;
···259 match src with
260 | `Manual -> (Bytes.empty, 0, 1, 0)
261 | `Channel _ -> (Bytes.create io_buffer_size, 0, 1, 0)
262+ | `String s -> (Bytes.unsafe_of_string s, 0, 0, String.length s - 1) in
263+ {
264+ src;
265+ i_off;
266+ i_pos;
267+ i_len;
268+ i;
269+ s = { quantum = 0; size = 0; buffer = Bytes.create 3 };
270+ padding = 0;
271+ unsafe = false;
272+ byte_count = 0;
273+ limit_count = 0;
274+ pp;
275+ k;
276+ }
277278let decode decoder = decoder.k decoder
279+280let decoder_byte_count decoder = decoder.byte_count
281+282let decoder_src decoder = decoder.src
283+284let decoder_dangerous decoder = decoder.unsafe
285286(* / *)
287288let invalid_encode () = invalid_arg "Expected `Await encode"
289290+type dst = [ `Channel of out_channel | `Buffer of Buffer.t | `Manual ]
0291292+type encode = [ `Await | `End | `Char of char ]
293+294+type encoder = {
295+ dst : dst;
296+ mutable o : Bytes.t;
297+ mutable o_off : int;
298+ mutable o_pos : int;
299+ mutable o_len : int;
300+ mutable c_col : int;
301+ i : Bytes.t;
302+ mutable s : int;
303+ t : Bytes.t;
304+ mutable t_pos : int;
305+ mutable t_len : int;
306+ mutable k : encoder -> encode -> [ `Ok | `Partial ];
307+}
308309let o_rem encoder = encoder.o_len - encoder.o_pos + 1
310311let dst encoder source off len =
312+ if off < 0 || len < 0 || off + len > Bytes.length source
313+ then invalid_bounds off len ;
314 encoder.o <- source ;
315 encoder.o_off <- off ;
316 encoder.o_pos <- 0 ;
···345 let blit encoder len =
346 unsafe_blit encoder.t encoder.t_pos encoder.o encoder.o_pos len ;
347 encoder.o_pos <- encoder.o_pos + len ;
348+ encoder.t_pos <- encoder.t_pos + len in
0349 let rem = o_rem encoder in
350 let len = encoder.t_len - encoder.t_pos + 1 in
351+ if rem < len
352+ then (
353 blit encoder rem ;
354+ flush (t_flush k) encoder)
355+ else (
356+ blit encoder len ;
357+ k encoder)
358359let rec encode_line_break k encoder =
360 let rem = o_rem encoder in
361 let s, j, k =
362+ if rem < 2
363+ then (
364 t_range encoder 2 ;
365+ (encoder.t, 0, t_flush k))
366 else
367 let j = encoder.o_pos in
368 encoder.o_pos <- encoder.o_pos + 2 ;
369+ (encoder.o, encoder.o_off + j, k) in
0370 unsafe_set_chr s j '\r' ;
371 unsafe_set_chr s (j + 1) '\n' ;
372 encoder.c_col <- 0 ;
373 k encoder
374375and encode_char chr k (encoder : encoder) =
376+ if encoder.s >= 2
377+ then (
378+ let a, b, c = (unsafe_byte encoder.i 0 0, unsafe_byte encoder.i 0 1, chr) in
0379 encoder.s <- 0 ;
380 let quantum = (Char.code a lsl 16) + (Char.code b lsl 8) + Char.code c in
381 let a = quantum lsr 18 in
···384 let d = quantum land 63 in
385 let rem = o_rem encoder in
386 let s, j, k =
387+ if rem < 4
388+ then (
389 t_range encoder 4 ;
390+ (encoder.t, 0, t_flush (k 4)))
391 else
392 let j = encoder.o_pos in
393 encoder.o_pos <- encoder.o_pos + 4 ;
394+ (encoder.o, encoder.o_off + j, k 4) in
0395 unsafe_set_chr s j default_alphabet.[a] ;
396 unsafe_set_chr s (j + 1) default_alphabet.[b] ;
397 unsafe_set_chr s (j + 2) default_alphabet.[c] ;
398 unsafe_set_chr s (j + 3) default_alphabet.[d] ;
399+ flush k encoder)
400 else (
401 unsafe_set_chr encoder.i encoder.s chr ;
402 encoder.s <- encoder.s + 1 ;
403+ k 0 encoder)
404405and encode_trailing k encoder =
406 match encoder.s with
···413 let d = quantum land 63 in
414 let rem = o_rem encoder in
415 let s, j, k =
416+ if rem < 4
417+ then (
418 t_range encoder 4 ;
419+ (encoder.t, 0, t_flush (k 4)))
420 else
421 let j = encoder.o_pos in
422 encoder.o_pos <- encoder.o_pos + 4 ;
423+ (encoder.o, encoder.o_off + j, k 4) in
0424 unsafe_set_chr s j default_alphabet.[b] ;
425 unsafe_set_chr s (j + 1) default_alphabet.[c] ;
426 unsafe_set_chr s (j + 2) default_alphabet.[d] ;
···434 let d = quantum land 63 in
435 let rem = o_rem encoder in
436 let s, j, k =
437+ if rem < 4
438+ then (
439 t_range encoder 4 ;
440+ (encoder.t, 0, t_flush (k 4)))
441 else
442 let j = encoder.o_pos in
443 encoder.o_pos <- encoder.o_pos + 4 ;
444+ (encoder.o, encoder.o_off + j, k 4) in
0445 unsafe_set_chr s j default_alphabet.[c] ;
446 unsafe_set_chr s (j + 1) default_alphabet.[d] ;
447 unsafe_set_chr s (j + 2) '=' ;
···454 let k col_count encoder =
455 encoder.c_col <- encoder.c_col + col_count ;
456 encoder.k <- encode_base64 ;
457+ `Ok in
0458 match v with
459 | `Await -> k 0 encoder
460 | `End ->
461+ if encoder.c_col = 76
462+ then encode_line_break (fun encoder -> encode_base64 encoder v) encoder
463 else encode_trailing k encoder
464 | `Char chr ->
465 let rem = o_rem encoder in
466+ if rem < 1
467+ then flush (fun encoder -> encode_base64 encoder v) encoder
468+ else if encoder.c_col = 76
469+ then encode_line_break (fun encoder -> encode_base64 encoder v) encoder
470 else encode_char chr k encoder
471472let encoder dst =
···474 match dst with
475 | `Manual -> (Bytes.empty, 1, 0, 0)
476 | `Buffer _ | `Channel _ ->
477+ (Bytes.create io_buffer_size, 0, 0, io_buffer_size - 1) in
478+ {
479+ dst;
480+ o_off;
481+ o_pos;
482+ o_len;
483+ o;
484+ t = Bytes.create 4;
485+ t_pos = 1;
486+ t_len = 0;
487+ c_col = 0;
488+ i = Bytes.create 3;
489+ s = 0;
490+ k = encode_base64;
491+ }
492493let encode encoder = encoder.k encoder
494+495let encoder_dst encoder = encoder.dst
+9-9
src/base64_rfc2045.mli
···20val default_alphabet : string
21(** A 64-character string specifying the regular Base64 alphabet. *)
2223-(** The type for decoders. *)
24type decoder
025026(** The type for input sources. With a [`Manual] source the client must provide
27 input with {!src}. *)
28-type src = [`Manual | `Channel of in_channel | `String of string]
2930type decode =
31- [`Await | `End | `Flush of string | `Malformed of string | `Wrong_padding]
3233val src : decoder -> Bytes.t -> int -> int -> unit
34(** [src d s j l] provides [d] with [l] bytes to read, starting at [j] in [s].
···66 still continue to decode even if [decoder_dangerous d] returns [true].
67 Nothing grow automatically internally in this state. *)
68069(** The type for output destinations. With a [`Manual] destination the client
70 must provide output storage with {!dst}. *)
71-type dst = [`Channel of out_channel | `Buffer of Buffer.t | `Manual]
7273-type encode = [`Await | `End | `Char of char]
74075(** The type for Base64 (RFC2045) encoder. *)
76-type encoder
7778val encoder : dst -> encoder
79(** [encoder dst] is an encoder for Base64 (RFC2045) that outputs to [dst]. *)
8081-val encode : encoder -> encode -> [`Ok | `Partial]
82(** [encode e v]: is {ul {- [`Partial] iff [e] has a [`Manual] destination and
83 needs more output storage. The client must use {!dst} to provide a new
84 buffer and then call {!encode} with [`Await] until [`Ok] is returned.} {-
···99val dst : encoder -> Bytes.t -> int -> int -> unit
100(** [dst e s j l] provides [e] with [l] bytes to write, starting at [j] in [s].
101 This byte range is written by calls to {!encode} with [e] until [`Partial]
102- is returned. Use {!dst_rem} to know the remaining number of non-written
103- free bytes in [s]. *)
104105val dst_rem : encoder -> int
106(** [dst_rem e] is the remaining number of non-written, free bytes in the last
···20val default_alphabet : string
21(** A 64-character string specifying the regular Base64 alphabet. *)
22023type decoder
24+(** The type for decoders. *)
2526+type src = [ `Manual | `Channel of in_channel | `String of string ]
27(** The type for input sources. With a [`Manual] source the client must provide
28 input with {!src}. *)
02930type decode =
31+ [ `Await | `End | `Flush of string | `Malformed of string | `Wrong_padding ]
3233val src : decoder -> Bytes.t -> int -> int -> unit
34(** [src d s j l] provides [d] with [l] bytes to read, starting at [j] in [s].
···66 still continue to decode even if [decoder_dangerous d] returns [true].
67 Nothing grow automatically internally in this state. *)
6869+type dst = [ `Channel of out_channel | `Buffer of Buffer.t | `Manual ]
70(** The type for output destinations. With a [`Manual] destination the client
71 must provide output storage with {!dst}. *)
07273+type encode = [ `Await | `End | `Char of char ]
7475+type encoder
76(** The type for Base64 (RFC2045) encoder. *)
07778val encoder : dst -> encoder
79(** [encoder dst] is an encoder for Base64 (RFC2045) that outputs to [dst]. *)
8081+val encode : encoder -> encode -> [ `Ok | `Partial ]
82(** [encode e v]: is {ul {- [`Partial] iff [e] has a [`Manual] destination and
83 needs more output storage. The client must use {!dst} to provide a new
84 buffer and then call {!encode} with [`Await] until [`Ok] is returned.} {-
···99val dst : encoder -> Bytes.t -> int -> int -> unit
100(** [dst e s j l] provides [e] with [l] bytes to write, starting at [j] in [s].
101 This byte range is written by calls to {!encode} with [e] until [`Partial]
102+ is returned. Use {!dst_rem} to know the remaining number of non-written free
103+ bytes in [s]. *)
104105val dst_rem : encoder -> int
106(** [dst_rem e] is the remaining number of non-written, free bytes in the last
+2-1
src/unsafe_pre407.ml
···1-external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_string_set16u" [@@noalloc]
0
···1+external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_string_set16u"
2+ [@@noalloc]
+2-1
src/unsafe_stable.ml
···1-external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_bytes_set16u" [@@noalloc]
0
···1+external unsafe_set_uint16 : bytes -> int -> int -> unit = "%caml_bytes_set16u"
2+ [@@noalloc]
+212-149
test/test.ml
···28 BASE64("foobar") = "Zm9vYmFy"
29*)
3031-let rfc4648_tests = [
32- "", "";
33- "f", "Zg==";
34- "fo", "Zm8=";
35- "foo", "Zm9v";
36- "foob", "Zm9vYg==";
37- "fooba", "Zm9vYmE=";
38- "foobar", "Zm9vYmFy";
39-]
04041-let hannes_tests = [
42- "dummy", "ZHVtbXk=";
43- "dummy", "ZHVtbXk";
44- "dummy", "ZHVtbXk==";
45- "dummy", "ZHVtbXk===";
46- "dummy", "ZHVtbXk====";
47- "dummy", "ZHVtbXk=====";
48- "dummy", "ZHVtbXk======";
49-]
05051-let php_tests = [
52- "πάντα χωρεῖ καὶ οὐδὲν μένει …", "z4DOrM69z4TOsSDPh8-Jz4HOteG_liDOus6x4b22IM6_4b2QzrThvbLOvSDOvM6tzr3Otc65IOKApg"
53-]
0005455-let rfc3548_tests = [
56- "\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+";
57- "\x14\xfb\x9c\x03\xd9", "FPucA9k=";
58- "\x14\xfb\x9c\x03", "FPucAw==";
59-]
06061-let cfcs_tests = [
62- 0, 2, "\004", "BB";
63- 1, 2, "\004", "ABB";
64- 1, 2, "\004", "ABBA";
65- 2, 2, "\004", "AABBA";
66- 2, 2, "\004", "AABBAA";
67- 0, 0, "", "BB";
68- 1, 0, "", "BB";
69- 2, 0, "", "BB";
70-]
07172let nocrypto_tests =
73- [ "\x00\x5a\x6d\x39\x76", None
74- ; "\x5a\x6d\x39\x76", Some "\x66\x6f\x6f"
75- ; "\x5a\x6d\x39\x76\x76", None
76- ; "\x5a\x6d\x39\x76\x76\x76", None
77- ; "\x5a\x6d\x39\x76\x76\x76\x76", None
78- ; "\x5a\x6d\x39\x76\x00", None
79- ; "\x5a\x6d\x39\x76\x62\x77\x3d\x3d", Some "\x66\x6f\x6f\x6f"
80- ; "\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00", None
81- ; "\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01", None
82- ; "\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01\x02", None
83- ; "\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01\x02\x03", None
84- ; "\x5a\x6d\x39\x76\x62\x32\x38\x3d", Some "\x66\x6f\x6f\x6f\x6f"
85- ; "\x5a\x6d\x39\x76\x62\x32\x39\x76", Some "\x66\x6f\x6f\x6f\x6f\x6f"
86- ; "YWE=", Some "aa"
87- ; "YWE==", None
88- ; "YWE===", None
89- ; "YWE=====", None
90- ; "YWE======", None ]
009192let alphabet_size () =
93- List.iter (fun (name,alphabet) ->
94- Alcotest.(check int) (sprintf "Alphabet size %s = 64" name)
95- 64 (Base64.length_alphabet alphabet))
96- ["default",Base64.default_alphabet; "uri_safe",Base64.uri_safe_alphabet]
0000009798(* Encode using OpenSSL `base64` utility *)
99let openssl_encode buf =
100- Bos.(OS.Cmd.in_string buf |> OS.Cmd.run_io (Cmd.v "base64") |> OS.Cmd.to_string ~trim:true) |>
101- function | Ok r -> prerr_endline r; r | Error (`Msg e) -> raise (Failure (sprintf "OpenSSL decode: %s" e))
0000000102103(* Encode using this library *)
104-let lib_encode buf =
105- Base64.encode_exn ~pad:true buf
106107let test_rfc4648 () =
108- List.iter (fun (c,r) ->
109- (* Base64 vs openssl *)
110- Alcotest.(check string) (sprintf "encode %s" c) (openssl_encode c) (lib_encode c);
111- (* Base64 vs test cases above *)
112- Alcotest.(check string) (sprintf "encode rfc4648 %s" c) r (lib_encode c);
113- (* Base64 decode vs library *)
114- Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn r);
115- ) rfc4648_tests
00116117let test_rfc3548 () =
118- List.iter (fun (c,r) ->
119- (* Base64 vs openssl *)
120- Alcotest.(check string) (sprintf "encode %s" c) (openssl_encode c) (lib_encode c);
121- (* Base64 vs test cases above *)
122- Alcotest.(check string) (sprintf "encode rfc3548 %s" c) r (lib_encode c);
123- (* Base64 decode vs library *)
124- Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn r);
125- ) rfc3548_tests
00126127let test_hannes () =
128- List.iter (fun (c,r) ->
129- (* Base64 vs test cases above *)
130- Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn ~pad:false r);
131- ) hannes_tests
000132133let test_php () =
134- List.iter (fun (c,r) ->
135- Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn ~pad:false ~alphabet:Base64.uri_safe_alphabet r);
136- ) php_tests
000137138let test_cfcs () =
139- List.iter (fun (off, len, c,r) ->
140- Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn ~pad:false ~off ~len r);
141- ) cfcs_tests
000142143let test_nocrypto () =
144- List.iter (fun (input, res) ->
145- let res' = match Base64.decode ~pad:true input with
146- | Ok v -> Some v
147- | Error _ -> None in
148- Alcotest.(check (option string)) (sprintf "decode %S" input) res' res ;
149- ) nocrypto_tests
00150151exception Malformed
0152exception Wrong_padding
153154let strict_base64_rfc2045_of_string x =
155 let decoder = Base64_rfc2045.decoder (`String x) in
156 let res = Buffer.create 16 in
157158- let rec go () = match Base64_rfc2045.decode decoder with
159- | `End -> ()
160- | `Wrong_padding -> raise Wrong_padding
161- | `Malformed _ -> raise Malformed
162- | `Flush x -> Buffer.add_string res x ; go ()
163- | `Await -> Alcotest.failf "Retrieve impossible case: `Await" in
000164165 Base64_rfc2045.src decoder (Bytes.unsafe_of_string x) 0 (String.length x) ;
166- go () ; Buffer.contents res
0167168let relaxed_base64_rfc2045_of_string x =
169 let decoder = Base64_rfc2045.decoder (`String x) in
170 let res = Buffer.create 16 in
171172- let rec go () = match Base64_rfc2045.decode decoder with
173- | `End -> ()
174- | `Wrong_padding -> go ()
175- | `Malformed _ -> go ()
176- | `Flush x -> Buffer.add_string res x ; go ()
177- | `Await -> Alcotest.failf "Retrieve impossible case: `Await" in
000178179 Base64_rfc2045.src decoder (Bytes.unsafe_of_string x) 0 (String.length x) ;
180- go () ; Buffer.contents res
0181182let test_strict_rfc2045 =
183- [ "c2FsdXQgbGVzIGNvcGFpbnMgZmF1dCBhYnNvbHVtZW50IHF1ZSBqZSBkw6lwYXNzZSBsZXMgODAg\r\n\
184- Y2hhcmFjdGVycyBwb3VyIHZvaXIgc2kgbW9uIGVuY29kZXIgZml0cyBiaWVuIGRhbnMgbGVzIGxp\r\n\
185- bWl0ZXMgZGUgbGEgUkZDIDIwNDUgLi4u",
186- "salut les copains faut absolument que je dépasse les 80 characters pour voir si \
187- mon encoder fits bien dans les limites de la RFC 2045 ..."
188- ; "", ""
189- ; "Zg==", "f"
190- ; "Zm8=", "fo"
191- ; "Zm9v", "foo"
192- ; "Zm9vYg==", "foob"
193- ; "Zm9vYmE=", "fooba"
194- ; "Zm9vYmFy", "foobar" ]
000195196let test_relaxed_rfc2045 =
197- [ "Zg", "f"
198- ; "Zm\n8", "fo"
199- ; "Zm\r9v", "foo"
200- ; "Zm9 vYg", "foob"
201- ; "Zm9\r\n vYmE", "fooba"
202- ; "Zm9évYmFy", "foobar" ]
00203204let strict_base64_rfc2045_to_string x =
205 let res = Buffer.create 16 in
206 let encoder = Base64_rfc2045.encoder (`Buffer res) in
207 String.iter
208- (fun chr -> match Base64_rfc2045.encode encoder (`Char chr) with
0209 | `Ok -> ()
210- | `Partial -> Alcotest.failf "Retrieve impossible case for (`Char %02x): `Partial" (Char.code chr))
00211 x ;
212 match Base64_rfc2045.encode encoder `End with
213 | `Ok -> Buffer.contents res
214 | `Partial -> Alcotest.fail "Retrieve impossible case for `End: `Partial"
215216let test_strict_with_malformed_input_rfc2045 =
217- List.mapi (fun i (has, _) ->
218- Alcotest.test_case (Fmt.strf "strict rfc2045 - %02d" i) `Quick @@ fun () ->
00219 try
220 let _ = strict_base64_rfc2045_of_string has in
221 Alcotest.failf "Strict parser valids malformed input: %S" has
222- with Malformed | Wrong_padding -> () )
223 test_relaxed_rfc2045
224225let test_strict_rfc2045 =
226- List.mapi (fun i (has, expect) ->
227- Alcotest.test_case (Fmt.strf "strict rfc2045 - %02d" i) `Quick @@ fun () ->
00228 try
229 let res0 = strict_base64_rfc2045_of_string has in
230 let res1 = strict_base64_rfc2045_to_string res0 in
···234 test_strict_rfc2045
235236let test_relaxed_rfc2045 =
237- List.mapi (fun i (has, expect) ->
238- Alcotest.test_case (Fmt.strf "relaxed rfc2045 - %02d" i) `Quick @@ fun () ->
00239 let res0 = relaxed_base64_rfc2045_of_string has in
240 Alcotest.(check string) "decode(x)" res0 expect)
241 test_relaxed_rfc2045
242243-let test_invariants = [ "Alphabet size", `Quick, alphabet_size ]
244-let test_codec = [ "RFC4648 test vectors", `Quick, test_rfc4648
245- ; "RFC3548 test vectors", `Quick, test_rfc3548
246- ; "Hannes test vectors", `Quick, test_hannes
247- ; "Cfcs test vectors", `Quick, test_cfcs
248- ; "PHP test vectors", `Quick, test_php
249- ; "Nocrypto test vectors", `Quick, test_nocrypto ]
250251-let () =
252- Alcotest.run "Base64" [
253- "invariants", test_invariants;
254- "codec", test_codec;
255- "rfc2045 (0)", test_strict_rfc2045;
256- "rfc2045 (1)", test_strict_with_malformed_input_rfc2045;
257- "rfc2045 (2)", test_relaxed_rfc2045;
0258 ]
259000000000
···28 BASE64("foobar") = "Zm9vYmFy"
29*)
3031+let rfc4648_tests =
32+ [
33+ ("", "");
34+ ("f", "Zg==");
35+ ("fo", "Zm8=");
36+ ("foo", "Zm9v");
37+ ("foob", "Zm9vYg==");
38+ ("fooba", "Zm9vYmE=");
39+ ("foobar", "Zm9vYmFy");
40+ ]
4142+let hannes_tests =
43+ [
44+ ("dummy", "ZHVtbXk=");
45+ ("dummy", "ZHVtbXk");
46+ ("dummy", "ZHVtbXk==");
47+ ("dummy", "ZHVtbXk===");
48+ ("dummy", "ZHVtbXk====");
49+ ("dummy", "ZHVtbXk=====");
50+ ("dummy", "ZHVtbXk======");
51+ ]
5253+let php_tests =
54+ [
55+ ( "πάντα χωρεῖ καὶ οὐδὲν μένει …",
56+ "z4DOrM69z4TOsSDPh8-Jz4HOteG_liDOus6x4b22IM6_4b2QzrThvbLOvSDOvM6tzr3Otc65IOKApg"
57+ );
58+ ]
5960+let rfc3548_tests =
61+ [
62+ ("\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+");
63+ ("\x14\xfb\x9c\x03\xd9", "FPucA9k=");
64+ ("\x14\xfb\x9c\x03", "FPucAw==");
65+ ]
6667+let cfcs_tests =
68+ [
69+ (0, 2, "\004", "BB");
70+ (1, 2, "\004", "ABB");
71+ (1, 2, "\004", "ABBA");
72+ (2, 2, "\004", "AABBA");
73+ (2, 2, "\004", "AABBAA");
74+ (0, 0, "", "BB");
75+ (1, 0, "", "BB");
76+ (2, 0, "", "BB");
77+ ]
7879let nocrypto_tests =
80+ [
81+ ("\x00\x5a\x6d\x39\x76", None);
82+ ("\x5a\x6d\x39\x76", Some "\x66\x6f\x6f");
83+ ("\x5a\x6d\x39\x76\x76", None);
84+ ("\x5a\x6d\x39\x76\x76\x76", None);
85+ ("\x5a\x6d\x39\x76\x76\x76\x76", None);
86+ ("\x5a\x6d\x39\x76\x00", None);
87+ ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d", Some "\x66\x6f\x6f\x6f");
88+ ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00", None);
89+ ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01", None);
90+ ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01\x02", None);
91+ ("\x5a\x6d\x39\x76\x62\x77\x3d\x3d\x00\x01\x02\x03", None);
92+ ("\x5a\x6d\x39\x76\x62\x32\x38\x3d", Some "\x66\x6f\x6f\x6f\x6f");
93+ ("\x5a\x6d\x39\x76\x62\x32\x39\x76", Some "\x66\x6f\x6f\x6f\x6f\x6f");
94+ ("YWE=", Some "aa");
95+ ("YWE==", None);
96+ ("YWE===", None);
97+ ("YWE=====", None);
98+ ("YWE======", None);
99+ ]
100101let alphabet_size () =
102+ List.iter
103+ (fun (name, alphabet) ->
104+ Alcotest.(check int)
105+ (sprintf "Alphabet size %s = 64" name)
106+ 64
107+ (Base64.length_alphabet alphabet))
108+ [
109+ ("default", Base64.default_alphabet);
110+ ("uri_safe", Base64.uri_safe_alphabet);
111+ ]
112113(* Encode using OpenSSL `base64` utility *)
114let openssl_encode buf =
115+ Bos.(
116+ OS.Cmd.in_string buf
117+ |> OS.Cmd.run_io (Cmd.v "base64")
118+ |> OS.Cmd.to_string ~trim:true)
119+ |> function
120+ | Ok r ->
121+ prerr_endline r ;
122+ r
123+ | Error (`Msg e) -> raise (Failure (sprintf "OpenSSL decode: %s" e))
124125(* Encode using this library *)
126+let lib_encode buf = Base64.encode_exn ~pad:true buf
0127128let test_rfc4648 () =
129+ List.iter
130+ (fun (c, r) ->
131+ (* Base64 vs openssl *)
132+ Alcotest.(check string)
133+ (sprintf "encode %s" c) (openssl_encode c) (lib_encode c) ;
134+ (* Base64 vs test cases above *)
135+ Alcotest.(check string) (sprintf "encode rfc4648 %s" c) r (lib_encode c) ;
136+ (* Base64 decode vs library *)
137+ Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn r))
138+ rfc4648_tests
139140let test_rfc3548 () =
141+ List.iter
142+ (fun (c, r) ->
143+ (* Base64 vs openssl *)
144+ Alcotest.(check string)
145+ (sprintf "encode %s" c) (openssl_encode c) (lib_encode c) ;
146+ (* Base64 vs test cases above *)
147+ Alcotest.(check string) (sprintf "encode rfc3548 %s" c) r (lib_encode c) ;
148+ (* Base64 decode vs library *)
149+ Alcotest.(check string) (sprintf "decode %s" r) c (Base64.decode_exn r))
150+ rfc3548_tests
151152let test_hannes () =
153+ List.iter
154+ (fun (c, r) ->
155+ (* Base64 vs test cases above *)
156+ Alcotest.(check string)
157+ (sprintf "decode %s" r) c
158+ (Base64.decode_exn ~pad:false r))
159+ hannes_tests
160161let test_php () =
162+ List.iter
163+ (fun (c, r) ->
164+ Alcotest.(check string)
165+ (sprintf "decode %s" r) c
166+ (Base64.decode_exn ~pad:false ~alphabet:Base64.uri_safe_alphabet r))
167+ php_tests
168169let test_cfcs () =
170+ List.iter
171+ (fun (off, len, c, r) ->
172+ Alcotest.(check string)
173+ (sprintf "decode %s" r) c
174+ (Base64.decode_exn ~pad:false ~off ~len r))
175+ cfcs_tests
176177let test_nocrypto () =
178+ List.iter
179+ (fun (input, res) ->
180+ let res' =
181+ match Base64.decode ~pad:true input with
182+ | Ok v -> Some v
183+ | Error _ -> None in
184+ Alcotest.(check (option string)) (sprintf "decode %S" input) res' res)
185+ nocrypto_tests
186187exception Malformed
188+189exception Wrong_padding
190191let strict_base64_rfc2045_of_string x =
192 let decoder = Base64_rfc2045.decoder (`String x) in
193 let res = Buffer.create 16 in
194195+ let rec go () =
196+ match Base64_rfc2045.decode decoder with
197+ | `End -> ()
198+ | `Wrong_padding -> raise Wrong_padding
199+ | `Malformed _ -> raise Malformed
200+ | `Flush x ->
201+ Buffer.add_string res x ;
202+ go ()
203+ | `Await -> Alcotest.failf "Retrieve impossible case: `Await" in
204205 Base64_rfc2045.src decoder (Bytes.unsafe_of_string x) 0 (String.length x) ;
206+ go () ;
207+ Buffer.contents res
208209let relaxed_base64_rfc2045_of_string x =
210 let decoder = Base64_rfc2045.decoder (`String x) in
211 let res = Buffer.create 16 in
212213+ let rec go () =
214+ match Base64_rfc2045.decode decoder with
215+ | `End -> ()
216+ | `Wrong_padding -> go ()
217+ | `Malformed _ -> go ()
218+ | `Flush x ->
219+ Buffer.add_string res x ;
220+ go ()
221+ | `Await -> Alcotest.failf "Retrieve impossible case: `Await" in
222223 Base64_rfc2045.src decoder (Bytes.unsafe_of_string x) 0 (String.length x) ;
224+ go () ;
225+ Buffer.contents res
226227let test_strict_rfc2045 =
228+ [
229+ ( "c2FsdXQgbGVzIGNvcGFpbnMgZmF1dCBhYnNvbHVtZW50IHF1ZSBqZSBkw6lwYXNzZSBsZXMgODAg\r\n\
230+ Y2hhcmFjdGVycyBwb3VyIHZvaXIgc2kgbW9uIGVuY29kZXIgZml0cyBiaWVuIGRhbnMgbGVzIGxp\r\n\
231+ bWl0ZXMgZGUgbGEgUkZDIDIwNDUgLi4u",
232+ "salut les copains faut absolument que je dépasse les 80 characters \
233+ pour voir si mon encoder fits bien dans les limites de la RFC 2045 ..."
234+ );
235+ ("", "");
236+ ("Zg==", "f");
237+ ("Zm8=", "fo");
238+ ("Zm9v", "foo");
239+ ("Zm9vYg==", "foob");
240+ ("Zm9vYmE=", "fooba");
241+ ("Zm9vYmFy", "foobar");
242+ ]
243244let test_relaxed_rfc2045 =
245+ [
246+ ("Zg", "f");
247+ ("Zm\n8", "fo");
248+ ("Zm\r9v", "foo");
249+ ("Zm9 vYg", "foob");
250+ ("Zm9\r\n vYmE", "fooba");
251+ ("Zm9évYmFy", "foobar");
252+ ]
253254let strict_base64_rfc2045_to_string x =
255 let res = Buffer.create 16 in
256 let encoder = Base64_rfc2045.encoder (`Buffer res) in
257 String.iter
258+ (fun chr ->
259+ match Base64_rfc2045.encode encoder (`Char chr) with
260 | `Ok -> ()
261+ | `Partial ->
262+ Alcotest.failf "Retrieve impossible case for (`Char %02x): `Partial"
263+ (Char.code chr))
264 x ;
265 match Base64_rfc2045.encode encoder `End with
266 | `Ok -> Buffer.contents res
267 | `Partial -> Alcotest.fail "Retrieve impossible case for `End: `Partial"
268269let test_strict_with_malformed_input_rfc2045 =
270+ List.mapi
271+ (fun i (has, _) ->
272+ Alcotest.test_case (Fmt.strf "strict rfc2045 - %02d" i) `Quick
273+ @@ fun () ->
274 try
275 let _ = strict_base64_rfc2045_of_string has in
276 Alcotest.failf "Strict parser valids malformed input: %S" has
277+ with Malformed | Wrong_padding -> ())
278 test_relaxed_rfc2045
279280let test_strict_rfc2045 =
281+ List.mapi
282+ (fun i (has, expect) ->
283+ Alcotest.test_case (Fmt.strf "strict rfc2045 - %02d" i) `Quick
284+ @@ fun () ->
285 try
286 let res0 = strict_base64_rfc2045_of_string has in
287 let res1 = strict_base64_rfc2045_to_string res0 in
···291 test_strict_rfc2045
292293let test_relaxed_rfc2045 =
294+ List.mapi
295+ (fun i (has, expect) ->
296+ Alcotest.test_case (Fmt.strf "relaxed rfc2045 - %02d" i) `Quick
297+ @@ fun () ->
298 let res0 = relaxed_base64_rfc2045_of_string has in
299 Alcotest.(check string) "decode(x)" res0 expect)
300 test_relaxed_rfc2045
301302+let test_invariants = [ ("Alphabet size", `Quick, alphabet_size) ]
000000303304+let test_codec =
305+ [
306+ ("RFC4648 test vectors", `Quick, test_rfc4648);
307+ ("RFC3548 test vectors", `Quick, test_rfc3548);
308+ ("Hannes test vectors", `Quick, test_hannes);
309+ ("Cfcs test vectors", `Quick, test_cfcs);
310+ ("PHP test vectors", `Quick, test_php);
311+ ("Nocrypto test vectors", `Quick, test_nocrypto);
312 ]
313314+let () =
315+ Alcotest.run "Base64"
316+ [
317+ ("invariants", test_invariants);
318+ ("codec", test_codec);
319+ ("rfc2045 (0)", test_strict_rfc2045);
320+ ("rfc2045 (1)", test_strict_with_malformed_input_rfc2045);
321+ ("rfc2045 (2)", test_relaxed_rfc2045);
322+ ]