Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_url_safe.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
27
28#include "nilorea/n_log.h"
29#include "nilorea/n_url_safe.h"
30
31#include <string.h>
32
33static int failures = 0;
34
35#define CHECK(cond, msg) \
36 do { \
37 if (!(cond)) { \
38 n_log(LOG_ERR, "FAIL: %s", (msg)); \
39 failures++; \
40 } \
41 } while (0)
42
43static int uparse(const char* s, n_url_safe* out) {
44 return n_url_safe_parse(s, strlen(s), out);
45}
46
47int main(void) {
49
50 /* URL parse: components */
51 {
52 n_url_safe u;
53 CHECK(uparse("https://e.org/a/b?x=1#frag", &u), "valid https parses");
54 CHECK(u.https == 1 && u.port == 443, "https default port");
55 CHECK(strcmp(u.host, "e.org") == 0, "host");
56 CHECK(strcmp(u.path, "/a/b?x=1") == 0, "path, fragment dropped");
57
58 CHECK(uparse("http://h:8080/", &u) && u.https == 0 && u.port == 8080,
59 "http with explicit port");
60
61 /* an IPv6 literal has its brackets stripped */
62 CHECK(uparse("https://[2606:4700::1]/x", &u) &&
63 strcmp(u.host, "2606:4700::1") == 0,
64 "IPv6 literal brackets stripped");
65
66 /* rejections */
67 CHECK(!uparse("ftp://e.org/", &u), "non-http(s) scheme rejected");
68 CHECK(!uparse("https://user@e.org/", &u),
69 "userinfo rejected (SSRF/phishing)");
70 CHECK(!uparse("https:///x", &u), "empty host rejected");
71 CHECK(!uparse("https://e.org:99999/", &u), "bad port rejected");
72 }
73
74 /* response head parse */
75 {
77 const char* r =
78 "HTTP/1.1 301 Moved\r\nLocation: https://e.org/new\r\n"
79 "Content-Type: text/html; charset=utf-8\r\n"
80 "Content-Length: 12\r\n\r\nbodybytes...";
81 CHECK(n_url_safe_head_parse(r, strlen(r), &h), "response head parses");
82 CHECK(h.status == 301, "status 301");
83 CHECK(h.content_length == 12, "content-length");
84 CHECK(strcmp(h.location, "https://e.org/new") == 0, "location");
85 CHECK(strcmp(h.content_type, "text/html") == 0,
86 "content-type, params dropped");
87
88 CHECK(!n_url_safe_head_parse("", 0, &h), "empty head rejected");
89 CHECK(!n_url_safe_head_parse("\r\n\r", 3, &h),
90 "head shorter than CRLFCRLF rejected");
91 CHECK(!n_url_safe_head_parse("HTTP/1.1 200 OK\r\nHost: e", 24, &h),
92 "partial head returns 0");
93 }
94
95 /* redirect resolution */
96 {
97 char out[N_URL_SAFE_MAX + 1];
98 const char* base = "https://e.org/dir/page";
99 const char* abs = "https://x.org/y";
100 const char* rel = "//x.org/y";
101 const char* root = "/abs";
102 const char* bare = "rel/path";
103 CHECK(n_url_safe_resolve(base, strlen(base), abs, strlen(abs), out,
104 sizeof(out)) &&
105 strcmp(out, "https://x.org/y") == 0,
106 "absolute redirect");
107 CHECK(n_url_safe_resolve(base, strlen(base), rel, strlen(rel), out,
108 sizeof(out)) &&
109 strcmp(out, "https://x.org/y") == 0,
110 "scheme-relative redirect");
111 CHECK(n_url_safe_resolve(base, strlen(base), root, strlen(root), out,
112 sizeof(out)) &&
113 strcmp(out, "https://e.org/abs") == 0,
114 "root-relative redirect");
115 CHECK(!n_url_safe_resolve(base, strlen(base), bare, strlen(bare), out,
116 sizeof(out)),
117 "bare relative redirect refused");
118 }
119
120 /* URL finding in text */
121 {
122 char out[N_URL_SAFE_MAX + 1];
123 const char* t = "see https://e.org/p. thanks";
124 CHECK(n_url_safe_find(t, strlen(t), out, sizeof(out)) &&
125 strcmp(out, "https://e.org/p") == 0,
126 "find URL, trailing '.' trimmed");
127 CHECK(!n_url_safe_find("x https://a@b/", 14, out, sizeof(out)),
128 "malformed URL not surfaced");
129 }
130
131 if (failures) {
132 n_log(LOG_ERR, "ex_url_safe: %d failure(s)", failures);
133 return 1;
134 }
135 n_log(LOG_NOTICE, "ex_url_safe: all checks passed");
136 return 0;
137}
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
#define CHECK(cond, msg)
static int uparse(const char *s, n_url_safe *out)
Definition ex_url_safe.c:43
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
Generic log system.
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
Definition n_url_safe.c:308
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
Definition n_url_safe.c:344
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)
Definition n_url_safe.c:230
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
Definition n_url_safe.c:76
Hardened, bounded URL + HTTP/1.x response-head parsing for a safe fetcher.
char host[253u+1]
Definition n_url_safe.h:62
uint16_t port
Definition n_url_safe.h:61
char location[1024u+1]
Location (redirects).
Definition n_url_safe.h:72
long long content_length
Content-Length, or -1 if absent.
Definition n_url_safe.h:69
#define N_URL_SAFE_MAX
Longest URL / path / Location accepted.
Definition n_url_safe.h:50
int status
HTTP status code, e.g.
Definition n_url_safe.h:68
char content_type[127u+1]
mime, params dropped
Definition n_url_safe.h:73
int https
1 = https (default port 443), 0 = http (80)
Definition n_url_safe.h:60
char path[1024u+1]
Definition n_url_safe.h:63
The parts of an absolute http(s) URL.
Definition n_url_safe.h:59
The response-head fields a fetcher acts on.
Definition n_url_safe.h:67