1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
type t =
| IPv4 of Ipaddr.V4.t
| IPv6 of Ipaddr.V6.t
| Extension of string * string
| Domain of string list
let equal a b = match a, b with
| IPv4 a, IPv4 b -> Ipaddr.(compare (V4 a) (V4 b)) = 0
| IPv6 a, IPv6 b -> Ipaddr.(compare (V6 a) (V6 b)) = 0
| IPv4 a, IPv6 b -> Ipaddr.(compare (V4 a) (V6 b)) = 0
| IPv6 a, IPv4 b -> Ipaddr.(compare (V6 a) (V4 b)) = 0
| Extension (ka, va), Extension (kb, vb) ->
String.equal ka kb && String.equal va vb
| Domain a, Domain b ->
(try List.for_all2 String.equal a b
with _ -> false)
| _, _ -> false
let pp ppf = function
| IPv4 ipv4 -> Ipaddr.V4.pp ppf ipv4
| IPv6 ipv6 -> Ipaddr.V6.pp ppf ipv6
| Extension (k, v) -> Fmt.pf ppf "%s:%s" k v
| Domain l -> Fmt.pf ppf "%a" Fmt.(list ~sep:(const string ".") string) l
module Parser = struct
open Angstrom
let ( or ) a b = fun x -> a x || b x
let is_alpha = function 'a' .. 'z' | 'A' .. 'Z' -> true | _ -> false
let is_digit = function '0' .. '9' -> true | _ -> false
let is_dash = (=) '-'
let let_dig = satisfy (is_alpha or is_digit)
let ldh_str =
take_while1 (is_alpha or is_digit or is_dash)
>>= fun res ->
if String.get res (String.length res - 1) <> '-'
then return res
else fail "Invalid ldh-str token"
let sub_domain =
let_dig
>>= fun pre -> option "" ldh_str
>>| fun lst -> String.concat "" [ String.make 1 pre; lst ]
let domain =
sub_domain
>>= fun x -> many (char '.' *> sub_domain)
>>| fun r -> Domain (x :: r)
let is_dcontent = function
| '\033' .. '\090' | '\094' .. '\126' -> true
| _ -> false
let ipv4_address_literal =
Unsafe.take_while1 is_dcontent (fun buf ~off ~len ->
let raw = Bigstringaf.substring buf ~off ~len in
let pos = ref 0 in
try
let res = Ipaddr.V4.of_string_raw raw pos in
if !pos = len then Some res else None
with Ipaddr.Parse_error _ -> None )
>>= function Some v -> return v | None -> fail "ipv4_address_literal"
let ipv6_addr =
Unsafe.take_while1 is_dcontent (fun buf ~off ~len ->
let raw = Bigstringaf.substring buf ~off ~len in
let pos = ref 0 in
try
let res = Ipaddr.V6.of_string_raw raw pos in
if !pos = len then Some res else None
with Ipaddr.Parse_error _ -> None )
>>= function Some v -> return v | None -> fail "ipv6_addr"
let ipv6_address_literal = string "IPv6:" *> ipv6_addr
let failf fmt = Fmt.kstrf fail fmt
let ldh_str =
take_while1 (function
| 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '-' -> true
| _ -> false )
>>= fun ldh ->
if String.unsafe_get ldh (String.length ldh - 1) = '-'
then failf "ldh_str: %s is invalid" ldh
else return ldh
let general_address_literal =
ldh_str <* char ':'
>>= fun ldh -> take_while1 is_dcontent
>>| fun value -> Extension (ldh, value)
let address_literal =
char '[' *>
((ipv4_address_literal >>| (fun v -> IPv4 v))
<|> (ipv6_address_literal >>| fun v -> IPv6 v)
<|> general_address_literal)
<* char ']'
let of_string_exn x =
match parse_string (domain <|> address_literal) x with
| Ok v -> v
| Error _ -> Fmt.invalid_arg "Invalid domain: %s" x
let of_string x =
match parse_string (domain <|> address_literal) x with
| Ok _ as v -> v
| Error _ -> Rresult.R.error_msgf "Invalid domain: %S" x
end
module Encoder = struct
let to_string = function
| IPv4 ipv4 -> Fmt.strf "[%s]" (Ipaddr.V4.to_string ipv4)
| IPv6 ipv6 -> Fmt.strf "[IPv6:%s]" (Ipaddr.V6.to_string ipv6)
| Extension (k, v) -> Fmt.strf "[%s:%s]" k v
| Domain l -> Fmt.strf "%a" Fmt.(list ~sep:(const string ".") string) l
end
let of_string = Parser.of_string
let of_string_exn = Parser.of_string_exn
let to_string = Encoder.to_string
exception Break
let satisfy predicate x =
let len = String.length x in
try for i = 0 to len - 1 do if not (predicate x.[i]) then raise Break done ; true
with Break -> false
let extension k v =
let is_ldh = Parser.(is_alpha or is_digit or is_dash) in
let is_dcontent = Parser.is_dcontent in
if String.length k > 0 && satisfy is_ldh k && k.[String.length k - 1] <> '-'
&& String.length v > 0 && satisfy is_dcontent v
then Ok (Extension (k, v))
else Rresult.R.error_msgf "Invalid key:%S or value:%S" k v
let is_atext_valid_string _ = true
type atom = string
let atom x =
if is_atext_valid_string x
then Ok x
else Rresult.R.error_msgf "atom %S does not respect standards" x
let atom_exn x =
match atom x with
| Ok v -> v
| Error (`Msg err) -> invalid_arg err
let a = atom_exn
module Peano = struct
type z = Z
type 'a s = S
end
let unsafe_domain_of_list_exn = function
| [] -> Fmt.invalid_arg "A domain must contain at least one element"
| domain -> Domain domain
type 'a domain =
| ( :: ) : atom * 'a domain -> 'a Peano.s domain
| [] : Peano.z domain
let rec coerce : type a. a Peano.s domain -> string list = function
| [x] -> [x]
| x :: y :: r -> List.cons x (coerce (y :: r))
let make_domain : type a. a domain -> (string list, [ `Msg of string ]) result = function
| [] -> Rresult.R.error_msg "A domain must contain at least one element"
| x :: r -> Ok (coerce (x :: r))
type 'a w =
| WDomain : 'a domain w
| WIPv4 : Ipaddr.V4.t w
| WIPv6 : Ipaddr.V6.t w
let domain = WDomain
let ipv4 = WIPv4
let ipv6 = WIPv6
let make : type a. a w -> a -> (t, [ `Msg of string ]) result =
fun witness v ->
match witness with
| WDomain -> Rresult.R.(make_domain v >>| fun v -> Domain v)
| WIPv4 -> Ok (IPv4 v)
| WIPv6 -> Ok (IPv6 v)
let v : type a. a w -> a -> t =
fun witness v ->
match make witness v with
| Ok v -> v
| Error (`Msg err) -> invalid_arg err