1 ex__http__cookie_8c
gullradriel edited this page 2026-10-25 10:46:33 +01:00

examples/ex_http_cookie.c

Functions

Name
void test_read(void )
reading a Cookie header
void test_build(void )
building Set-Cookie values
int process_args(int argc, char ** argv)
read -V
int main(int argc, char ** argv)
run the checks
void expect_true(const char * what, int ok)
count a failed check
int cookie_is(const char * header, const char * name, const char * want)
the cookie name in header has exactly the value want (NULL: absent)
int builds(const N_HTTP_SET_COOKIE * c, const char * want)
build one Set-Cookie and compare

Attributes

Name
int log_level
log level from -V
int failures
failed checks

Functions Documentation

function test_read

static void test_read(
    void 
)

reading a Cookie header

function test_build

static void test_build(
    void 
)

building Set-Cookie values

function process_args

static int process_args(
    int argc,
    char ** argv
)

read -V

Parameters:

  • argc argument count
  • argv arguments

Return: 0, or -1 on a bad argument

function main

int main(
    int argc,
    char ** argv
)

run the checks

Parameters:

  • argc argument count
  • argv arguments

Return: 0 when every check passes

function expect_true

static void expect_true(
    const char * what,
    int ok
)

count a failed check

Parameters:

  • what the check
  • ok its result
static int cookie_is(
    const char * header,
    const char * name,
    const char * want
)

the cookie name in header has exactly the value want (NULL: absent)

Parameters:

  • header Cookie header value
  • name cookie name
  • want expected value or NULL

Return: non-zero when it matches

function builds

static int builds(
    const N_HTTP_SET_COOKIE * c,
    const char * want
)

build one Set-Cookie and compare

Parameters:

  • c the cookie
  • want expected header value, or NULL when building must fail

Return: non-zero when the outcome is the expected one

Attributes Documentation

variable log_level

static int log_level = 3;

log level from -V

variable failures

static int failures = 0;

failed checks

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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "nilorea/n_common.h"
#include "nilorea/n_http_cookie.h"
#include "nilorea/n_log.h"
#include "nilorea/n_str.h"

static int log_level = LOG_ERR;
static int failures = 0;

static void expect_true(const char* what, int ok) {
    if (!ok) {
        n_log(LOG_ERR, "FAIL: %s", what);
        failures++;
    } else {
        n_log(LOG_INFO, "ok: %s", what);
    }
}

static int process_args(int argc, char** argv) {
    int opt = 0;

    while ((opt = getopt(argc, argv, "V:")) != -1) {
        if (opt == 'V') {
            log_level = atoi(optarg);
        } else {
            return -1;
        }
    }
    return 0;
}

static int cookie_is(const char* header, const char* name, const char* want) {
    size_t vl = 99;
    const char* v = n_http_cookie_get(header, strlen(header), name, &vl);
    if (!want)
        return v == NULL && vl == 0;
    return v && vl == strlen(want) && memcmp(v, want, vl) == 0;
}

static void test_read(void) {
    const char* h = "a=first; b=x%41;a=second; c=\"q\"; bad name=1; =v; d;  e = spaced ;;f=";

    expect_true("first occurrence wins", cookie_is(h, "a", "first"));
    expect_true("both occurrences counted", n_http_cookie_count(h, strlen(h), "a") == 2);
    expect_true("value kept raw (percent escape)", cookie_is(h, "b", "x%41"));
    expect_true("value kept raw (quotes)", cookie_is(h, "c", "\"q\""));
    expect_true("pair with a non-token name skipped", cookie_is(h, "bad name", NULL) && cookie_is(h, "name", NULL));
    expect_true("pair without '=' has an empty value", cookie_is(h, "d", ""));
    expect_true("spaces around name and value trimmed", cookie_is(h, "e", "spaced"));
    expect_true("empty value", cookie_is(h, "f", ""));
    expect_true("absent cookie", cookie_is(h, "zz", NULL));
    expect_true("names are case-sensitive", cookie_is(h, "A", NULL));
    expect_true("a name prefix does not match", cookie_is("abc=1", "ab", NULL));
    expect_true("__Host- name found", cookie_is("__Host-nsid=abc123; other=1", "__Host-nsid", "abc123"));
    expect_true("empty header", cookie_is("", "a", NULL));
    expect_true("NULL header", n_http_cookie_get(NULL, 0, "a", NULL) == NULL && n_http_cookie_count(NULL, 0, "a") == 0);
    expect_true("empty name", n_http_cookie_get("a=1", 3, "", NULL) == NULL);
}

static int builds(const N_HTTP_SET_COOKIE* c, const char* want) {
    N_STR* out = NULL;
    int ok = n_http_set_cookie_build(&out, c);
    int res = want ? (ok == TRUE && out && strcmp(out->data, want) == 0) : (ok == FALSE && out == NULL);
    if (!res)
        n_log(LOG_ERR, "got: %s", out ? out->data : "(nothing)");
    free_nstr(&out);
    return res;
}

