Skip to content

Commit e6f4ee8

Browse files
authored
Merge pull request #108 from mirage/with_port_of_string
Add *.with_port_of_string functions
2 parents 2df4d68 + 3272d36 commit e6f4ee8

File tree

3 files changed

+123
-0
lines changed

3 files changed

+123
-0
lines changed

lib/ipaddr.ml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,20 @@ module V4 = struct
172172

173173
let of_string s = try_with_result of_string_exn s
174174

175+
let with_port_of_string ~default s =
176+
try
177+
let len = String.length s and o = ref 0 in
178+
let ipv4 = of_string_raw s o in
179+
if !o < len && s.[!o] = ':' then (
180+
incr o;
181+
let port = parse_dec_int s o in
182+
expect_end s o;
183+
Ok (ipv4, port))
184+
else (
185+
expect_end s o;
186+
Ok (ipv4, default))
187+
with Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg))
188+
175189
let to_buffer b i =
176190
Printf.bprintf b "%ld.%ld.%ld.%ld" (i >! 24) (i >! 16) (i >! 8) (i >! 0)
177191

@@ -626,6 +640,20 @@ module V6 = struct
626640

627641
let of_string s = try_with_result of_string_exn s
628642

643+
let with_port_of_string ~default s =
644+
let len = String.length s and o = ref 0 in
645+
try
646+
let ipv6 = of_string_raw s o in
647+
if !o < len && s.[!o] = ':' then (
648+
incr o;
649+
let port = parse_dec_int s o in
650+
expect_end s o;
651+
Ok (ipv6, port))
652+
else (
653+
expect_end s o;
654+
Ok (ipv6, default))
655+
with Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg))
656+
629657
(* http://tools.ietf.org/html/rfc5952 *)
630658
let to_buffer buf addr =
631659
let ((a, b, c, d, e, f, g, h) as comp) = to_int16 addr in
@@ -1013,6 +1041,20 @@ let of_string_exn s =
10131041

10141042
let of_string s = try_with_result of_string_exn s
10151043

1044+
let with_port_of_string ~default s =
1045+
let len = String.length s and o = ref 0 in
1046+
try
1047+
let ipv6 = of_string_raw s o in
1048+
if !o < len && s.[!o] = ':' then (
1049+
incr o;
1050+
let port = parse_dec_int s o in
1051+
expect_end s o;
1052+
Ok (ipv6, port))
1053+
else (
1054+
expect_end s o;
1055+
Ok (ipv6, default))
1056+
with Parse_error (msg, _) -> Error (`Msg ("Ipaddr: " ^ msg))
1057+
10161058
let v6_of_v4 v4 =
10171059
V6.(Prefix.(network_address ipv4_mapped (of_int32 (0l, 0l, 0l, v4))))
10181060

lib/ipaddr.mli

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ module V4 : sig
6767
an unspecified value during the function call. [s] will a {!Parse_error}
6868
exception if it is an invalid or truncated IP address. *)
6969

70+
val with_port_of_string :
71+
default:int -> string -> (t * int, [> `Msg of string ]) result
72+
(** [with_port_of_string ~default s] is the address {!t} represented by the
73+
human-readble IPv4 address [s] with a possibly port [:<port>] (otherwise,
74+
we take the [default] value). *)
75+
7076
val to_string : t -> string
7177
(** [to_string ipv4] is the dotted decimal string representation of [ipv4],
7278
i.e. [XXX.XX.X.XXX]. *)
@@ -332,6 +338,16 @@ module V6 : sig
332338
(** Same as [of_string_exn] but returns an option type instead of raising an
333339
exception. *)
334340

