diff --git a/net/http/do.ha b/net/http/do.ha index f787f09..7720dcc 100644 --- a/net/http/do.ha +++ b/net/http/do.ha @@ -44,29 +44,7 @@ export fn do(client: *client, req: *request) (response | error) = { const scan = bufio::newscanner_static(conn, buf); read_statusline(&resp, &scan)?; read_header(&resp.header, &scan)?; - - const cl = header_get(&resp.header, "Content-Length"); - const te = header_get(&resp.header, "Transfer-Encoding"); - if (len(cl) > 1 || len(te) > 1) { - return protoerr; - }; - - if (len(te) == 1) { - abort(); // TODO: Assign transport encoding appropriately - } else { - let length = types::SIZE_MAX; - if (len(cl) == 1) { - length = match (strconv::stoz(cl[0])) { - case let z: size => - yield z; - case => - return protoerr; - }; - }; - const remain = bufio::scan_buffer(&scan); - resp.body = new_identity_reader(conn, remain, length); - }; - + resp.body = new_reader(conn, &resp, &scan)?; return resp; }; diff --git a/net/http/error.ha b/net/http/error.ha index d529bf3..2306aee 100644 --- a/net/http/error.ha +++ b/net/http/error.ha @@ -1,14 +1,15 @@ +use errors; use io; use net::dial; // Errors possible while servicing HTTP requests. Note that these errors are for // errors related to the processing of the HTTP connection; semantic HTTP errors // such as [[STATUS_NOTFOUND]] are not handled by this type. -export type error = !(dial::error | io::error | protoerr); +export type error = !(dial::error | io::error | errors::unsupported | protoerr); // An HTTP protocol error occurred, indicating that the remote party is not // conformant with HTTP semantics. -export type protoerr = void; +export type protoerr = !void; // Converts an [[error]] to a string. export fn strerror(err: error) const str = { @@ -17,6 +18,8 @@ export fn strerror(err: error) const str = { return dial::strerror(err); case let err: io::error => return io::strerror(err); + case errors::unsupported => + return "Unsupported HTTP feature"; case protoerr => return "HTTP protocol error"; }; diff --git a/net/http/header.ha b/net/http/header.ha index 7766e25..c96226f 100644 --- a/net/http/header.ha +++ b/net/http/header.ha @@ -35,16 +35,16 @@ export fn header_del(head: *header, name: str) void = { }; }; -// Retrieves a value, or values, from a header. The empty slice indicates the -// abscence of a header. -export fn header_get(head: *header, name: str) []str = { +// Retrieves a value, or values, from a header. An empty string indicates the +// absence of a header. +export fn header_get(head: *header, name: str) str = { for (let i = 0z; i < len(head); i += 1) { const (key, val) = head[i]; if (key == name) { - return [val]; + return val; }; }; - return []; + return ""; }; // Frees state associated with an HTTP [[header]]. diff --git a/net/http/transport.ha b/net/http/transport.ha index 24a036a..44c1296 100644 --- a/net/http/transport.ha +++ b/net/http/transport.ha @@ -1,5 +1,66 @@ +use errors; +use bufio; use io; use os; +use strconv; +use strings; +use types; + +export fn new_reader( + conn: io::handle, + resp: *response, + scan: *bufio::scanner, +) (*io::stream | errors::unsupported | protoerr) = { + // TODO: Content-Encoding support + const cl = header_get(&resp.header, "Content-Length"); + const te = header_get(&resp.header, "Transfer-Encoding"); + + if (cl != "" || te == "") { + let length = types::SIZE_MAX; + if (cl != "") { + length = match (strconv::stoz(cl)) { + case let z: size => + yield z; + case => + return protoerr; + }; + }; + const remain = bufio::scan_buffer(scan); + return new_identity_reader(conn, remain, length); + }; + + let stream: io::handle = conn; + let buffer: []u8 = bufio::scan_buffer(scan); + const iter = strings::tokenize(te, ","); + for (true) { + const te = match (strings::next_token(&iter)) { + case let tok: str => + yield strings::trim(tok); + case void => + break; + }; + + // XXX: We could add lzw support if someone added it to + // hare-compress + switch (te) { + case "chunked" => + stream = new_chunked_reader(stream, buffer); + buffer = []; + case "deflate" => + abort(); // TODO + case "gzip" => + abort(); // TODO + case => + return errors::unsupported; + }; + }; + + if (!(stream is *io::stream)) { + // Empty Transfer-Encoding header + return protoerr; + }; + return stream as *io::stream; +}; export type identity_reader = struct { vtable: io::stream, @@ -67,3 +128,29 @@ fn identity_read( rd.length -= n; return n; }; + +export type chunked_reader = struct { + vtable: io::stream, + conn: io::handle, + buffer: [os::BUFSIZ]u8, + pending: size, +}; + +fn new_chunked_reader( + conn: io::handle, + buffer: []u8, +) *io::stream = { + abort(); // TODO +}; + +const chunked_reader_vtable = io::vtable { + reader = &chunked_read, + ... +}; + +fn chunked_read( + s: *io::stream, + buf: []u8, +) (size | io::EOF | io::error) = { + abort(); // TODO +};