2024-06-01 16:20:06 +00:00
|
|
|
use ascii;
|
2024-06-01 14:46:01 +00:00
|
|
|
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);
|
2024-06-01 16:20:06 +00:00
|
|
|
let name = ascii::strlower(name);
|
|
|
|
append(head, (name, strings::dup(val)));
|
2024-06-01 14:46:01 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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) {
|
2024-06-01 16:20:06 +00:00
|
|
|
if (ascii::strcasecmp(head[i].0, name) == 0) {
|
2024-06-01 14:46:01 +00:00
|
|
|
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];
|
2024-06-01 16:20:06 +00:00
|
|
|
if (ascii::strcasecmp(key, name) == 0) {
|
2024-06-01 14:46:01 +00:00
|
|
|
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 == "") {
|
2024-06-01 16:20:06 +00:00
|
|
|
continue;
|
2024-06-01 14:46:01 +00:00
|
|
|
};
|
|
|
|
// TODO: validate field-name
|
|
|
|
|
|
|
|
header_add(head, name, val);
|
|
|
|
};
|
|
|
|
};
|