static void test_build(void) {
    N_HTTP_SET_COOKIE c;
    N_STR* out = NULL;

    memset(&c, 0, sizeof(c));
    c.name = "__Host-nsid";
    c.value = "AbC-123_x";
    c.path = "/";
    c.max_age = N_HTTP_COOKIE_SESSION;
    c.secure = 1;
    c.http_only = 1;
    c.same_site = N_HTTP_SAMESITE_LAX;
    expect_true("session cookie, as the site sends it",
                builds(&c, "__Host-nsid=AbC-123_x; Path=/; Secure; HttpOnly; SameSite=Lax"));
    c.max_age = 0;
    c.value = "";
    expect_true("deletion",
                builds(&c, "__Host-nsid=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; Secure; HttpOnly; SameSite=Lax"));
    c.max_age = 3600;
    c.same_site = N_HTTP_SAMESITE_STRICT;
    expect_true("Max-Age and Strict", builds(&c, "__Host-nsid=; Max-Age=3600; Path=/; Secure; HttpOnly; SameSite=Strict"));

    memset(&c, 0, sizeof(c));
    c.name = "pref";
    c.value = "dark";
    c.max_age = N_HTTP_COOKIE_SESSION;
    c.domain = "example.org";
    c.path = "/a b";
    expect_true("plain cookie with Domain, no flags", builds(&c, "pref=dark; Path=/a b; Domain=example.org"));

    /* injection and invalid values */
    c.domain = NULL;
    c.path = NULL;
    c.value = "a;b";
    expect_true("';' in a value refused", builds(&c, NULL));
    c.value = "a\r\nSet-Cookie: x=1";
    expect_true("CRLF in a value refused", builds(&c, NULL));
    c.value = "a b";
    expect_true("space in a value refused", builds(&c, NULL));
    c.value = "\"q\"";
    expect_true("quotes in a value refused", builds(&c, NULL));
    c.value = "ok";
    c.name = "a=b";
    expect_true("'=' in a name refused", builds(&c, NULL));
    c.name = "";
    expect_true("empty name refused", builds(&c, NULL));
    c.name = NULL;
    expect_true("NULL name refused", builds(&c, NULL));
    c.name = "ok";
    c.path = "/x;Domain=evil";
    expect_true("';' in a path refused", builds(&c, NULL));
    c.path = "/x\n";
    expect_true("control byte in a path refused", builds(&c, NULL));
    c.path = "";
    expect_true("empty path refused", builds(&c, NULL));
    c.path = NULL;
    c.domain = "evil.org; Secure";
    expect_true("bad domain refused", builds(&c, NULL));
    c.domain = NULL;
    c.max_age = -5;
    expect_true("negative Max-Age refused", builds(&c, NULL));
    c.max_age = N_HTTP_COOKIE_SESSION;
    c.same_site = N_HTTP_SAMESITE_NONE;
    expect_true("SameSite=None without Secure refused", builds(&c, NULL));
    c.secure = 1;
    expect_true("SameSite=None with Secure", builds(&c, "ok=ok; Secure; SameSite=None"));
    c.same_site = (N_HTTP_SAMESITE)7;
    expect_true("unknown SameSite refused", builds(&c, NULL));
    c.same_site = N_HTTP_SAMESITE_UNSET;

    /* cookie prefixes */
    c.name = "__Host-x";
    c.path = "/";
    c.secure = 0;
    expect_true("__Host- without Secure refused", builds(&c, NULL));
    c.secure = 1;
    c.path = "/admin";
    expect_true("__Host- with Path other than / refused", builds(&c, NULL));
    c.path = NULL;
    expect_true("__Host- without Path refused", builds(&c, NULL));
    c.path = "/";
    c.domain = "example.org";
    expect_true("__Host- with Domain refused", builds(&c, NULL));
    c.domain = NULL;
    c.name = "__Secure-x";
    c.secure = 0;
    expect_true("__Secure- without Secure refused", builds(&c, NULL));
    c.secure = 1;
    expect_true("__Secure- with Secure", builds(&c, "__Secure-x=ok; Path=/; Secure"));

    /* appends; a refused cookie leaves the destination unchanged */
    out = char_to_nstr("X: ");
    c.name = "a";
    c.path = NULL;
    c.secure = 0;
    expect_true("append", n_http_set_cookie_build(&out, &c) == TRUE && strcmp(out->data, "X: a=ok") == 0);
    c.value = "b;c";
    expect_true("refused append returns FALSE", n_http_set_cookie_build(&out, &c) == FALSE);
    expect_true("refused append leaves the destination", strcmp(out->data, "X: a=ok") == 0);
    free_nstr(&out);
    expect_true("NULL arguments", n_http_set_cookie_build(NULL, &c) == FALSE && n_http_set_cookie_build(&out, NULL) == FALSE);
}

int main(int argc, char** argv) {
    if (process_args(argc, argv) != 0) {
        fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
        return 1;
    }
    set_log_level(log_level);

    test_read();
    test_build();

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