1
Fork 0

Improve support riggings for Transfer-Encoding

This commit is contained in:
Drew DeVault 2023-02-12 11:04:05 +01:00
parent 8edf406c52
commit 0e4277ded9
4 changed files with 98 additions and 30 deletions

View file

@ -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;
};

View file

@ -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";
};

View file

@ -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]].

View file

@ -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
};