Esercizio - URI, scomposizione, percent-encoding e riferimenti relativi (sul modello della prova pratica)
In questa pagina 6
Testo (sul modello della prova pratica di Reti di Calcolatori, Ing. Informatica UniPD; sintassi da RFC 3986).
Scrivere in C, senza librerie, le funzioni per lavorare con gli URI:
uri_split: scomporrescheme://userinfo@host:porta/percorso?query#frammentonei suoi componenti, distinguendo un componente assente da uno vuoto;percent_encodeepercent_decode: codificare con%HHi byte che non sono caratteri unreserved (lettere, cifre,- . _ ~) e decodificarli, rifiutando le sequenze rotte; verificarecaffè & latte→caff%C3%A8%20%26%20latte;remove_dot_segments: togliere i segmenti.e..da un percorso (RFC 3986 par. 5.2.4), per esempio/a/b/c/./../../g→/a/g;uri_resolve: risolvere un riferimento relativo rispetto a una base (par. 5.2.2) e ricomporre l'URI (par. 5.3);- provare tutto con gli esempi della RFC (base
http://a/b/c/d;p?q).
Teoria: Caching, autenticazione, tipi MIME e URI in HTTPLa cache HTTP riusa le risposte senza contattare il server finche' sono fresche (Cache-Control: max-age, Expires; in mancanza una durata euristica pari a circa il 10% del tempo trascorso da Last-Modified) e, quando sono scadute, le rivalida con una richiesta condizionale (If-None-Match con ETag, If-Modified-Since con Last-Modified) alla quale il server risponde 304 senza corpo; no-store vieta di memorizzare, no-cache obbliga a rivalidare, private esclude le cache condivise. L'autenticazione usa 401 con WWW-Authenticate e la risposta Authorization: Basic (base64 di utente:password, solo sopra TLS) o Digest (hash con nonce); 407 vale per i proxy. Content-Type porta il tipo MIME (tipo/sottotipo; parametri come charset e boundary), Accept e gli altri header Accept-* guidano la negoziazione. Un URI (RFC 3986) e' scheme://userinfo@host:porta/percorso?query#frammento, con percent-encoding %HH per i byte non ammessi e regole per risolvere i riferimenti relativi.Caching, autenticazione, tipi MIME e URI in HTTP →, Esercizio - Parsing di request line, header e URI (sul modello della prova pratica), Richiami di C per la programmazione di rete - memoria, puntatori, struct ed endiannessIn C un programma di rete maneggia byte, non oggetti: un processo ha codice, dati statici, heap e stack; i tipi hanno dimensioni fisse solo se si usano <stdint.h> (uint8_t, uint16_t, uint32_t); i dati che arrivano da un socket sono un buffer di byte con una lunghezza, NON una stringa C terminata da '\0'; i puntatori e l'aritmetica dei puntatori (buf + totale) permettono di riempire un buffer a pezzi; una struct puo' contenere byte di riempimento (padding) per l'allineamento, quindi non si spedisce con write(&s, sizeof s); sulla rete i numeri a piu' byte viaggiano in big endian (network byte order) e si convertono con htons, htonl, ntohs, ntohl, oppure si serializzano a mano con shift e maschere.Richiami di C per la programmazione di rete - memoria, puntatori, struct ed endianness →.
Idea
Un URI è una stringa con una sintassi precisa (RFC 3986), e le trappole stanno nei dettagli:
- un componente può mancare (
http://a/b, nessuna query) oppure essere vuoto (http://a/b?, query vuota) e la ricomposizione deve distinguerli: per questo la struttura ha dei flaghas_query,has_fragment,has_authority,has_scheme; - il percorso può contenere
.e.., che si risolvono prima di usarlo (e prima di concederne l'accesso a un file); - i caratteri non ammessi si scrivono come
%HH, byte per byte della forma UTF-8:èsono due byte (C3 A8) e quindi%C3%A8.
Il programma non apre socket e ha un main con autotest: 41 coppie (riferimento, risultato atteso) dalla tabella degli esempi normali e anomali di RFC 3986 par. 5.4, più i casi di scomposizione e codifica.
remove_dot_segments è l'algoritmo del par. 5.2.4: si scorre il percorso in ingresso e si costruisce l'uscita, un segmento alla volta:
- se l'ingresso comincia con
../o./, si toglie quel prefisso; - se comincia con
/./o è esattamente/., si sostituisce con/; - se comincia con
/../o è/.., si sostituisce con/e si toglie dall'uscita l'ultimo segmento; - se è solo
.o.., si toglie; - altrimenti si sposta il primo segmento (con l'eventuale
/iniziale) dall'ingresso all'uscita.
uri_resolve (par. 5.2.2) in sintesi: se il riferimento ha uno schema, è già assoluto; se ha un'autorità, si prendono schema e basi dalla base e il resto dal riferimento; se il percorso è vuoto si usa quello della base (e la query della base se il riferimento non ne ha una); se il percorso comincia per / si usa quello; altrimenti si fonde con la base (la base fino all'ultima /, più il riferimento) e si rimuovono i segmenti punto.
Codice
/* uri_demo.c - URI secondo RFC 3986: scomposizione, percent-encoding, rimozione dei segmenti "." e "..",
* risoluzione di un riferimento relativo, con autotest sugli esempi della RFC (par. 5.4).
* Compilare e provare: gcc -Wall -Wextra -o uri_demo uri_demo.c && ./uri_demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#undef NDEBUG /* gli assert dell'autotest devono restare attivi anche con -DNDEBUG */
#include <assert.h>
struct uri {
char scheme[16], userinfo[64], host[128], port[8], path[256], query[256], fragment[128];
int has_scheme, has_authority, has_query, has_fragment;
};
/* Scompone scheme:[//[userinfo@]host[:port]]path[?query][#fragment] (RFC 3986, appendice B). */
static int uri_split(const char *s, struct uri *u)
{
memset(u, 0, sizeof *u);
const char *p = s;
/* scheme: lettere, cifre, + - . fino ai ':' ; deve esserci un ':' prima di '/', '?', '#' */
size_t k = strcspn(p, ":/?#");
if (p[k] == ':' && k > 0 && isalpha((unsigned char)p[0]) && k < sizeof u->scheme) {
memcpy(u->scheme, p, k);
u->has_scheme = 1;
p += k + 1;
}
if (p[0] == '/' && p[1] == '/') { /* autorita': fra "//" e il prossimo '/', '?' o '#' */
p += 2;
size_t a = strcspn(p, "/?#");
char auth[256];
if (a >= sizeof auth)
return -1;
memcpy(auth, p, a);
auth[a] = '\0';
u->has_authority = 1;
p += a;
char *at = strrchr(auth, '@');
char *hostpart = auth;
if (at) {
*at = '\0';
snprintf(u->userinfo, sizeof u->userinfo, "%s", auth);
hostpart = at + 1;
}
char *colon = strrchr(hostpart, ':'); /* l'ultimo ':' (e non dentro [IPv6]) separa la porta */
if (colon && !(hostpart[0] == '[' && strchr(colon, ']'))) {
snprintf(u->port, sizeof u->port, "%s", colon + 1);
*colon = '\0';
}
snprintf(u->host, sizeof u->host, "%s", hostpart);
}
size_t pl = strcspn(p, "?#"); /* percorso: fino a '?' o '#' */
if (pl >= sizeof u->path)
return -1;
memcpy(u->path, p, pl);
p += pl;
if (*p == '?') {
size_t q = strcspn(++p, "#");
if (q >= sizeof u->query)
return -1;
memcpy(u->query, p, q);
u->has_query = 1;
p += q;
}
if (*p == '#') {
snprintf(u->fragment, sizeof u->fragment, "%s", p + 1);
u->has_fragment = 1;
}
return 0;
}
/* Algoritmo di RFC 3986 par. 5.2.4: toglie i segmenti "." e ".." da un percorso. */
static void remove_dot_segments(const char *in, char *out, size_t size)
{
char buf[256];
snprintf(buf, sizeof buf, "%s", in);
char *i = buf;
size_t o = 0;
out[0] = '\0';
while (*i) {
if (strncmp(i, "../", 3) == 0) i += 3;
else if (strncmp(i, "./", 2) == 0) i += 2;
else if (strncmp(i, "/./", 3) == 0) i += 2; /* "/./x" -> "/x" */
else if (strcmp(i, "/.") == 0) { i[1] = '\0'; } /* "/." -> "/" */
else if (strncmp(i, "/../", 4) == 0 || strcmp(i, "/..") == 0) {
if (strcmp(i, "/..") == 0) i[1] = '\0'; else i += 3; /* "/../x" -> "/x", "/.." -> "/" */
while (o > 0 && out[o - 1] != '/') /* si toglie l'ultimo segmento gia' in uscita */
o--;
if (o > 0)
o--;
out[o] = '\0';
}
else if (strcmp(i, ".") == 0 || strcmp(i, "..") == 0) *i = '\0';
else { /* altrimenti si sposta un segmento in uscita */
size_t n = strcspn(i + (*i == '/'), "/") + (*i == '/');
if (o + n + 1 > size)
return;
memcpy(out + o, i, n);
o += n;
out[o] = '\0';
i += n;
}
}
}
/* Risoluzione di un riferimento relativo rispetto a una base (RFC 3986 par. 5.2.2). */
static void uri_resolve(const struct uri *base, const struct uri *ref, char *out, size_t size)
{
struct uri t;
memset(&t, 0, sizeof t);
if (ref->has_scheme) {
t = *ref;
remove_dot_segments(ref->path, t.path, sizeof t.path);
} else {
if (ref->has_authority) {
t = *ref;
remove_dot_segments(ref->path, t.path, sizeof t.path);
} else {
if (ref->path[0] == '\0') {
snprintf(t.path, sizeof t.path, "%s", base->path);
t.has_query = ref->has_query ? 1 : base->has_query;
snprintf(t.query, sizeof t.query, "%s", ref->has_query ? ref->query : base->query);
} else {
char merged[512];
if (ref->path[0] == '/') {
snprintf(merged, sizeof merged, "%s", ref->path);
} else if (base->has_authority && base->path[0] == '\0') {
snprintf(merged, sizeof merged, "/%s", ref->path);
} else {
snprintf(merged, sizeof merged, "%s", base->path);
char *slash = strrchr(merged, '/'); /* si tiene la base fino all'ultima '/' */
if (slash) slash[1] = '\0'; else merged[0] = '\0';
strncat(merged, ref->path, sizeof merged - strlen(merged) - 1);
}
remove_dot_segments(merged, t.path, sizeof t.path);
t.has_query = ref->has_query;
snprintf(t.query, sizeof t.query, "%s", ref->query);
}
t.has_authority = base->has_authority;
snprintf(t.userinfo, sizeof t.userinfo, "%s", base->userinfo);
snprintf(t.host, sizeof t.host, "%s", base->host);
snprintf(t.port, sizeof t.port, "%s", base->port);
}
t.has_scheme = base->has_scheme;
snprintf(t.scheme, sizeof t.scheme, "%s", base->scheme);
}
t.has_fragment = ref->has_fragment;
snprintf(t.fragment, sizeof t.fragment, "%s", ref->fragment);
/* ricomposizione (par. 5.3) */
size_t o = 0;
out[0] = '\0';
if (t.has_scheme)
o += (size_t)snprintf(out + o, size - o, "%s:", t.scheme);
if (t.has_authority) {
o += (size_t)snprintf(out + o, size - o, "//");
if (t.userinfo[0])
o += (size_t)snprintf(out + o, size - o, "%s@", t.userinfo);
o += (size_t)snprintf(out + o, size - o, "%s", t.host);
if (t.port[0])
o += (size_t)snprintf(out + o, size - o, ":%s", t.port);
}
o += (size_t)snprintf(out + o, size - o, "%s", t.path);
if (t.has_query)
o += (size_t)snprintf(out + o, size - o, "?%s", t.query);
if (t.has_fragment)
snprintf(out + o, size - o, "#%s", t.fragment);
}
/* Percent-encoding: ogni byte che non e' "unreserved" (lettere, cifre, - . _ ~) diventa %HH.
* I caratteri non ASCII si codificano byte per byte nella loro forma UTF-8. */
static int percent_encode(const char *s, char *out, size_t size)
{
size_t o = 0;
for (; *s; s++) {
unsigned char c = (unsigned char)*s;
if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
if (o + 2 > size) return -1;
out[o++] = (char)c;
} else {
if (o + 4 > size) return -1;
o += (size_t)sprintf(out + o, "%%%02X", c);
}
}
out[o] = '\0';
return 0;
}
static int hexval(int c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
/* Inverso: %HH -> byte. Torna -1 se una sequenza e' rotta. Per i dati di un modulo ('+' = spazio) serve in piu'
* la sostituzione di '+', che NON vale nel percorso di un URI. */
static int percent_decode(const char *s, char *out, size_t size)
{
size_t o = 0;
for (; *s; s++) {
int c = (unsigned char)*s;
if (c == '%') {
int h = hexval((unsigned char)s[1]);
int l = (h >= 0) ? hexval((unsigned char)s[2]) : -1;
if (h < 0 || l < 0) return -1;
c = h * 16 + l;
s += 2;
}
if (o + 1 >= size) return -1;
out[o++] = (char)c;
}
out[o] = '\0';
return 0;
}
int main(void)
{
struct uri u;
char out[512];
/* 1) scomposizione */
assert(uri_split("http://mario:pwd@www.example.com:8080/a/b/../c.html?x=1&y=2#sezione", &u) == 0);
assert(!strcmp(u.scheme, "http") && !strcmp(u.userinfo, "mario:pwd") && !strcmp(u.host, "www.example.com"));
assert(!strcmp(u.port, "8080") && !strcmp(u.path, "/a/b/../c.html") && !strcmp(u.query, "x=1&y=2") && !strcmp(u.fragment, "sezione"));
assert(uri_split("http://[::1]:8080/x", &u) == 0 && !strcmp(u.host, "[::1]") && !strcmp(u.port, "8080"));
assert(uri_split("mailto:mario@example.com", &u) == 0 && !strcmp(u.scheme, "mailto") && !u.has_authority && !strcmp(u.path, "mario@example.com"));
assert(uri_split("/solo/percorso?q", &u) == 0 && !u.has_scheme && !u.has_authority && u.has_query);
assert(uri_split("www.example.com:80", &u) == 0 && !strcmp(u.scheme, "www.example.com")); /* "host:porta" senza // e' ambiguo: si legge come schema */
/* 2) percent-encoding */
assert(percent_encode("caff\xC3\xA8 & latte", out, sizeof out) == 0 && !strcmp(out, "caff%C3%A8%20%26%20latte"));
assert(percent_decode("caff%C3%A8%20%26%20latte", out, sizeof out) == 0 && !strcmp(out, "caff\xC3\xA8 & latte"));
assert(percent_decode("100%", out, sizeof out) == -1);
/* 3) rimozione dei segmenti . e .. */
remove_dot_segments("/a/b/c/./../../g", out, sizeof out);
assert(!strcmp(out, "/a/g"));
remove_dot_segments("mid/content=5/../6", out, sizeof out);
assert(!strcmp(out, "mid/6"));
remove_dot_segments("/../../etc/passwd", out, sizeof out);
assert(!strcmp(out, "/etc/passwd")); /* ".." non esce dalla radice, ma qui e' solo la sintassi */
/* 4) risoluzione di riferimenti relativi: tabella della RFC 3986 par. 5.4, base http://a/b/c/d;p?q */
static const char *table[][2] = {
{"g:h", "g:h"}, {"g", "http://a/b/c/g"}, {"./g", "http://a/b/c/g"}, {"g/", "http://a/b/c/g/"},
{"/g", "http://a/g"}, {"//g", "http://g"}, {"?y", "http://a/b/c/d;p?y"}, {"g?y", "http://a/b/c/g?y"},
{"#s", "http://a/b/c/d;p?q#s"}, {"g#s", "http://a/b/c/g#s"}, {"g?y#s", "http://a/b/c/g?y#s"},
{";x", "http://a/b/c/;x"}, {"g;x", "http://a/b/c/g;x"}, {"g;x?y#s", "http://a/b/c/g;x?y#s"},
{"", "http://a/b/c/d;p?q"}, {".", "http://a/b/c/"}, {"./", "http://a/b/c/"}, {"..", "http://a/b/"},
{"../", "http://a/b/"}, {"../g", "http://a/b/g"}, {"../..", "http://a/"}, {"../../", "http://a/"},
{"../../g", "http://a/g"}, {"../../../g", "http://a/g"}, {"../../../../g", "http://a/g"},
{"/./g", "http://a/g"}, {"/../g", "http://a/g"}, {"g.", "http://a/b/c/g."}, {".g", "http://a/b/c/.g"},
{"g..", "http://a/b/c/g.."}, {"..g", "http://a/b/c/..g"}, {"./../g", "http://a/b/g"},
{"./g/.", "http://a/b/c/g/"}, {"g/./h", "http://a/b/c/g/h"}, {"g/../h", "http://a/b/c/h"},
{"g;x=1/./y", "http://a/b/c/g;x=1/y"}, {"g;x=1/../y", "http://a/b/c/y"},
{"g?y/./x", "http://a/b/c/g?y/./x"}, {"g?y/../x", "http://a/b/c/g?y/../x"},
{"g#s/./x", "http://a/b/c/g#s/./x"}, {"g#s/../x", "http://a/b/c/g#s/../x"},
};
struct uri base, ref;
uri_split("http://a/b/c/d;p?q", &base);
for (size_t i = 0; i < sizeof table / sizeof table[0]; i++) {
uri_split(table[i][0], &ref);
uri_resolve(&base, &ref, out, sizeof out);
if (strcmp(out, table[i][1]) != 0) {
fprintf(stderr, "ERRORE: \"%s\" -> \"%s\" invece di \"%s\"\n", table[i][0], out, table[i][1]);
return 1;
}
}
printf("uri_demo: tutti i test passati (comprese %zu risoluzioni della RFC 3986)\n", sizeof table / sizeof table[0]);
return 0;
}Compilare e provare
$ gcc -Wall -Wextra -o uri_demo uri_demo.c
$ ./uri_demo
uri_demo: tutti i test passati (comprese 41 risoluzioni della RFC 3986)Il programma si ferma al primo caso sbagliato con un messaggio come ERRORE: "g?y/./x" -> "..." invece di "http://a/b/c/g?y/./x". Per aggiungere un caso di scomposizione:
assert(uri_split("ftp://u:p@h.it:21/x", &u) == 0 && !strcmp(u.scheme, "ftp") && !strcmp(u.port, "21"));Per confrontare con uno strumento standard, python3 -c "from urllib.parse import urljoin; print(urljoin('http://a/b/c/d;p?q', '../g'))" stampa http://a/b/g, come uri_resolve.
Spiegazione dei punti chiave
uri_split.
- Lo schema è il tratto prima del primo
:, ma solo se il:compare prima di/,?e#e il primo carattere è una lettera:strcspn(p, ":/?#")cerca il primo delimitatore. Per questomailto:mario@example.comha schemamailto, e anchewww.example.com:80(senza//) è letto come schemawww.example.come percorso80(è la lettura prevista dalla RFC:host:portasenza schema è ambiguo). - L'autorità esiste se subito dopo ci sono due
/: arriva fino al prossimo/,?o#. Al suo interno: l'ultimo@separauserinfo; l'ultimo:(fuori dalle parentesi[ ]di un indirizzo IPv6) separa la porta. - Il percorso arriva fino a
?o#; la query (dopo?) fino a#; il frammento è il resto. Ogni parte controlla di entrare nel buffer, altrimenti-1. has_queryvale 1 anche per una query vuota (?):http://a/b?ehttp://a/bsono diversi.
percent_encode. Per ogni byte: se è alfanumerico o - . _ ~ lo copia; altrimenti scrive % e due cifre esadecimali maiuscole (sprintf("%%%02X", c)). Il cast a unsigned char è indispensabile: un byte oltre 0x7F in un char con segno darebbe un numero negativo e %FFFFFFC3. Si controlla lo spazio residuo prima di ogni scrittura.
percent_decode. Per ogni % servono due cifre esadecimali (hexval restituisce -1 altrimenti); l'espressione h >= 0 ? hexval(s[2]) : -1 evita di leggere s[2] se s[1] era già il terminatore. Il + non si converte in spazio: vale solo nei dati dei moduli (application/x-www-form-urlencoded), non nel percorso.
remove_dot_segments. Lavora su una copia (buf) e su un indice di uscita o. Per il caso 3 toglie dall'uscita l'ultimo segmento: indietreggia fino al / precedente (while (o > 0 && out[o - 1] != '/') o--) e poi elimina anche quello. I casi si controllano nell'ordine della RFC (../, ./, /./, /., /../, /.., ., ..): l'ordine conta, perché /./ e /../ contengono i prefissi più corti. Un .. in testa non può uscire dalla radice: /../../etc/passwd diventa /etc/passwd (è solo sintassi: per un server di file la protezione è un'altra cosa, Esercizio - Parsing di request line, header e URI (sul modello della prova pratica)).
uri_resolve. Segue passo passo il par. 5.2.2, usando i flag per distinguere "assente" da "vuoto": per esempio, per ?y (riferimento con percorso vuoto e query) il risultato prende percorso e autorità della base ma la query del riferimento: http://a/b/c/d;p?y; per #s prende anche la query della base: http://a/b/c/d;p?q#s. La fusione (merge) prende la base fino all'ultima / e vi accoda il percorso del riferimento: per g e base /b/c/d;p si ottiene /b/c/g. La ricomposizione concatena schema:, //userinfo@host:porta, percorso, ?query, #frammento, ciascuno solo se presente.
Gli autotest. Verificano la scomposizione (anche con IPv6 e mailto:), la codifica e la decodifica, tre casi di rimozione dei segmenti punto e tutte le 41 risoluzioni della RFC (normali e anomale, come ../../../g → http://a/g, g?y/./x che lascia intatti i punti dentro la query, .g e ..g che non sono segmenti speciali).
Errori tipici
- Confondere componente assente e componente vuoto: una ricomposizione senza flag trasforma
http://a/b?inhttp://a/b. - Cercare lo schema senza controllare che i
:vengano prima di/: un percorso come/a:bnon ha schema. - Prendere il primo
:dell'autorità come separatore della porta (un indirizzo IPv6 ne contiene molti): si usa l'ultimo, fuori dalle parentesi quadre. - Codificare con
%i bytecharcon segno senza il cast aunsigned char. - Decodificare il
+come spazio nel percorso, o decodificare due volte (%252e). - Rimuovere i segmenti punto dopo aver aperto il file, invece che prima.
- Nella fusione dei percorsi, tenere la base fino al segmento finale invece che fino all'ultima
/. - Pensare che il frammento (
#...) vada al server: non si invia mai.
Varianti per esercitarsi
- Normalizzare un URI: schema e host in minuscolo, porta predefinita tolta (
:80perhttp), segmenti punto rimossi, percent-encoding portato a maiuscolo. - Confrontare due URI come equivalenti dopo la normalizzazione.
- Scomporre la query in coppie
chiave=valorecon decodifica (+e%XXcome nei moduli). - Usare
uri_resolveper scrivere un piccolo crawler: dato il testo di una pagina HTML, estrarre i link e risolverli rispetto all'URL della pagina.
Versione ripasso
- Testo.
uri_split(scheme://userinfo@host:porta/percorso?query#frammento, assente <> vuoto),percent_encode/percent_decode,remove_dot_segments(RFC 3986 par. 5.2.4),uri_resolve(par. 5.2.2) con le 41 risoluzioni della RFC (basehttp://a/b/c/d;p?q). - Struttura.
struct uricon i componenti e i flaghas_scheme,has_authority,has_query,has_fragment: la ricomposizione distinguehttp://a/b?(query vuota) dahttp://a/b. uri_split. Schema = prima di:se il:viene prima di/,?,#e il primo carattere è una lettera; autorità se segue//(fino a/,?,#); ultimo@=userinfo; ultimo:fuori da[ ]= porta; percorso fino a?/#; query fino a#; frammento = resto.- Percent-encoding. Unreserved: lettere, cifre,
- . _ ~; altrimenti%+ 2 cifre esadecimali maiuscole del byte (cast aunsigned char);caffè & latte->caff%C3%A8%20%26%20latte. Decodifica: due cifre dopo%(-1altrimenti);+non è spazio nel percorso. remove_dot_segments(ordine della RFC): (1)../o./in testa: tolti; (2)/./->/,/.->/; (3)/../o/..->/e si toglie l'ultimo segmento dall'uscita; (4).o..soli: tolti; (5) altrimenti si sposta un segmento in uscita./a/b/c/./../../g->/a/g;../../../gnon supera la radice.
else if (strncmp(i, "/../", 4) == 0 || strcmp(i, "/..") == 0) {
if (strcmp(i, "/..") == 0) i[1] = '\0'; else i += 3;
while (o > 0 && out[o - 1] != '/') o--; /* via l'ultimo segmento */
if (o > 0) o--; out[o] = '\0'; }uri_resolve. Schema nel riferimento: assoluto. Autorità nel riferimento: base solo per lo schema. Percorso vuoto: percorso della base (e query della base se il riferimento non ne ha). Percorso che inizia per/: quello. Altrimenti merge = base fino all'ultima/+ riferimento, poiremove_dot_segments. Esempi:g->http://a/b/c/g;?y->http://a/b/c/d;p?y;#s->http://a/b/c/d;p?q#s;//g->http://g;../../g->http://a/g;g?y/./xlascia intatti i punti nella query.- Prova.
gcc -Wall -Wextra -o uri_demo uri_demo.c && ./uri_demo->tutti i test passati (comprese 41 risoluzioni della RFC 3986); confronto conurllib.parse.urljoin. - Codice essenziale (le funzioni centrali, senza commenti):
static int uri_split(const char *s, struct uri *u)
{
memset(u, 0, sizeof *u);
const char *p = s;
size_t k = strcspn(p, ":/?#");
if (p[k] == ':' && k > 0 && isalpha((unsigned char)p[0]) && k < sizeof u->scheme) {
memcpy(u->scheme, p, k);
u->has_scheme = 1;
p += k + 1;
}
if (p[0] == '/' && p[1] == '/') {
p += 2;
size_t a = strcspn(p, "/?#");
char auth[256];
if (a >= sizeof auth)
return -1;
memcpy(auth, p, a);
auth[a] = '\0';
u->has_authority = 1;
p += a;
char *at = strrchr(auth, '@');
char *hostpart = auth;
if (at) {
*at = '\0';
snprintf(u->userinfo, sizeof u->userinfo, "%s", auth);
hostpart = at + 1;
}
char *colon = strrchr(hostpart, ':');
if (colon && !(hostpart[0] == '[' && strchr(colon, ']'))) {
snprintf(u->port, sizeof u->port, "%s", colon + 1);
*colon = '\0';
}
snprintf(u->host, sizeof u->host, "%s", hostpart);
}
size_t pl = strcspn(p, "?#");
if (pl >= sizeof u->path)
return -1;
memcpy(u->path, p, pl);
p += pl;
if (*p == '?') {
size_t q = strcspn(++p, "#");
if (q >= sizeof u->query)
return -1;
memcpy(u->query, p, q);
u->has_query = 1;
p += q;
}
if (*p == '#') {
snprintf(u->fragment, sizeof u->fragment, "%s", p + 1);
u->has_fragment = 1;
}
return 0;
}
static int percent_encode(const char *s, char *out, size_t size)
{
size_t o = 0;
for (; *s; s++) {
unsigned char c = (unsigned char)*s;
if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
if (o + 2 > size) return -1;
out[o++] = (char)c;
} else {
if (o + 4 > size) return -1;
o += (size_t)sprintf(out + o, "%%%02X", c);
}
}
out[o] = '\0';
return 0;
}
static int percent_decode(const char *s, char *out, size_t size)
{
size_t o = 0;
for (; *s; s++) {
int c = (unsigned char)*s;
if (c == '%') {
int h = hexval((unsigned char)s[1]);
int l = (h >= 0) ? hexval((unsigned char)s[2]) : -1;
if (h < 0 || l < 0) return -1;
c = h * 16 + l;
s += 2;
}
if (o + 1 >= size) return -1;
out[o++] = (char)c;
}
out[o] = '\0';
return 0;
}- Errori tipici: assente e vuoto confusi; schema cercato senza controllare che
:preceda/; primo:dell'autorità preso per la porta (IPv6);unsigned chardimenticato nella codifica;+come spazio nel percorso o doppia decodifica; punti rimossi dopo l'apertura del file; merge sulla base intera invece che fino all'ultima/.