shithub: mc

ref: 4770743d640a13ae81110b4161f0eb6eff9c0be1
dir: /lib/http/url.myr/

View raw version
use std

use "types"
use "parse"

pkg http =
	const parseurl	: (url : byte[:] -> std.result(url#, err))
	const urlfree	: (url : url# -> void)
;;

const __init__ = {
	var u : url#

	u = u
	std.fmtinstall(std.typeof(u), urlfmt, [
		("p", false)
	][:])
}

const urlfmt = {sb, ap, opts
	var url : url#
	var defaultport
	var sep
	var showhost 

	showhost = true
	url = std.vanext(ap)
	for o : opts
		match o
		| ("p", ""):	showhost = false
		| _:	std.fatal("unknown param\n")
		;;
	;;

	if showhost
		match url.schema
		| `Http:
			std.sbputs(sb, "http://")
			defaultport = 80
		| `Https:
			std.sbputs(sb, "https://")
			defaultport = 443
		;;

		std.sbputs(sb, url.host)
		if url.port != defaultport
			std.sbfmt(sb, ":{}", url.port)
		;;
	;;

	std.sbfmt(sb, url.path)

	if url.params.len > 0
		sep = '?'
		for (k, v) : url.params
			std.sbfmt(sb, "{}{}={}", sep, k, v)
			sep = '&'
		;;
	;;
}

const parseurl = {url
	var schema, host, port, path, params

	match parseschema(&url)
	| `std.Ok s:	schema = s
	| `std.Err e:	-> `std.Err e
	;;

	match parsehostname(&url)
	| `std.Ok h:	host = h
	| `std.Err e:	-> `std.Err e
	;;

	match parseport(&url)
	| `std.Ok p:	port = p
	| `std.Err e:	-> `std.Err e
	;;

	match parsepath(&url)
	| `std.Ok p:	path = p
	| `std.Err e:	-> `std.Err e
	;;

	match parseparams(&url)
	| `std.Ok p:	params = p
	| `std.Err e:	-> `std.Err e
	;;

	/* todo: params */
	-> `std.Ok std.mk([
		.schema=schema,
		.host=std.sldup(host),
		.port=port,
		.path=std.sldup(path),
		.params=params,
	])
}

const urlfree = {url
	std.slfree(url.host)
	std.slfree(url.path)
	std.free(url)
}

const parseschema = {url
	if std.chomp(url, "http://")
		-> `std.Ok `Http
	elif std.chomp(url, "https://")
		-> `std.Err `Eunsupp
	else
		-> `std.Err `Eproto
	;;
}

const parsehostname = {url
	if std.chomp(url, "[")
		-> ipv6hostname(url)
	else
		-> hostname(url)
	;;
}

const parsepath = {url
	var len, p

	if url#.len == 0
		-> `std.Ok "/"
	elif std.decode(url#) == '?'
		-> `std.Ok "/"
	elif std.decode(url#) == '/'
		match std.strfind(url#, "?")
		| `std.Some i:	len = i
		| `std.None:	len = url#.len
		;;
		p = url#[:len]
		url# = url#[len:]
		-> `std.Ok p
	else
		-> `std.Err `Egarbled
	;;
}

const parseparams = {url
	var kvp : byte[:][2]
	var params

	if url#.len == 0
		-> `std.Ok [][:]
	;;

	match std.decode(url#)
	| '?':	(_, url#) = std.strstep(url#)
	| _:	-> `std.Err `Egarbled
	;;

	params = [][:]
	for sp : std.bysplit(url#, "&")
		if std.bstrsplit(kvp[:], sp, "=").len != 2
			-> `std.Err `Egarbled
		;;
		std.slpush(&params, (std.sldup(kvp[0]), std.sldup(kvp[1])))
	;;

	-> `std.Ok params
}

const hostname = {url
	var len, host

	len = 0
	for c : std.bychar(url#)
		match c
		| ':':	break
		| '/':	break
		| '?':	break
		| chr:
			if ishostchar(chr)
				len += std.charlen(chr)
			else
				-> `std.Err `Egarbled
			;;
		;;
	;;

	host = url#[:len]
	url# = url#[len:]
	-> `std.Ok host
}

const ipv6hostname = {url -> std.result(byte[:], err)
	var ip

	match std.strfind(url#, "]")
	| `std.None:	-> `std.Err `Egarbled
	| `std.Some idx:
		ip = url#[:idx]
		url# = url#[idx+1:]
		match std.ip6parse(url#[:idx])
		| `std.Some _:	-> `std.Ok ip
		| `std.None:	-> `std.Err `Egarbled
		;;
	;;
}

const parseport = {url
	if std.chomp(url, ":")
		match parsenumber(url, 10)
		| `std.None:	-> `std.Err `Egarbled
		| `std.Some n:
			if n > 65535
				-> `std.Err `Egarbled
			else
				-> `std.Ok (n : uint16)
			;;
		;;
	else
		-> `std.Ok 80
	;;
}

const ishostchar = {c
	if !std.isascii(c)
		-> false
	else
		-> std.isalnum(c) || unreserved(c) || subdelim(c)
	;;
}

const unreserved = {c
	-> c == '.' || c == '-' || c == '_' || c == '~'
}

const subdelim = {c
	-> c == '.' || c == '-' || c == '_' || c == '~' || c == '!' || \
		c == '$' || c == '&' || c == '\'' || c ==  '(' || c ==  ')' || \
		c ==  '*' || c ==  '+' || c ==  ',' || c ==  ';' || c ==  '='
}