diff --git a/net/http/do.ha b/net/http/do.ha index c68cb9b..5fa21ca 100644 --- a/net/http/do.ha +++ b/net/http/do.ha @@ -1,5 +1,6 @@ use bufio; use encoding::utf8; +use errors; use fmt; use io; use net::dial; @@ -43,7 +44,6 @@ export fn do(client: *client, req: *request) (response | error) = { yield; }; - // TODO: Improve error handling let resp = response { ... }; const scan = bufio::newscanner_static(conn, buf); read_statusline(&resp, &scan)?; @@ -64,32 +64,52 @@ export fn do(client: *client, req: *request) (response | error) = { fn read_statusline( resp: *response, scan: *bufio::scanner, -) (void | io::error) = { +) (void | error) = { const status = match (bufio::scan_string(scan, "\r\n")) { case let line: const str => yield line; case let err: io::error => return err; case utf8::invalid => - abort(); // TODO + return protoerr; case io::EOF => - abort(); // TODO + return protoerr; }; - // TODO: Error handling const tok = strings::tokenize(status, " "); - const version = strings::next_token(&tok) as str; - const status = strings::next_token(&tok) as str; - const reason = strings::next_token(&tok) as str; - assert(version == "HTTP/1.1"); // TODO + const version = match (strings::next_token(&tok)) { + case let ver: str => + yield ver; + case void => + return protoerr; + }; + + const status = match (strings::next_token(&tok)) { + case let status: str => + yield status; + case void => + return protoerr; + }; + + const reason = match (strings::next_token(&tok)) { + case let reason: str => + yield reason; + case void => + return protoerr; + }; + const (_, version) = strings::cut(version, "/"); const (major, minor) = strings::cut(version, "."); - resp.version = ( strconv::stou(major)!, strconv::stou(minor)!, ); + + if (resp.version.0 > 1) { + return errors::unsupported; + }; + resp.status = strconv::stou(status)!; resp.reason = strings::dup(reason); }; diff --git a/net/http/error.ha b/net/http/error.ha index 9a5bf26..d529bf3 100644 --- a/net/http/error.ha +++ b/net/http/error.ha @@ -4,7 +4,11 @@ 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); +export type error = !(dial::error | io::error | protoerr); + +// An HTTP protocol error occurred, indicating that the remote party is not +// conformant with HTTP semantics. +export type protoerr = void; // Converts an [[error]] to a string. export fn strerror(err: error) const str = { @@ -13,5 +17,7 @@ export fn strerror(err: error) const str = { return dial::strerror(err); case let err: io::error => return io::strerror(err); + case protoerr => + return "HTTP protocol error"; }; }; diff --git a/net/http/header.ha b/net/http/header.ha index ef5e8ff..53c416c 100644 --- a/net/http/header.ha +++ b/net/http/header.ha @@ -64,7 +64,7 @@ export fn write_header(sink: io::handle, head: *header) (size | io::error) = { return z; }; -fn read_header(head: *header, scan: *bufio::scanner) (void | io::error) = { +fn read_header(head: *header, scan: *bufio::scanner) (void | error) = { for (true) { const item = match (bufio::scan_string(scan, "\r\n")) { case let line: const str => @@ -74,15 +74,19 @@ fn read_header(head: *header, scan: *bufio::scanner) (void | io::error) = { case let err: io::error => return err; case utf8::invalid => - abort(); // TODO + return protoerr; }; if (item == "") { break; }; - // TODO: validate field-name let (name, val) = strings::cut(item, ":"); val = strings::trim(val); + if (val == "") { + return protoerr; + }; + // TODO: validate field-name + header_add(head, name, val); }; };