shithub: mc

ref: ddb327c92ebe3f96c5c816ec61996440551de401
dir: /lib/std/ipparse.myr/

View raw version
use "chartype"
use "die"
use "fmt"
use "intparse"
use "option"
use "slcp"
use "sleq"
use "slfill"
use "strfind"
use "striter"
use "types"

 /* FIXME: needed for decls which should be pulled in as hidden */
use "hasprefix"
use "utf"

pkg std =

	type netaddr = union
		`Ipv4	byte[4]
		`Ipv6	byte[16]
	;;

	const ipparse	: (ip : byte[:]	-> option(netaddr))
	const ip4parse	: (ip : byte[:] -> option(netaddr))
	const ip6parse	: (ip : byte[:] -> option(netaddr))
;;

const ipparse = {ip
	match strfind(ip, ":")
	| `Some _:	-> ip6parse(ip)
	| `None:	-> ip4parse(ip)
	;;
}

const ip4parse = {ip
	var a, b, c, d
	var ok

	(a, ip, ok) = num(ip, 0, 255, 10, '.', true)
	(ip, ok) = delim(ip, '.', ok)
	(b, ip, ok) = num(ip, 0, 255, 10, '.', ok)
	(ip, ok) = delim(ip, '.', ok)
	(c, ip, ok) = num(ip, 0, 255, 10, '.', ok)
	(ip, ok) = delim(ip, '.', ok)
	(d, ip, ok) = num(ip, 0, 255, 10, '.', ok)

	if ok && ip.len == 0
		-> `Some (`Ipv4 [a, b, c, d])
	else
		-> `None
	;;
}

const ip6parse = {ip
	var val : byte[16]
	var expand, split
	var v, ok
	var i, nseg

	ok = true
	expand = false
	split = 0
	if ip.len > 2 && std.sleq(ip[:2], "::")
		expand = true
		split = 0
	;;
	nseg = 0
	for i = 0; ip.len > 0 && ok; i++
		/* parse 'num' segment */
		(v, ip, ok) = num(ip, 0, 65536, 16, ':', ok)

		/* pack it into the bytes */
		val[i*2] = ((v & 0xff00) >> 8 : byte)
		val[i*2 + 1] = (v & 0xff : byte)

		nseg++
		if ip.len == 0 || nseg == 8
			break
		;;

		(ip, ok) = delim(ip, ':', ok)
		/* only one '::' allowed once */
		if ip.len > 0 && ip[0] == (':' : byte) && !expand
			(ip, ok) = delim(ip, ':', ok)
			expand = true
			split = i
		;;
	;;

	if ok && ip.len == 0
		if expand
			expandsplit(val[:], split, i)
		elif nseg != 8
			-> `None
		;;
		-> `Some `Ipv6 val
	else
		-> `None
	;;
}

/* take "a:b::c:d" and expand it to "a:b:0:0:...:0:c:d" */
const expandsplit = {ip, split, len
	var width

	width = 16 - len
	std.slcp(ip[split:len], ip[split+width:len+width])
	std.slfill(ip[len:len+width], 0)
}

const delim = {ip, sep, ok
	if ip.len > 0 && ip[0] == (sep : byte)
		-> (ip[1:], ok)
	else
		-> ("", false)
	;;
}

generic num = {ip, lo, hi, base, sep, ok -> (@a::(numeric,integral), byte[:], bool)
	var len

	if !ok
		-> (0, "", false)
	;;

	for len = 0; len < ip.len; len++
		if ip[len] == (sep : byte)
			break
		;;
	;;
	match intparsebase(ip[:len], base)
	| `std.Some v:
		if v < lo || v > hi
			-> (0, "", false)
		;;
		-> ((v : @a::(numeric,integral)), ip[len:], true)
	| `std.None:
		-> (0, "", false)
	;;
}