33 return (c >=
'A' && c <=
'Z') ? c + 32 : c;
38static int ci_prefix(
const char* s,
const char* lit) {
40 for (i = 0; lit[i]; i++)
41 if (
lc((
unsigned char)s[i]) != (
unsigned char)lit[i])
47static int name_eq(
const char* s,
size_t n,
const char* lit) {
49 for (i = 0; i < n; i++) {
52 if (
lc((
unsigned char)s[i]) != (
unsigned char)lit[i])
58static int parse_port(
const char* s,
size_t n, uint16_t* out) {
63 for (i = 0; i < n; i++) {
64 if (s[i] <
'0' || s[i] >
'9')
66 v = v * 10u + (
unsigned long)(s[i] -
'0');
80 memset(out, 0,
sizeof(*out));
96 while (i < len && url[i] !=
'/' && url[i] !=
'?' && url[i] !=
'#') {
97 if (url[i] ==
'@' || (
unsigned char)url[i] <= 0x20)
103 const char* auth = url + hs;
104 size_t alen = he - hs, hostlen, k;
108 if (auth[0] ==
'[') {
110 while (rb < alen && auth[rb] !=
']')
112 if (rb >= alen || rb == 1)
117 if (auth[rb + 1] !=
':' ||
123 for (k = 0; k < alen; k++)
124 if (auth[k] ==
':') {
136 for (k = 0; k < hostlen; k++) {
137 unsigned char c = (
unsigned char)host[k];
138 if (c <= 0x20 || c ==
'/' || c ==
'\\' || c ==
'@')
141 memcpy(out->
host, host, hostlen);
142 out->
host[hostlen] =
'\0';
146 size_t oi = 0, k = he;
147 if (k >= len || url[k] ==
'#') {
153 out->
path[oi++] =
'/';
154 for (; k < len && url[k] !=
'#'; k++) {
155 if ((
unsigned char)url[k] <= 0x20)
157 if (oi + 1 >=
sizeof(out->
path))
159 out->
path[oi++] = url[k];
161 out->
path[oi] =
'\0';
162 if (out->
path[0] !=
'/')
174 for (i = 0; i < n; i++) {
175 if (s[i] <
'0' || s[i] >
'9')
177 if (v > (
long long)1 << 50)
179 v = v * 10 + (s[i] -
'0');
185static int has_token(
const char* s,
size_t n,
const char* tok) {
186 size_t i = 0, tl = strlen(tok);
189 while (i < n && (s[i] ==
' ' || s[i] ==
'\t' || s[i] ==
','))
192 while (j < n && s[j] !=
',')
196 while (e > i && (s[e - 1] ==
' ' || s[e - 1] ==
'\t'))
198 if (e - i == tl &&
name_eq(s + i, tl, tok))
207static void copy_clean(
char* out,
size_t cap,
const char* v,
size_t n) {
211 for (i = 0; i < n && oi + 1 < cap; i++)
212 if ((
unsigned char)v[i] >= 0x20)
218static void copy_mime(
char* out,
size_t cap,
const char* v,
size_t n) {
222 for (i = 0; i < n && v[i] !=
';' && oi + 1 < cap; i++) {
223 unsigned char c = (
unsigned char)v[i];
225 out[oi++] = (char)
lc(c);
231 size_t scan, i, line_start, sl, p;
234 memset(out, 0,
sizeof(*out));
246 for (i = 0; i + 3 < scan; i++)
247 if (buf[i] ==
'\r' && buf[i + 1] ==
'\n' && buf[i + 2] ==
'\r' &&
248 buf[i + 3] ==
'\n') {
256 while (sl + 1 < out->
head_len && !(buf[sl] ==
'\r' && buf[sl + 1] ==
'\n'))
261 while (p < sl && buf[p] !=
' ')
263 while (p < sl && buf[p] ==
' ')
265 if (p + 3 > sl || buf[p] <
'0' || buf[p] >
'9' || buf[p + 1] <
'0' ||
266 buf[p + 1] >
'9' || buf[p + 2] <
'0' || buf[p + 2] >
'9')
269 (buf[p] -
'0') * 100 + (buf[p + 1] -
'0') * 10 + (buf[p + 2] -
'0');
272 while (line_start + 1 < out->
head_len) {
273 size_t le = line_start, colon;
275 !(buf[
le] ==
'\r' && buf[
le + 1] ==
'\n'))
277 if (
le == line_start)
280 while (colon <
le && buf[colon] !=
':')
283 const char* name = buf + line_start;
284 size_t nlen = colon - line_start;
285 size_t vs = colon + 1, vlen;
287 while (vs <
le && (buf[vs] ==
' ' || buf[vs] ==
'\t'))
291 while (vlen > 0 && (val[vlen - 1] ==
' ' || val[vlen - 1] ==
'\t'))
293 if (
name_eq(name, nlen,
"location"))
295 else if (
name_eq(name, nlen,
"content-type"))
298 else if (
name_eq(name, nlen,
"content-length"))
300 else if (
name_eq(name, nlen,
"transfer-encoding"))
308int n_url_safe_resolve(
const char* base,
size_t base_len,
const char* loc,
size_t loc_len,
char* out,
size_t cap) {
310 if (!base || !loc || !out || cap == 0)
314 if ((loc_len >= 7 &&
ci_prefix(loc,
"http://")) ||
315 (loc_len >= 8 &&
ci_prefix(loc,
"https://"))) {
316 if (loc_len + 1 > cap)
318 memcpy(out, loc, loc_len);
325 scheme = b.
https ?
"https" :
"http";
326 if (loc_len >= 2 && loc[0] ==
'/' && loc[1] ==
'/') {
327 n = snprintf(out, cap,
"%s:%.*s", scheme, (
int)loc_len, loc);
328 }
else if (loc_len >= 1 && loc[0] ==
'/') {
330 n = snprintf(out, cap,
"%s://%s%.*s", scheme, b.
host,
333 n = snprintf(out, cap,
"%s://%s:%u%.*s", scheme, b.
host,
334 (
unsigned)b.
port, (
int)loc_len, loc);
338 if (n < 0 || (
size_t)n >= cap)
346 if (!text || !out || cap == 0)
349 for (i = 0; i < len; i++) {
352 if ((text[i] !=
'h' && text[i] !=
'H') || i + 7 > len)
355 !(i + 8 <= len &&
ci_prefix(text + i,
"https://")))
360 unsigned char c = (
unsigned char)text[e];
361 if (c <= 0x20 || c ==
'<' || c ==
'>' || c ==
'"' || c ==
'\\' ||
367 while (e > i && strchr(
".,;:!?)]}'\"", text[e - 1]))
369 if (e > i && (e - i) < cap) {
370 memcpy(out, text + i, e - i);
376 i = e > i ? e - 1 : i;
static uint64_t le(const unsigned char *p, int n)
read a little endian unsigned value
static void copy_clean(char *out, size_t cap, const char *v, size_t n)
static int parse_port(const char *s, size_t n, uint16_t *out)
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
static void copy_mime(char *out, size_t cap, const char *v, size_t n)
static int ci_prefix(const char *s, const char *lit)
static long long parse_clen(const char *s, size_t n)
static int has_token(const char *s, size_t n, const char *tok)
static int name_eq(const char *s, size_t n, const char *lit)
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.
size_t head_len
bytes through the terminating CRLFCRLF
int chunked
Transfer-Encoding: chunked present.
#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
#define N_URL_SAFE_HEAD_MAX
Largest response head (status line + headers, through CRLFCRLF) accepted.
int https
1 = https (default port 443), 0 = http (80)
#define N_URL_SAFE_HOST_MAX
Longest hostname / IP literal accepted (RFC 1035 limit).
The parts of an absolute http(s) URL.
The response-head fields a fetcher acts on.