use ascii; use bufio; use encoding::utf8; use fmt; use io; use strings; // List of HTTP headers. // TODO: [](str, []str) export type header = [](str, str); // Adds a given HTTP header, which may be added more than once. The name should // be canonicalized by the caller. export fn header_add(head: *header, name: str, val: str) void = { assert(len(name) >= 1 && len(val) >= 1); let name = ascii::strlower(name); append(head, (name, strings::dup(val))); }; // Sets the value of a given HTTP header, removing any previous values. The name // should be canonicalized by the caller. export fn header_set(head: *header, name: str, val: str) void = { header_del(head, name); header_add(head, name, val); }; // Removes an HTTP header from a list of [[header]]. If multiple headers match // the given name, all matching headers are removed. export fn header_del(head: *header, name: str) void = { for (let i = 0z; i < len(head); i += 1) { if (ascii::strcasecmp(head[i].0, name) == 0) { free(head[i].0); free(head[i].1); delete(head[i]); i -= 1; }; }; }; // 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 (ascii::strcasecmp(key, name) == 0) { return val; }; }; return ""; }; // Frees state associated with an HTTP [[header]]. export fn header_free(head: *header) void = { for (let i = 0z; i < len(head); i += 1) { free(head[i].0); free(head[i].1); }; free(*head); }; // Duplicates a set of HTTP headers. export fn header_dup(head: *header) header = { let new: header = []; for (let i = 0z; i < len(head); i += 1) { const (key, val) = head[i]; header_add(&new, key, val); }; return new; }; // Writes a list of HTTP headers to the provided I/O handle in the HTTP wire // format. export fn write_header(sink: io::handle, head: *header) (size | io::error) = { let z = 0z; for (let i = 0z; i < len(head); i += 1) { const (name, val) = head[i]; z += fmt::fprintf(sink, "{}: {}\r\n", name, val)?; }; return z; }; fn read_header(head: *header, scan: *bufio::scanner) (void | io::error | protoerr) = { for (true) { const item = match (bufio::scan_string(scan, "\r\n")) { case let line: const str => yield line; case io::EOF => break; case let err: io::error => return err; case utf8::invalid => return protoerr; }; if (item == "") { break; }; let (name, val) = strings::cut(item, ":"); val = strings::trim(val); if (val == "") { continue; }; // TODO: validate field-name header_add(head, name, val); }; };