shithub: mc

Download patch

ref: 22d71e9e0471402b4d9e918f4f10683137ee9121
author: Ori Bernstein <ori@eigenstate.org>
date: Sun Jun 19 09:58:58 EDT 2016

Initial commit.

--- /dev/null
+++ b/lib/http/bld.sub
@@ -1,0 +1,14 @@
+bin h =
+	main.myr
+
+	lib http
+;;
+
+lib http =
+	parse.myr
+	types.myr
+	url.myr
+	verbs.myr
+	session.myr
+
+;;
--- /dev/null
+++ b/lib/http/main.myr
@@ -1,0 +1,61 @@
+use std
+use http
+
+const main = {args
+	var data, method, hdr
+	var s, u, r
+	var cmd
+
+	cmd = std.optparse(args, &[
+		.argdesc = "url...",
+		.opts = [
+			[.opt='m', .arg="method", .desc="http method to use"],
+			[.opt='d', .arg="data", .desc="data to put in request body"],
+			[.opt='H', .desc="show headers"],
+		][:]
+	])
+
+	hdr = false
+	method = "get"
+	data = ""
+	for opt in cmd.opts
+		match opt
+		| ('m', m):	method = m
+		| ('d', d):	data = d
+		| ('h', ""):	hdr = true
+		| _:	std.die("unreachable")
+		;;
+	;;
+
+
+	for url in cmd.args
+		u = std.try(http.parseurl(url))
+		std.put("URL: {}\n", u#)
+		s = std.try(http.mksession(u.host))
+
+		match method
+		| "get":	r = http.get(s, u.path)
+		| "head":	r = http.head(s, u.path)
+		| "delete":	r = http.delete(s, u.path)
+		| "trace":	r = http.trace(s, u.path)
+		| "options":	r = http.trace(s, u.path)
+		| "put":	r = http.put(s, u.path, data)
+		| "post":	r = http.post(s, u.path, data)
+		| unknown:	std.fatal("unknown method '{}'\n", unknown)
+		;;
+
+		match r
+		| `std.Ok resp:
+			std.put("status: {}\n", resp.status)
+			for h in resp.hdrs
+				std.put("{}\n", h)
+			;;
+			std.put("{}\n", resp.body)
+			http.freeresp(resp)
+		| `std.Fail e:
+			std.put("{}\n", e)
+		;;
+		http.urlfree(u)
+	;;
+}
+
--- /dev/null
+++ b/lib/http/parse.myr
@@ -1,0 +1,176 @@
+use std
+use bio
+
+use "types"
+
+pkg http =
+	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 parseresp = {s, r
+	match bio.readln(s.f)
+	| `bio.Err e:	r.err = `std.Some `Econn
+	| `bio.Eof:	r.err = `std.Some `Econn
+	| `bio.Ok ln:
+		if !parsestatus(s, r, ln)
+			std.slfree(ln)
+			-> false
+		;;
+		std.slfree(ln)
+	;;
+
+	while true
+		match bio.readln(s.f)
+		| `bio.Err e:	r.err = `std.Some `Econn
+		| `bio.Eof:	r.err = `std.Some `Econn
+		| `bio.Ok ln:
+			if std.strstrip(ln).len == 0
+				break
+			;;
+			if !parsehdr(s, r, ln)
+				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.Fail e:
+		r.err = `std.Some e
+		-> false
+	;;
+	-> true
+
+}
+
+const parsechunksz = {s
+	var ret, str
+
+	match bio.readln(s.f)
+	| `bio.Eof:	ret = `std.Fail `Econn
+	| `bio.Err e:	ret = `std.Fail `Econn
+	| `bio.Ok ln:
+		str = ln
+		match parsenumber(&str, 16)
+		| `std.Some n:	ret = `std.Ok (n : std.size)
+		| `std.None:
+			ret = `std.Fail `Eproto
+		;;
+		std.slfree(ln)
+	;;
+	-> ret
+}
+
+const parsestatus = {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, r, 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.slpush(&r.hdrs, (key, val))
+		-> true
+	| `std.None:
+		r.err = `std.Some `Ehdr
+		-> false
+	;;
+}
+
+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 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.Fail `Eenc
+	;;
+}
+
+const findhdr = {r, name
+	for (k, v) in r.hdrs
+		if std.strcaseeq(k, name)
+			-> `std.Some v
+		;;
+	;;
+	-> `std.None
+}
+
+const parsenumber = {ln, base
+	var n, ok
+	var c, s, dig
+
+	n = 0
+	s = ln#
+	ok = false
+	while true
+		(c, s) = std.strstep(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
+	;;
+}
+
--- /dev/null
+++ b/lib/http/session.myr
@@ -1,0 +1,56 @@
+use std
+use bio
+
+use "types"
+
+pkg http =
+	const mksession	: (host	: byte[:] -> std.result(session#, err))
+	const freesession	: (s : session# -> void)
+
+	pkglocal const ioput	: (s : session#, fmt : byte[:], args : ... -> bool)
+	pkglocal const ioflush	: (s : session# -> void)
+;;
+
+const mksession = {host
+	var s, sess
+
+	s = std.fmt("tcp!{}!80", host)
+	match std.dial(s)
+	| `std.Fail e:	sess = `std.Fail `Econn
+	| `std.Ok fd:	sess = `std.Ok std.mk([
+		.err = false,
+		.ua = std.sldup("Myrfoo HTTP"),
+		.host = std.sldup(host),
+		.f = bio.mkfile(fd, bio.Rw)
+	])
+	;;
+	-> sess
+}
+
+const freesession = {s
+	bio.close(s.f)
+	std.slfree(s.host)
+	std.slfree(s.ua)
+	std.free(s)
+}
+
+const ioput = {s, fmt, args
+	var ap
+
+	if s.err
+		-> false
+	;;
+	ap = std.vastart(&args)
+	std.putv(fmt, &ap)
+	ap = std.vastart(&args)
+	match bio.putv(s.f, fmt, &ap)
+	| `bio.Ok _:	/* nothing */
+	| `bio.Err _:	s.err = true
+	| `bio.Eof:	s.err = true
+	;;
+	-> s.err
+}
+
+const ioflush = {s
+	bio.flush(s.f)
+}
--- /dev/null
+++ b/lib/http/types.myr
@@ -1,0 +1,70 @@
+use std
+use bio
+
+pkg http =
+	type session = struct
+		f	: bio.file#
+		host	: byte[:]
+		ua	: byte[:]
+		err	: bool
+	;;
+
+	type url = struct
+		schema	: schema
+		port	: int
+		host	: byte[:]
+		path	: byte[:]
+	;;
+
+
+	type err = union
+		`Ewat
+		`Eunsupp
+		`Econn
+		`Ehttp int
+		`Ehdr
+		`Eproto
+		`Eshort
+		`Esyntax
+		`Eenc
+	;;
+
+	type schema = union
+		`Http
+		`Https
+	;;
+
+	type method = union
+		`Get
+		`Head
+		`Put
+		`Post
+		`Delete
+		`Trace
+		`Options
+	;;
+
+	type encoding = union
+		`Length
+		`Chunked
+		`Compress
+		`Deflate
+		`Gzip
+	;;
+
+	type req = struct
+		hdrs	: (byte[:], byte[:])[:]
+		method	: method
+	;;
+
+	type resp = struct
+		status	: int
+		hdrs	: (byte[:], byte[:])[:]
+		len	: std.size
+		err	: std.option(err)
+		reason	: byte[:]
+		body	: byte[:]
+		enc	: encoding
+	;;
+
+;;
--- /dev/null
+++ b/lib/http/url.myr
@@ -1,0 +1,140 @@
+use std
+
+use "types"
+use "parse"
+
+pkg http =
+	const parseurl	: (url : byte[:] -> std.result(url#, err))
+	const urlfree	: (url : url# -> void)
+;;
+
+const parseurl = {url
+	var schema, host, path, port
+
+	match parseschema(&url)
+	| `std.Ok s:	schema = s
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	match parsehostname(&url)
+	| `std.Ok h:	host = h
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	match parseport(&url)
+	| `std.Ok p:	port = p
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	match parsepath(&url)
+	| `std.Ok p:	path = p
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	/* todo: params */
+	-> `std.Ok std.mk([
+		.schema=schema,
+		.host=std.sldup(host),
+		.path=std.sldup(path),
+	])
+}
+
+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.Fail `Eunsupp
+	else
+		-> `std.Fail `Eproto
+	;;
+}
+
+const parsehostname = {url
+	if std.chomp(url, "[")
+		-> ipv6hostname(url)
+	else
+		-> hostname(url)
+	;;
+}
+
+const parsepath = {url
+	if url#.len == 0
+		-> `std.Ok "/"
+	elif std.decode(url#) == '/'
+		-> `std.Ok url#
+	else
+		-> `std.Fail `Esyntax
+	;;
+}
+
+const hostname = {url
+	var len, host
+
+	len = 0
+	for c in std.bychar(url#)
+		match c
+		| ':':	break
+		| '/':	break
+		| chr:
+			if ishostchar(chr)
+				len += std.charlen(chr)
+			else
+				-> `std.Fail `Esyntax
+			;;
+		;;
+	;;
+
+	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.Fail `Esyntax
+	| `std.Some idx:
+		ip = url#[:idx]
+		url# = url#[idx+1:]
+		match std.ip6parse(url#[:idx])
+		| `std.Some _:	-> `std.Ok ip
+		| `std.None:	-> `std.Fail `Esyntax
+		;;
+	;;
+}
+
+const parseport = {url
+	if std.chomp(url, ":")
+		match parsenumber(url, 10)
+		| `std.Some n:	-> `std.Ok n
+		| `std.None:	-> `std.Fail `Esyntax
+		;;
+	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 ==  '='
+}
--- /dev/null
+++ b/lib/http/verbs.myr
@@ -1,0 +1,262 @@
+use std
+use bio
+
+use "types"
+use "session"
+use "parse"
+
+pkg http =
+	const get	: (s : session#, path : byte[:]	-> std.result(resp#, err))
+	const head	: (s : session#, path : byte[:] -> std.result(resp#, err))
+	const put	: (s : session#, path : byte[:], data : byte[:] -> std.result(resp#, err))
+	const post	: (s : session#, path : byte[:], data : byte[:] -> std.result(resp#, err))
+	const delete	: (s : session#, path : byte[:] -> std.result(resp#, err))
+	const options	: (s : session#, path : byte[:] -> std.result(resp#, err))
+	const trace	: (s : session#, path : byte[:] -> std.result(resp#, err))
+
+	const freeresp	: (r : resp# -> void)
+;;
+
+const get = {s, path
+	match request(s, `Get, path, [][:], `std.None)
+	| `std.Ok _:	/* nothing */
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	-> response(s, true)
+}
+
+const head = {s, path
+	match request(s, `Head, path, [][:], `std.None)
+	| `std.Ok _:	/* nothing */
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	-> response(s, false)
+}
+
+const put = {s, path, data
+	match request(s, `Put, path, [][:], `std.Some data)
+	| `std.Ok _:	/* nothing */
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	-> response(s, true)
+}
+
+const post = {s, path, data
+	match request(s, `Post, path, [][:], `std.Some data)
+	| `std.Ok _:	/* nothing */
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	-> response(s, true)
+}
+
+const delete = {s, path
+	match request(s, `Delete, path, [][:], `std.None)
+	| `std.Ok _:	/* nothing */
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	-> response(s, true)
+}
+
+const options = {s, path
+	match request(s, `Options, path, [][:], `std.None)
+	| `std.Ok _:	/* nothing */
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	-> response(s, true)
+}
+
+const trace = {s, path
+	match request(s, `Options, path, [][:], `std.None)
+	| `std.Ok _:	/* nothing */
+	| `std.Fail e:	-> `std.Fail e
+	;;
+
+	-> response(s, true)
+}
+
+const response = {s, body
+	var resp
+
+	resp = std.mk([
+		.hdrs = [][:],
+		.len = 0,
+		.err = `std.None,
+		.reason = "",
+		.status = 0,
+		.enc = `Length,
+	])
+
+	if parseresp(s, resp)
+		if !body
+			-> `std.Ok resp
+		else
+			match readbody(s, resp)
+			| `std.Ok buf:	resp.body = buf
+			| `std.Fail e:	-> `std.Fail e
+			;;
+		;;
+	else
+		match resp.err
+		| `std.Some e:	-> `std.Fail e
+		| `std.None:	-> `std.Fail `Ewat
+		;;
+	;;
+	-> `std.Ok resp
+}
+
+const request = {s, method, path, hdrs : (byte[:], byte[:])[:], data
+	/* status */
+	ioput(s, "{} {} HTTP/1.1\r\n", method, path)
+
+	/* headers */
+	ioput(s, "Host: {}\r\n", s.host)
+	ioput(s, "User-Agent: {}\r\n", s.ua)
+	match data
+	| `std.Some d:	ioput(s, "Content-Length: {}\r\n", d.len)
+	| `std.None:	/* nothing to do */
+	;;
+	for (k, v) in hdrs
+		ioput(s, "{}: {}\r\n", k, v)
+	;;
+	ioput(s, "\r\n")
+
+	/* body */
+	match data
+	| `std.None:	/* nothing to do */
+	| `std.Some d:
+		ioput(s, d)
+		ioput(s, "\r\n")
+	;;
+	ioflush(s)
+
+	if s.err
+		-> `std.Fail `Econn
+	else
+		-> `std.Ok void
+	;;
+
+}
+
+const readbody = {s, r -> std.result(byte[:], err)
+	match r.enc
+	| `Length:	-> readlenbody(s, r)
+	| `Chunked:	-> readchunkedbody(s, r)
+	| badenc:	std.fatal("unsupported encoding {}\n", badenc)
+	;;
+}
+
+const readlenbody = {s, r
+	var buf
+
+	buf = ""
+	if r.len == 0
+		-> `std.Ok buf
+	;;
+
+	buf = std.slalloc(r.len)
+	match bio.read(s.f, buf)
+	| `bio.Err e:	goto shortread
+	| `bio.Eof:	goto shortread
+	| `bio.Ok rd:
+		if rd.len != r.len
+			goto shortread
+		;;
+	;;
+	-> `std.Ok buf
+:shortread
+	std.slfree(buf)
+	-> `std.Fail `Eshort
+}
+
+const __init__ = {
+	var m
+
+	m = `Get
+	std.fmtinstall(std.typeof(m), fmtmethod, [][:])
+}
+
+const readchunkedbody = {s, r
+	var buf, len
+
+	buf = ""
+	len = 0
+	while true
+		match parsechunksz(s)
+		| `std.Fail e:
+			std.slfree(buf)
+			-> `std.Fail e
+		| `std.Ok 0:
+			break
+		| `std.Ok sz:
+			std.slgrow(&buf, buf.len + sz)
+			match bio.read(s.f, buf[len:len + sz])
+			| `bio.Eof:
+				std.slfree(buf)
+				-> `std.Fail `Eshort
+			| `bio.Err e:
+				std.slfree(buf)
+				-> `std.Fail `Econn
+			| `bio.Ok str:
+				if str.len != sz
+					std.slfree(buf)
+					-> `std.Fail `Eshort
+				;;
+				len += sz
+				match checkendln(s)
+				| `std.Ok _:	/* nothing */
+				| `std.Fail e:	
+					std.slfree(buf)
+					-> `std.Fail e
+				;;
+			;;
+		;;
+	;;
+	-> `std.Ok buf
+}
+
+const checkendln = {s
+	var r
+
+	match bio.readln(s.f)
+	| `bio.Err e:	r = `std.Fail `Econn
+	| `bio.Eof:	r = `std.Fail `Econn
+	| `bio.Ok crlf:
+		if std.strstrip(crlf).len == 0
+			r = `std.Ok void
+		else
+			r = `std.Fail `Eproto
+		;;
+		std.slfree(crlf)
+	;;
+	-> r
+}
+
+const fmtmethod = {sb, ap, opt
+	var r
+
+	r = std.vanext(ap)
+	match r
+	| `Get: std.sbputs(sb, "GET")
+	| `Head: std.sbputs(sb, "HEAD")
+	| `Put: std.sbputs(sb, "PUT")
+	| `Post: std.sbputs(sb, "POST")
+	| `Delete: std.sbputs(sb, "DELETE")
+	| `Trace: std.sbputs(sb, "TRACE")
+	| `Options: std.sbputs(sb, "OPTIONS")
+	;;
+}
+
+const freeresp = {r
+	for (k, v) in r.hdrs
+		std.slfree(k)
+		std.slfree(v)
+	;;
+	std.slfree(r.reason)
+	std.free(r)
+}