7 ex__url__safe_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

examples/ex_url_safe.c

Functions

Name
int uparse(const char * s, n_url_safe * out)
int main(void )

Attributes

Name
int failures

Defines

Name
CHECK(cond, msg)

Functions Documentation

function uparse

static int uparse(
    const char * s,
    n_url_safe * out
)

function main

int main(
    void 
)

x", &u), "empty host rejected");

Attributes Documentation

variable failures

static int failures = 0;

Macros Documentation

define CHECK

#define CHECK(
    cond,
    msg
)
do {                                       \
    if (!(cond)) {                         \
        n_log(LOG_ERR, "FAIL: %s", (msg)); \
        failures++;                        \
    }                                      \
} while (0)

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_log.h"
#include "nilorea/n_url_safe.h"

#include <string.h>

static int failures = 0;

#define CHECK(cond, msg)                       \
    do {                                       \
        if (!(cond)) {                         \
            n_log(LOG_ERR, "FAIL: %s", (msg)); \
            failures++;                        \
        }                                      \
    } while (0)

static int uparse(const char* s, n_url_safe* out) {
    return n_url_safe_parse(s, strlen(s), out);
}

int main(void) {
    set_log_level(LOG_NOTICE);

    /* URL parse: components */
    {
        n_url_safe u;
        CHECK(uparse("https://e.org/a/b?x=1#frag", &u), "valid https parses");
        CHECK(u.https == 1 && u.port == 443, "https default port");
        CHECK(strcmp(u.host, "e.org") == 0, "host");
        CHECK(strcmp(u.path, "/a/b?x=1") == 0, "path, fragment dropped");

        CHECK(uparse("http://h:8080/", &u) && u.https == 0 && u.port == 8080,
              "http with explicit port");

        /* an IPv6 literal has its brackets stripped */
        CHECK(uparse("https://[2606:4700::1]/x", &u) &&
                  strcmp(u.host, "2606:4700::1") == 0,
              "IPv6 literal brackets stripped");

        /* rejections */
        CHECK(!uparse("ftp://e.org/", &u), "non-http(s) scheme rejected");
        CHECK(!uparse("https://user@e.org/", &u),
              "userinfo rejected (SSRF/phishing)");
        CHECK(!uparse("https:///x", &u), "empty host rejected");
        CHECK(!uparse("https://e.org:99999/", &u), "bad port rejected");
    }

    /* response head parse */
    {
        n_url_safe_head h;
        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...";
        CHECK(n_url_safe_head_parse(r, strlen(r), &h), "response head parses");
        CHECK(h.status == 301, "status 301");
        CHECK(h.content_length == 12, "content-length");
        CHECK(strcmp(h.location, "https://e.org/new") == 0, "location");
        CHECK(strcmp(h.content_type, "text/html") == 0,
              "content-type, params dropped");

        CHECK(!n_url_safe_head_parse("", 0, &h), "empty head rejected");
        CHECK(!n_url_safe_head_parse("\r\n\r", 3, &h),
              "head shorter than CRLFCRLF rejected");
        CHECK(!n_url_safe_head_parse("HTTP/1.1 200 OK\r\nHost: e", 24, &h),
              "partial head returns 0");
    }

    /* redirect resolution */
    {
        char out[N_URL_SAFE_MAX + 1];
        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";
        CHECK(n_url_safe_resolve(base, strlen(base), abs, strlen(abs), out,
                                 sizeof(out)) &&
                  strcmp(out, "https://x.org/y") == 0,
              "absolute redirect");
        CHECK(n_url_safe_resolve(base, strlen(base), rel, strlen(rel), out,
                                 sizeof(out)) &&
                  strcmp(out, "https://x.org/y") == 0,
              "scheme-relative redirect");
        CHECK(n_url_safe_resolve(base, strlen(base), root, strlen(root), out,
                                 sizeof(out)) &&
                  strcmp(out, "https://e.org/abs") == 0,
              "root-relative redirect");
        CHECK(!n_url_safe_resolve(base, strlen(base), bare, strlen(bare), out,
                                  sizeof(out)),
              "bare relative redirect refused");
    }

    /* URL finding in text */
    {
        char out[N_URL_SAFE_MAX + 1];
        const char* t = "see https://e.org/p. thanks";
        CHECK(n_url_safe_find(t, strlen(t), out, sizeof(out)) &&
                  strcmp(out, "https://e.org/p") == 0,
              "find URL, trailing '.' trimmed");
        CHECK(!n_url_safe_find("x https://a@b/", 14, out, sizeof(out)),
              "malformed URL not surfaced");
    }

    if (failures) {
        n_log(LOG_ERR, "ex_url_safe: %d failure(s)", failures);
        return 1;
    }
    n_log(LOG_NOTICE, "ex_url_safe: all checks passed");
    return 0;
}