summaryrefslogtreecommitdiff
path: root/lib/http/url.myr
diff options
context:
space:
mode:
Diffstat (limited to 'lib/http/url.myr')
-rw-r--r--lib/http/url.myr238
1 files changed, 238 insertions, 0 deletions
diff --git a/lib/http/url.myr b/lib/http/url.myr
new file mode 100644
index 0000000..be17d20
--- /dev/null
+++ b/lib/http/url.myr
@@ -0,0 +1,238 @@
+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 == '='
+}