341+
val with_port_of_string :
342+
default:int -> string -> (t * int, [> `Msg of string ]) result
343+
(** [with_port_of_string ~default ipv6_string] is the address represented by
344+
[ipv6_string] with a possibly [:<port>] (otherwise, we take the [default]
345+
value). Due to the [':'] separator, the user should expand [ipv6_string]
346+
to let us to consider the last [:<port>] as a port. In other words:
347+
348+
- [::1:8080] returns the IPv6 [::1:8080] with the [default] port
349+
- [0:0:0:0:0:0:0:1:8080] returns [::1] with the port [8080]. *)
350+
335351
val of_string_raw : string -> int ref -> t
336352
(** Same as [of_string_exn] but takes as an extra argument the offset into the
337353
string for reading. *)
@@ -612,6 +628,16 @@ val of_string_raw : string -> int ref -> t
612628
(** Same as [of_string_exn] but takes as an extra argument the offset into the
613629
string for reading. *)
614630

631+
val with_port_of_string :
632+
default:int -> string -> (t * int, [> `Msg of string ]) result
633+
(** [with_port_of_string ~default s] parses [s] as an IPv4 or IPv6 address with
634+
a possible port seperated by a [':'] (if not, we use [default]). For IPv6,
635+
due to the [':'] separator, only a full expansion of the IPv6 plus the port
636+
lets us to interpret the last [:<int>] as the port. In other words:
637+
638+
- [::1:8080] returns the IPv6 [::1:8080] with the [default] port
639+
- [0:0:0:0:0:0:0:1:8080] returns [::1] with the port [8080]. *)
640+
615641
val v4_of_v6 : V6.t -> V4.t option
616642
(** [v4_of_v6 ipv6] is the IPv4 representation of the IPv6 address [ipv6]. If
617643
[ipv6] is not an IPv4-mapped address, None is returned. *)

lib_test/test_ipaddr.ml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,59 @@ let test_string_raw_rt () =
954954
assert_equal ~msg (ts, !c) (result, cursor))
955955
addrs
956956

957+
let test_with_port_of_string () =
958+
let default = 8080 in
959+
let addrs =
960+
[
961+
("127.0.0.1", (Ipaddr.(V4 V4.localhost), default));
962+
("127.0.0.1:8080", (Ipaddr.(V4 V4.localhost), 8080));
963+
("127.0.0.1:4343", (Ipaddr.(V4 V4.localhost), 4343));
964+
("::1", (Ipaddr.(V6 V6.localhost), default));
965+
("0:0:0:0:0:0:0:1:8080", (Ipaddr.(V6 V6.localhost), 8080));
966+
("0:0:0:0:0:0:0:1:4343", (Ipaddr.(V6 V6.localhost), 4343));
967+
]
968+
in
969+
List.iter
970+
(fun (inet_addr, result) ->
971+
match Ipaddr.with_port_of_string ~default inet_addr with
972+
| Ok ((V4 ipv4, port) as result') ->
973+
let result'' = V4.with_port_of_string ~default inet_addr in
974+
let msg =
975+
Format.asprintf "%s <> %a:%d" inet_addr Ipaddr.V4.pp ipv4 port
976+
in
977+
assert_equal ~msg result result';
978+
assert_equal ~msg (Ok (ipv4, port)) result''
979+
| Ok ((V6 ipv6, port) as result') ->
980+
let result'' = V6.with_port_of_string ~default inet_addr in
981+
let msg =
982+
Format.asprintf "%s <> %a:%d" inet_addr Ipaddr.V6.pp ipv6 port
983+
in
984+
assert_equal ~msg result result';
985+
assert_equal ~msg (Ok (ipv6, port)) result''
986+
| Error (`Msg err) ->
987+
assert_failure (Format.asprintf "%s: %s" inet_addr err))
988+
addrs
989+
990+
let test_invalid_with_port_of_string () =
991+
let default = 8080 in
992+
let addrs =
993+
[
994+
"127.0.0.1:"; "127.0.0.1!8080"; "0:0:0:0:0:0:0:1!8080"; "0:0:0:0:0:0:0:1:";
995+
]
996+
in
997+
List.iter
998+
(fun inet_addr ->
999+
match
1000+
( Ipaddr.with_port_of_string ~default inet_addr,
1001+
Ipaddr.V4.with_port_of_string ~default inet_addr,
1002+
Ipaddr.V4.with_port_of_string ~default inet_addr )
1003+
with
1004+
| Error _, Error _, Error _ -> ()
1005+
| _ ->
1006+
assert_failure
1007+
(Format.asprintf "Unexpected valid inet_addr: %S" inet_addr))
1008+
addrs
1009+
9571010
let test_string_raw_rt_bad () =
9581011
let error (s, c) msg c' = ((s, c), (Parse_error (msg, s), c')) in
9591012
let addrs =
@@ -1052,6 +1105,8 @@ let suite =
10521105
"map" >:: test_map;
10531106
"prefix_mem" >:: test_prefix_mem;
10541107
"prefix_subset" >:: test_prefix_subset;
1108+
"with_port" >:: test_with_port_of_string;
1109+
"invalid_with_port" >:: test_invalid_with_port_of_string;
10551110
]
10561111
;;
10571112

0 commit comments

Comments
 (0)