ref: 3e9fc44da6d6f27d911211d6b8fbced97c0b4812
dir: /lib/http/url.myr/
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)
}
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.charstep(url#)
| _: -> `std.Err `Egarbled
;;
params = [][:]
for sp : std.bysplit(url#, "&")
if std.bstrsplit(kvp[:], sp, "=").len != 2
-> `std.Err `Egarbled
;;
std.slpush(¶ms, (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 == '='
}