ref: ceca87556db62e3e6b27e362a0391a0bcc322f3d
dir: /lib/http/parse.myr/
use std use bio use "types" pkg http = pkglocal const parsereq : (s : session# -> std.result(req#, err)) 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 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 ;; }