shithub: mc

ref: 74d91a0021de012908cfdb35fb61a1473a376130
dir: /lib/http/parse.myr/

View raw version
use std
use bio

use "types"

pkg http =
	pkglocal const parsereq		: (s : session# -> std.result(req#, err))
	pkglocal const freereq 		: (r : req# -> void)
	pkglocal const parseresp	: (s : session#, r : resp# -> bool)
	pkglocal const parsechunksz	: (s : session# -> std.result(std.size, err))
	pkglocal const parsenumber	: (str : byte[:]#, base : int -> std.option(int))
;;

const strcaseeq = {a : byte[:], b : byte[:] -> bool
	var ca, cb

	while a.len == 0 || b.len == 0
		(ca, a) = std.charstep(a)
		(cb, b) = std.charstep(b)
		if std.tolower(ca) != std.tolower(cb)
			-> false
		;;
	;;
	-> a.len == b.len
}

const parsereq = {s
	var r, err

	r.hdrs = [][:]
	r.url = std.mk([
		.schema=`Http,
		.host=std.sldup(s.host),
		.port=s.port,
		.path="",
		.params=[][:],
	])

	match bio.readln(s.f)
	| `std.Err e:
		err = `Econn
		goto error
	| `std.Ok ln:
		match parsereqstatus(s, r, ln)
		| `std.Ok void:
		| `std.Err e:
			std.slfree(ln)
			err = e
			-> `std.Err e
		;;
		std.slfree(ln)
	;;

	while true
		match bio.readln(s.f)
		| `std.Err e:	
			err = `Econn
			goto error
		| `std.Ok ln:
			if std.strstrip(ln).len == 0
				std.slfree(ln)
				break
			;;
			match parsehdr(s, ln)
			| `std.Ok kvp:
				std.slpush(&r.hdrs, kvp)
			| `std.Err e:
				std.slfree(ln)
				-> `std.Err e
			;;
			std.slfree(ln)
		;;
	;;

	-> `std.Ok std.mk(r)
:error
	-> `std.Err err
}

const freereq = {r
	for hdr : r.hdrs
		std.slfree(hdr.0)
		std.slfree(hdr.1)
	;;
	std.slfree(r.hdrs)

	for (k, v) : r.url.params
		std.slfree(k)
		std.slfree(v)
	;;
	std.slfree(r.url.params)

	std.slfree(r.url.host)
	std.slfree(r.url.path)
	std.free(r.url)
	std.free(r)
}

const parseresp = {s, r : resp#
	match bio.readln(s.f)
	| `std.Err _:	r.err = `std.Some `Econn
	| `std.Ok ln:
		if !parserespstatus(s, r, ln)
			std.slfree(ln)
			-> false
		;;
		std.slfree(ln)
	;;

	while true
		match bio.readln(s.f)
		| `std.Err e:	r.err = `std.Some `Econn
		| `std.Ok ln:
			if std.strstrip(ln).len == 0
				std.slfree(ln)
				break
			;;
			match parsehdr(s, ln)
			| `std.Ok kvp:
				std.slpush(&r.hdrs, kvp)
			| `std.Err e:
				r.err = `std.Some e
				std.slfree(ln)
				-> false
			;;
			std.slfree(ln)
		;;
	;;

	match getenc(r)
	| `std.Ok `Length:
		r.enc = `Length
		match getlen(r)
		| `std.Some n:
			r.len = n
		| `std.None:
			r.err = `std.Some `Eproto
			-> false
		;;
	| `std.Ok enc:
		r.enc = enc
	| `std.Err e:
		r.err = `std.Some e
		-> false
	;;
	-> true

}

const parsereqstatus = {s, r, ln
	match parseword(&ln)
	| `std.Some "GET":	r.method = `Get
	| `std.Some "HEAD":	r.method = `Head
	| `std.Some "POST":	r.method = `Post
	| `std.Some "DELETE":	r.method = `Delete
	| `std.Some "TRACE":	r.method = `Trace
	| `std.Some "OPTIONS":	r.method = `Options
	| `std.Some _:	-> `std.Err `Eproto
	| `std.None:	-> `std.Err `Eproto
	;;

	match parseword(&ln)
	| `std.Some w:	r.url.path = std.sldup(w)
	| `std.None:	-> `std.Err `Eproto
	;;

	match parseword(&ln)
	| `std.Some "HTTP/1.1":	/* ok */
	| `std.Some w:	std.put("warn: http version '{}'\n", w)
	| `std.None:	-> `std.Err `Eproto
	;;

	ln = std.strfstrip(ln)
	-> `std.Ok void
}

const parserespstatus = {s, r, ln
	/* HTTP/1.1 */
	ln = std.strfstrip(ln)
	if !std.chomp(&ln, "HTTP")
		r.err = `std.Some `Eproto
		-> false
	;;
	if !std.chomp(&ln, "/1.1")
		r.err = `std.Some `Eproto
		-> false
	;;

	ln = std.strfstrip(ln)
	match parsenumber(&ln, 10)
	| `std.Some n:
		r.status = n
	| `std.None:
		r.err = `std.Some `Eproto
		-> false
	;;

	ln = std.strfstrip(ln)
	r.reason = std.sldup(ln)
	-> true
}

const parsehdr = {s, ln
	var key, val

	match std.strfind(ln, ":")
	| `std.Some idx:
		key = std.sldup(std.strstrip(ln[:idx]))
		val = std.sldup(std.strstrip(ln[idx+1:]))
		-> `std.Ok (key, val)
	| `std.None:
		-> `std.Err `Ehdr
	;;
}

const getlen = {r
	match findhdr(r, "Content-Length")
	| `std.Some v:
		match std.intparsebase(v, 10)
		| `std.Some n:	-> `std.Some n
		| `std.None:	-> `std.None
		;;
	| `std.None:
		-> `std.None
	;;
}

const parsechunksz = {s
	var ret, str

	match bio.readln(s.f)
	| `std.Err e:	ret = `std.Err `Econn
	| `std.Ok ln:
		str = ln
		match parsenumber(&str, 16)
		| `std.Some n:	ret = `std.Ok (n : std.size)
		| `std.None:
			ret = `std.Err `Eproto
		;;
		std.slfree(ln)
	;;
	-> ret
}

const getenc = {r
	match findhdr(r, "Transfer-Encoding")
	| `std.None:	-> `std.Ok `Length
	| `std.Some "chunked":	-> `std.Ok `Chunked
	| `std.Some "compress":	-> `std.Ok `Compress
	| `std.Some "deflate":	-> `std.Ok `Deflate
	| `std.Some "gzip":	-> `std.Ok `Gzip
	| `std.Some unknown:	-> `std.Err `Eenc
	;;
}

const findhdr = {r, name
	for (k, v) : r.hdrs
		if strcaseeq(k, name)
			-> `std.Some v
		;;
	;;
	-> `std.None
}

const parseword = {ln
	var w, end

	ln# = std.strfstrip(ln#)
	end = 0
	for var i = 0; i < ln#.len; i++
		if i == ln#.len - 1
			end = i + 1
		elif std.isspace(std.decode(ln#[i:]))
			end = i
			break
		;;
	;;
	if end == 0
		-> `std.None
	else
		w = ln#[:end]
		ln# = ln#[end:]
		-> `std.Some w
	;;
}

const parsenumber = {ln, base
	var n, ok
	var c, s, dig

	n = 0
	s = ln#
	ok = false
	while true
		(c, s) = std.charstep(s)
		dig = std.charval(c, base)
		if dig >= 0 && dig < base
			ok = true
			n *= base
			n += dig
		else
			break
		;;
	;;
	ln# = s
	if ok
		-> `std.Some n
	else
		-> `std.None
	;;
}