2 n__http__cookie_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

src/n_http_cookie.c

Server-side cookies (see n_http_cookie.h). More...

Functions

Name
int _cookie_token(const char * s, size_t n)
int _cookie_tchar(unsigned char c)
size_t _cookie_scan(const char * h, size_t len, const char * name, const char ** first, size_t * first_len)
int _cookie_path_ok(const char * p)
int _cookie_octet(unsigned char c)
int _cookie_domain_ok(const char * d)

Detailed Description

Server-side cookies (see n_http_cookie.h).

Author: Castagnier Mickael

Version: 1.0

Date: 2026

Functions Documentation

static int _cookie_token(
    const char * s,
    size_t n
)
static int _cookie_tchar(
    unsigned char c
)
static size_t _cookie_scan(
    const char * h,
    size_t len,
    const char * name,
    const char ** first,
    size_t * first_len
)
static int _cookie_path_ok(
    const char * p
)
static int _cookie_octet(
    unsigned char c
)
static int _cookie_domain_ok(
    const char * d
)

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_http_cookie.h"

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

#include <stdio.h>
#include <string.h>

/* An RFC 9110 tchar. */
static int _cookie_tchar(unsigned char c) {
    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
        return 1;
    return c != '\0' && strchr("!#$%&'*+-.^_`|~", c) != NULL;
}

static int _cookie_token(const char* s, size_t n) {
    size_t i;
    if (n == 0)
        return 0;
    for (i = 0; i < n; i++)
        if (!_cookie_tchar((unsigned char)s[i]))
            return 0;
    return 1;
}

/* An RFC 6265 cookie-octet: visible ASCII except DQUOTE, ',', ';' and '\'. */
static int _cookie_octet(unsigned char c) {
    return c == 0x21 || (c >= 0x23 && c <= 0x2B) || (c >= 0x2D && c <= 0x3A) ||
           (c >= 0x3C && c <= 0x5B) || (c >= 0x5D && c <= 0x7E);
}

/* Walk the pairs of a Cookie header; for each pair named name, count it and
 * remember the first value. Returns the count. */
static size_t _cookie_scan(const char* h, size_t len, const char* name, const char** first, size_t* first_len) {
    size_t i = 0, count = 0, nlen = strlen(name);
    while (i < len) {
        size_t ps, pe, eq, ns, ne, vs, ve;
        while (i < len && (h[i] == ' ' || h[i] == '\t' || h[i] == ';'))
            i++;
        ps = i;
        while (i < len && h[i] != ';')
            i++;
        pe = i;
        if (pe == ps)
            continue;
        eq = ps;
        while (eq < pe && h[eq] != '=')
            eq++;
        ns = ps;
        ne = eq;
        while (ne > ns && (h[ne - 1] == ' ' || h[ne - 1] == '\t'))
            ne--;
        if (eq < pe) {
            vs = eq + 1;
            ve = pe;
            while (vs < ve && (h[vs] == ' ' || h[vs] == '\t'))
                vs++;
            while (ve > vs && (h[ve - 1] == ' ' || h[ve - 1] == '\t'))
                ve--;
        } else {
            vs = ve = pe;
        }
        if (!_cookie_token(h + ns, ne - ns) || ne - ns != nlen || memcmp(h + ns, name, nlen) != 0)
            continue;
        if (count == 0 && first) {
            *first = h + vs;
            *first_len = ve - vs;
        }
        count++;
    }
    return count;
}

const char* n_http_cookie_get(const char* header, size_t len, const char* name, size_t* value_len) {
    const char* v = NULL;
    size_t vl = 0;
    if (value_len)
        *value_len = 0;
    if (!header || !name || name[0] == '\0')
        return NULL;
    if (_cookie_scan(header, len, name, &v, &vl) == 0)
        return NULL;
    if (value_len)
        *value_len = vl;
    return v;
}

size_t n_http_cookie_count(const char* header, size_t len, const char* name) {
    if (!header || !name || name[0] == '\0')
        return 0;
    return _cookie_scan(header, len, name, NULL, NULL);
}

