ref: c8a5f3d79cd198facac5491f20249f6945db62b9
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 == '=' }