implement Get; include "sys.m"; sys: Sys; sprint: import sys; include "draw.m"; include "arg.m"; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "dial.m"; include "http.m"; http: Http; HConn, Url, Req, Rsp: import http; headers: Headers; HeaderMap: import headers; Get: module { init: fn(nil: ref Draw->Context, nil: list of string); }; Client: adt { fd: ref Sys->FD; req: ref Req; rsp: ref Rsp; received: big; length: big; send: fn(fd: ref Sys->FD, req: ref Req): ref Client; read: fn(nil: self ref Client, buf: array of byte, n: int): int; # private bin: ref Bufio->Iobuf; }; Client.send(fd: ref Sys->FD, req: ref Req): ref Client { r := ref *req; r.url = ref (*req.url).mask(Http->Upath | Http->Uquery); sys->fprint(fd, "%s", r.tostring(nil, 1)); if((bin := bufio->fopen(fd, Bufio->OREAD)) == nil) { sys->werrstr(sprint("bufio: %r")); return nil; } (rsp, err) := Rsp.parse(bin); if(err != nil) { sys->werrstr(err); return nil; } length := big -1; if((hdr := rsp.hmap.findall("Content-Length")) != nil) { if((n := len hdr) != 1) { sys->werrstr("unusual number of content-lengths: " + string n); return nil; } length = big hd hdr; } return ref Client(fd, req, rsp, big 0, length, bin); } Client.read(c: self ref Client, buf: array of byte, n: int): int { if(c.length != big -1) { if(c.received >= c.length) return 0; if(c.received + big n >= c.length) n = int (c.length - c.received); } m := c.bin.read(buf, n); if(m > 0) c.received += big m; return m; } init(nil: ref Draw->Context, args: list of string) { sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; http = load Http Http->PATH; http->init(); headers = http->headers; arg := load Arg Arg->PATH; arg->init(args); progname := arg->progname(); arg->setusage(sprint("%s [-ehs] url\n", progname)); printrsp := 0; rspfd := sys->fildes(1); succeed := 0; while((c := arg->opt()) != '\0') { case c { 'e' => printrsp = 1; rspfd = sys->fildes(2); 'h' => printrsp = 1; 's' => succeed = 1; * => arg->usage(); } } args = arg->argv(); if(len args != 1) arg->usage(); arg = nil; urlstr := hd args; url: Url; h: ref Client; dial := load Dial Dial->PATH; for(;;) { (u, err) := Url.parse(urlstr); if(err != nil) { sys->fprint(sys->fildes(2), "%s: bad url: %s\n", progname, err); raise "fail:Url.parse"; } url = Url.relative(url, u); if(url.scheme == nil) { sys->fprint(sys->fildes(2), "%s: no url scheme\n", progname); raise "fail:url scheme"; } if(url.scheme != "http") { sys->fprint(sys->fildes(2), "%s: only http is supported\n", progname); raise "fail:url scheme"; } if(url.host == "") { sys->fprint(sys->fildes(2), "%s: no url host\n", progname); raise "fail:url host"; } addr := "tcp!" + url.host + "!"; if(url.port != nil) addr += url.port; else addr += url.scheme; if((conn := dial->dial(addr, nil)) == nil) { sys->fprint(sys->fildes(2), "%s: dial %s: %r\n", progname, addr); raise "fail:dial"; } req := ref Req("GET", ref url, "HTTP/1.1", ref HeaderMap); host := url.host; if(url.port != nil) host += ":" + url.port; req.hmap.add("Host", host); if((h = Client.send(conn.dfd, req)) == nil) { sys->fprint(sys->fildes(2), "%s: Client.send: %r\n", progname); raise "fail:Client.send"; } if(printrsp) sys->fprint(rspfd, "%s", h.rsp.tostring(0)); if(h.rsp.statuscode < 300 || h.rsp.statuscode >= 400) break; if((urlstr = h.rsp.hmap.find("Location")) == nil) { sys->fprint(sys->fildes(2), "%s: redirect to nowhere\n", progname); raise "fail:redirect"; } } if(!succeed && h.rsp.statuscode >= 400 && h.rsp.statuscode < 600) { err := sprint("%d %s", h.rsp.statuscode, h.rsp.statusmsg); sys->fprint(sys->fildes(2), "%s: %s\n", progname, err); raise "fail:" + err; } buf := array[8192] of byte; while((n := h.read(buf, len buf)) > 0) { if(sys->write(sys->fildes(1), buf, n) != n) { sys->fprint(sys->fildes(2), "%s: write: %r\n", progname); raise "fail:write"; } } if(n < 0) { sys->fprint(sys->fildes(2), "%s: read\n", progname); raise "fail:read"; } }