#include <string.h>
#define CHECK(cond, msg) \
do { \
if (!(cond)) { \
n_log(LOG_ERR, "FAIL: %s", (msg)); \
failures++; \
} \
} while (0)
}
{
CHECK(
uparse(
"https://e.org/a/b?x=1#frag", &u),
"valid https parses");
CHECK(strcmp(u.
path,
"/a/b?x=1") == 0,
"path, fragment dropped");
"http with explicit port");
strcmp(u.
host,
"2606:4700::1") == 0,
"IPv6 literal brackets stripped");
CHECK(!
uparse(
"ftp://e.org/", &u),
"non-http(s) scheme rejected");
"userinfo rejected (SSRF/phishing)");
CHECK(!
uparse(
"https:///x", &u),
"empty host rejected");
CHECK(!
uparse(
"https://e.org:99999/", &u),
"bad port rejected");
}
{
const char* r =
"HTTP/1.1 301 Moved\r\nLocation: https://e.org/new\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Content-Length: 12\r\n\r\nbodybytes...";
"content-type, params dropped");
"head shorter than CRLFCRLF rejected");
"partial head returns 0");
}
{
const char* base = "https://e.org/dir/page";
const char* abs = "https://x.org/y";
const char* rel = "//x.org/y";
const char* root = "/abs";
const char* bare = "rel/path";
sizeof(out)) &&
strcmp(out, "https://x.org/y") == 0,
"absolute redirect");
sizeof(out)) &&
strcmp(out, "https://x.org/y") == 0,
"scheme-relative redirect");
sizeof(out)) &&
strcmp(out, "https://e.org/abs") == 0,
"root-relative redirect");
sizeof(out)),
"bare relative redirect refused");
}
{
const char* t = "see https://e.org/p. thanks";
strcmp(out, "https://e.org/p") == 0,
"find URL, trailing '.' trimmed");
"malformed URL not surfaced");
}
return 1;
}
return 0;
}
static int uparse(const char *s, n_url_safe *out)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
int n_url_safe_resolve(const char *base, size_t base_len, const char *loc, size_t loc_len, char *out, size_t cap)
resolve a redirect Location against a request URL
int n_url_safe_find(const char *text, size_t len, char *out, size_t cap)
find the first http(s) URL in a block of text
int n_url_safe_head_parse(const char *buf, size_t len, n_url_safe_head *out)
parse an HTTP/1.x response head from [buf, buf+len)
int n_url_safe_parse(const char *url, size_t len, n_url_safe *out)
parse an absolute http(s) URL [url, url+len) into a bounded struct
Hardened, bounded URL + HTTP/1.x response-head parsing for a safe fetcher.
char location[1024u+1]
Location (redirects).
long long content_length
Content-Length, or -1 if absent.
#define N_URL_SAFE_MAX
Longest URL / path / Location accepted.
int status
HTTP status code, e.g.
char content_type[127u+1]
mime, params dropped
int https
1 = https (default port 443), 0 = http (80)
The parts of an absolute http(s) URL.
The response-head fields a fetcher acts on.