/* Path: non-empty, visible ASCII and spaces, no ';'. */
static int _cookie_path_ok(const char* p) {
    if (*p == '\0')
        return 0;
    for (; *p; p++) {
        unsigned char c = (unsigned char)*p;
        if (c < 0x20 || c > 0x7E || c == ';')
            return 0;
    }
    return 1;
}

static int _cookie_domain_ok(const char* d) {
    if (*d == '\0')
        return 0;
    for (; *d; d++) {
        unsigned char c = (unsigned char)*d;
        if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '-'))
            return 0;
    }
    return 1;
}

int n_http_set_cookie_build(N_STR** out, const N_HTTP_SET_COOKIE* c) {
    char buf[64];
    size_t i;
    const char* value;
    N_STR* tmp = NULL;

    __n_assert(out, return FALSE);
    __n_assert(c, return FALSE);
    if (!c->name || !_cookie_token(c->name, strlen(c->name))) {
        n_log(LOG_DEBUG, "set-cookie: name is not a token");
        return FALSE;
    }
    value = c->value ? c->value : "";
    for (i = 0; value[i]; i++)
        if (!_cookie_octet((unsigned char)value[i])) {
            n_log(LOG_DEBUG, "set-cookie: value byte 0x%02x refused", (unsigned char)value[i]);
            return FALSE;
        }
    if ((c->path && !_cookie_path_ok(c->path)) || (c->domain && !_cookie_domain_ok(c->domain)))
        return FALSE;
    if (c->max_age < 0 && c->max_age != N_HTTP_COOKIE_SESSION)
        return FALSE;
    if (strncmp(c->name, "__Host-", 7) == 0 &&
        (!c->secure || !c->path || strcmp(c->path, "/") != 0 || c->domain))
        return FALSE;
    if (strncmp(c->name, "__Secure-", 9) == 0 && !c->secure)
        return FALSE;
    if (c->same_site == N_HTTP_SAMESITE_NONE && !c->secure)
        return FALSE;
    if ((int)c->same_site < (int)N_HTTP_SAMESITE_UNSET || (int)c->same_site > (int)N_HTTP_SAMESITE_NONE)
        return FALSE;

    /* built aside, appended only once complete: a failure leaves *out as it was */
    if (!nstrcat_bytes(tmp, (void*)c->name) || !nstrcat_bytes(tmp, "=") ||
        (value[0] && !nstrcat_bytes(tmp, (void*)value)))
        goto fail;
    if (c->max_age == 0) {
        if (!nstrcat_bytes(tmp, "; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT"))
            goto fail;
    } else if (c->max_age > 0) {
        snprintf(buf, sizeof(buf), "; Max-Age=%lld", c->max_age);
        if (!nstrcat_bytes(tmp, buf))
            goto fail;
    }
    if (c->path && (!nstrcat_bytes(tmp, "; Path=") || !nstrcat_bytes(tmp, (void*)c->path)))
        goto fail;
    if (c->domain && (!nstrcat_bytes(tmp, "; Domain=") || !nstrcat_bytes(tmp, (void*)c->domain)))
        goto fail;
    if (c->secure && !nstrcat_bytes(tmp, "; Secure"))
        goto fail;
    if (c->http_only && !nstrcat_bytes(tmp, "; HttpOnly"))
        goto fail;
    if (c->same_site == N_HTTP_SAMESITE_LAX && !nstrcat_bytes(tmp, "; SameSite=Lax"))
        goto fail;
    if (c->same_site == N_HTTP_SAMESITE_STRICT && !nstrcat_bytes(tmp, "; SameSite=Strict"))
        goto fail;
    if (c->same_site == N_HTTP_SAMESITE_NONE && !nstrcat_bytes(tmp, "; SameSite=None"))
        goto fail;
    if (!*out) {
        *out = tmp;
        return TRUE;
    }
    if (!nstrcat_ex(out, tmp->data, tmp->written, 1))
        goto fail;
    free_nstr(&tmp);
    return TRUE;
fail:
    free_nstr(&tmp);
    return FALSE;
}