Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_url.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#include "nilorea/n_common.h"
28#include "nilorea/n_log.h"
29#include "nilorea/n_network.h"
30#include "nilorea/n_str.h"
31
32#include <stdio.h>
33#include <string.h>
34#include <zlib.h>
35
36/* Compress s with the given zlib window bits (31 = gzip, -15 = raw deflate). */
37static N_STR* gz_compress(const char* s, int window_bits) {
38 z_stream strm;
39 size_t inlen = strlen(s);
40 uLong cap;
41 char* buf;
42 N_STR* r = NULL;
43 size_t outlen;
44 memset(&strm, 0, sizeof(strm));
45 if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, window_bits, 8, Z_DEFAULT_STRATEGY) != Z_OK)
46 return NULL;
47 cap = deflateBound(&strm, (uLong)inlen);
48 buf = malloc(cap);
49 if (!buf) {
50 deflateEnd(&strm);
51 return NULL;
52 }
53 strm.next_in = (Bytef*)s;
54 strm.avail_in = (uInt)inlen;
55 strm.next_out = (Bytef*)buf;
56 strm.avail_out = (uInt)cap;
57 deflate(&strm, Z_FINISH);
58 outlen = cap - strm.avail_out;
59 deflateEnd(&strm);
60 char_to_nstr_ex(buf, (NSTRBYTE)outlen, &r);
61 free(buf);
62 return r;
63}
64
65static int failures = 0;
66
67/* Parse the -V LOG_LEVEL verbosity argument. */
68void process_args(int argc, char** argv) {
69 int opt = 0;
70 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
71 switch (opt) {
72 case 'V':
73 if (!strncmp("LOG_NULL", optarg, 8))
75 else if (!strncmp("LOG_NOTICE", optarg, 10))
77 else if (!strncmp("LOG_INFO", optarg, 8))
79 else if (!strncmp("LOG_ERR", optarg, 7))
81 else if (!strncmp("LOG_DEBUG", optarg, 9))
83 else {
84 fprintf(stderr, "Unknown log level %s\n", optarg);
85 exit(1);
86 }
87 break;
88 case 'v':
89 fprintf(stderr, "ex_url\n");
90 exit(1);
91 case 'h':
92 default:
93 fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
94 break;
95 }
96 }
97}
98
99static void expect_canon(const char* in, const char* want) {
101 if (!got || !got->data || strcmp(got->data, want) != 0) {
102 n_log(LOG_ERR, "canon('%s'): got '%s', expected '%s'", in, got && got->data ? got->data : "(null)", want);
103 failures++;
104 } else {
105 n_log(LOG_INFO, "canon('%s') = '%s' OK", in, got->data);
106 }
107 if (got) free_nstr(&got);
108}
109
110static void expect_resolve(const char* base, const char* ref, const char* want) {
111 N_STR* got = n_url_resolve(base, ref);
112 if (!got || !got->data || strcmp(got->data, want) != 0) {
113 n_log(LOG_ERR, "resolve('%s','%s'): got '%s', expected '%s'", base, ref, got && got->data ? got->data : "(null)", want);
114 failures++;
115 } else {
116 n_log(LOG_INFO, "resolve('%s','%s') = '%s' OK", base, ref, got->data);
117 }
118 if (got) free_nstr(&got);
119}
120
121int main(int argc, char** argv) {
123 process_args(argc, argv);
124
125 /* scheme + host lowercased, default port dropped, dot segments removed */
126 expect_canon("HTTP://Example.COM:80/a/./b/../c?x=1", "http://example.com/a/c?x=1");
127 expect_canon("https://Host:443/", "https://host/");
128 expect_canon("http://host", "http://host/");
129 expect_canon("https://host/a/b/../../c/", "https://host/c/");
130 /* non-default port kept */
131 expect_canon("http://host:8080/p", "http://host:8080/p");
132
133 /* two spellings of the same resource canonicalize identically (dedup) */
134 {
135 N_STR* a = n_url_canonicalize_string("http://EXAMPLE.com/x/./y");
136 N_STR* b = n_url_canonicalize_string("http://example.com:80/x/y");
137 if (!a || !b || strcmp(a->data, b->data) != 0) {
138 n_log(LOG_ERR, "dedup mismatch: '%s' vs '%s'", a && a->data ? a->data : "(null)", b && b->data ? b->data : "(null)");
139 failures++;
140 }
141 if (a) free_nstr(&a);
142 if (b) free_nstr(&b);
143 }
144
145 /* RFC 3986 reference resolution against an absolute base */
146 {
147 const char* base = "http://example.com/dir/page.html?old=1";
148 expect_resolve(base, "other.html", "http://example.com/dir/other.html");
149 expect_resolve(base, "sub/leaf.html", "http://example.com/dir/sub/leaf.html");
150 expect_resolve(base, "/root.html", "http://example.com/root.html");
151 expect_resolve(base, "../up.html", "http://example.com/up.html");
152 expect_resolve(base, "./same.html", "http://example.com/dir/same.html");
153 expect_resolve(base, "?new=2", "http://example.com/dir/page.html?new=2");
154 expect_resolve(base, "#frag", "http://example.com/dir/page.html?old=1");
155 expect_resolve(base, "next.html#frag", "http://example.com/dir/next.html");
156 expect_resolve(base, "//cdn.example.org/x.js", "http://cdn.example.org/x.js");
157 expect_resolve(base, "https://other.test/p", "https://other.test/p");
158 expect_resolve(base, "mailto:a@b.com", "mailto:a@b.com");
159 expect_resolve(base, "", "http://example.com/dir/page.html?old=1");
160 }
161 /* a port in the base authority is preserved through relative resolution */
162 expect_resolve("http://host:8080/a/b", "c", "http://host:8080/a/c");
163
164 /* HTTP status classifiers */
165 {
166 if (n_http_status_class(200) != 2 || n_http_status_class(404) != 4 ||
167 n_http_status_class(599) != 5 || n_http_status_class(99) != 0 ||
168 n_http_status_class(600) != 0) {
169 n_log(LOG_ERR, "n_http_status_class wrong");
170 failures++;
171 }
177 }
178
179 /* HTTP body decompression: gzip and raw-deflate round-trips + identity */
180 {
181 const char* msg = "The quick brown fox jumps over the lazy dog, repeatedly. AAAAAAAAAAAA.";
182 N_STR* gz = gz_compress(msg, 31); /* gzip framing */
183 N_STR* df = gz_compress(msg, -15); /* raw deflate */
184 N_STR* plain = char_to_nstr(msg);
185 N_STR* out = NULL;
186 if (gz && n_http_decompress_body(gz, "gzip", &out) == 0 && out && out->data && strcmp(out->data, msg) == 0) {
187 free_nstr(&out);
188 } else {
189 n_log(LOG_ERR, "gzip decompress round-trip failed");
190 failures++;
191 if (out) free_nstr(&out);
192 }
193 if (df && n_http_decompress_body(df, "deflate", &out) == 0 && out && out->data && strcmp(out->data, msg) == 0) {
194 free_nstr(&out);
195 } else {
196 n_log(LOG_ERR, "deflate decompress round-trip failed");
197 failures++;
198 if (out) free_nstr(&out);
199 }
200 /* identity / NULL encoding passes the body through unchanged */
201 if (plain && n_http_decompress_body(plain, NULL, &out) == 0 && out && out->data && strcmp(out->data, msg) == 0) {
202 free_nstr(&out);
203 } else {
204 n_log(LOG_ERR, "identity passthrough failed");
205 failures++;
206 if (out) free_nstr(&out);
207 }
208 if (gz) free_nstr(&gz);
209 if (df) free_nstr(&df);
210 if (plain) free_nstr(&plain);
211 }
212
213 /* multipart/form-data parse, build round-trip, and boundary extraction */
214 {
215 char bnd[128];
216 const char* wire =
217 "--BkXyZ\r\n"
218 "Content-Disposition: form-data; name=\"field1\"\r\n"
219 "\r\n"
220 "value1\r\n"
221 "--BkXyZ\r\n"
222 "Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n"
223 "Content-Type: text/plain\r\n"
224 "\r\n"
225 "file-content\r\n"
226 "--BkXyZ--\r\n";
227 N_STR* body = char_to_nstr(wire);
228 LIST* parts = n_http_parse_multipart(body, "BkXyZ");
229
230 if (n_http_multipart_boundary("multipart/form-data; boundary=----WebKitX", bnd, sizeof(bnd)) != 0 || strcmp(bnd, "----WebKitX") != 0) {
231 n_log(LOG_ERR, "boundary extraction failed: '%s'", bnd);
232 failures++;
233 }
234 if (!parts || parts->nb_items != 2) {
235 n_log(LOG_ERR, "multipart parse: expected 2 parts, got %zu", parts ? parts->nb_items : (size_t)0);
236 failures++;
237 } else {
240 if (strcmp(p0->name, "field1") != 0 || strcmp(p0->filename, "") != 0 || !p0->body || strcmp(p0->body->data, "value1") != 0) {
241 n_log(LOG_ERR, "multipart part0 wrong: name=%s file=%s body=%s", p0->name, p0->filename, p0->body ? p0->body->data : "(null)");
242 failures++;
243 }
244 if (strcmp(p1->name, "file1") != 0 || strcmp(p1->filename, "a.txt") != 0 || strcmp(p1->content_type, "text/plain") != 0 || !p1->body || strcmp(p1->body->data, "file-content") != 0) {
245 n_log(LOG_ERR, "multipart part1 wrong: name=%s file=%s ct=%s body=%s", p1->name, p1->filename, p1->content_type, p1->body ? p1->body->data : "(null)");
246 failures++;
247 }
248 /* build from the parsed parts, re-parse, and confirm the round-trip */
249 {
250 N_STR* rebuilt = n_http_build_multipart(parts, "BkXyZ");
251 LIST* parts2 = rebuilt ? n_http_parse_multipart(rebuilt, "BkXyZ") : NULL;
252 if (!parts2 || parts2->nb_items != 2) {
253 n_log(LOG_ERR, "multipart round-trip: expected 2 parts");
254 failures++;
255 } else {
257 if (strcmp(q1->name, "file1") != 0 || strcmp(q1->filename, "a.txt") != 0 || strcmp(q1->body->data, "file-content") != 0) {
258 n_log(LOG_ERR, "multipart round-trip mismatch");
259 failures++;
260 }
261 }
262 if (parts2) n_http_multipart_free(&parts2);
263 if (rebuilt) free_nstr(&rebuilt);
264 }
265 }
266 if (parts) n_http_multipart_free(&parts);
267 free_nstr(&body);
268 }
269
270 if (failures) {
271 n_log(LOG_ERR, "ex_url: %d failure(s)", failures);
272 return 1;
273 }
274 n_log(LOG_NOTICE, "ex_url: all checks passed");
275 return 0;
276}
static int failures
int main(void)
void process_args(int argc, char **argv)
Definition ex_common.c:48
static void expect_canon(const char *in, const char *want)
Definition ex_url.c:99
static N_STR * gz_compress(const char *s, int window_bits)
Definition ex_url.c:37
static void expect_resolve(const char *base, const char *ref, const char *want)
Definition ex_url.c:110
void * ptr
void pointer to store
Definition n_list.h:46
LIST_NODE * start
pointer to the start of the list
Definition n_list.h:66
size_t nb_items
number of item currently in the list
Definition n_list.h:61
struct LIST_NODE * next
pointer to the next node
Definition n_list.h:52
Structure of a generic LIST container.
Definition n_list.h:59
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:80
#define LOG_NULL
no log output
Definition n_log.h:46
#define LOG_INFO
informational
Definition n_log.h:82
char * data
the string
Definition n_str.h:63
size_t NSTRBYTE
N_STR base unit.
Definition n_str.h:58
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
N_STR * char_to_nstr(const char *src)
Convert a char into a N_STR, short version.
Definition n_str.c:255
int char_to_nstr_ex(const char *from, NSTRBYTE nboct, N_STR **to)
Convert a char into a N_STR, extended version.
Definition n_str.c:232
A box including a string and his lenght.
Definition n_str.h:61
char * filename
Content-Disposition filename, or "".
Definition n_network.h:792
char * content_type
the part's Content-Type, or ""
Definition n_network.h:793
N_STR * body
the part's raw body bytes (binary-safe), or NULL
Definition n_network.h:794
char * name
Content-Disposition form-field name, or "".
Definition n_network.h:791
N_STR * n_http_build_multipart(LIST *parts, const char *boundary)
build a multipart/form-data body from a LIST of N_HTTP_MULTIPART_PART* using boundary (without "--");...
Definition n_network.c:6049
int n_http_status_class(int status_code)
leading digit of an HTTP status code (1..5), or 0 when outside 100..599
Definition n_network.c:5668
void n_http_multipart_free(LIST **parts)
free a LIST returned by n_http_parse_multipart and NULL the pointer
Definition n_network.c:5850
N_STR * n_url_canonicalize_string(const char *url)
parse and canonicalize a URL string in one call
Definition n_network.c:7395
N_STR * n_url_resolve(const char *base, const char *ref)
resolve a (possibly relative) URL reference against an absolute base
Definition n_network.c:7433
int n_http_status_is_server_error(int status_code)
1 when status_code is a server error (5xx), else 0
Definition n_network.c:5690
int n_http_status_is_client_error(int status_code)
1 when status_code is a client error (4xx), else 0
Definition n_network.c:5686
int n_http_decompress_body(const N_STR *body, const char *encoding, N_STR **out)
decompress an HTTP body per its Content-Encoding ("gzip"/"deflate"; identity/NULL/unknown copies thro...
Definition n_network.c:5751
int n_http_multipart_boundary(const char *content_type, char *out, size_t outsz)
copy the boundary token from a Content-Type value into out (without the leading "--").
Definition n_network.c:5811
int n_http_status_is_informational(int status_code)
1 when status_code is informational (1xx), else 0
Definition n_network.c:5674
int n_http_status_is_success(int status_code)
1 when status_code is success (2xx), else 0
Definition n_network.c:5678
int n_http_status_is_redirect(int status_code)
1 when status_code is a redirect (3xx), else 0
Definition n_network.c:5682
LIST * n_http_parse_multipart(const N_STR *body, const char *boundary)
parse a multipart body (boundary given without "--") into a LIST of N_HTTP_MULTIPART_PART*; free with...
Definition n_network.c:5910
a single part of a parsed multipart/form-data body
Definition n_network.h:790
Common headers and low-level functions & define.
Generic log system.
Network Engine.
N_STR and string function declaration.