Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_http_cookie.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
29
30#include "nilorea/n_common.h"
31#include "nilorea/n_log.h"
32#include "nilorea/n_str.h"
33
34#include <stdio.h>
35#include <string.h>
36
37/* An RFC 9110 tchar. */
38static int _cookie_tchar(unsigned char c) {
39 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
40 return 1;
41 return c != '\0' && strchr("!#$%&'*+-.^_`|~", c) != NULL;
42}
43
44static int _cookie_token(const char* s, size_t n) {
45 size_t i;
46 if (n == 0)
47 return 0;
48 for (i = 0; i < n; i++)
49 if (!_cookie_tchar((unsigned char)s[i]))
50 return 0;
51 return 1;
52}
53
54/* An RFC 6265 cookie-octet: visible ASCII except DQUOTE, ',', ';' and '\'. */
55static int _cookie_octet(unsigned char c) {
56 return c == 0x21 || (c >= 0x23 && c <= 0x2B) || (c >= 0x2D && c <= 0x3A) ||
57 (c >= 0x3C && c <= 0x5B) || (c >= 0x5D && c <= 0x7E);
58}
59
60/* Walk the pairs of a Cookie header; for each pair named name, count it and
61 * remember the first value. Returns the count. */
62static size_t _cookie_scan(const char* h, size_t len, const char* name, const char** first, size_t* first_len) {
63 size_t i = 0, count = 0, nlen = strlen(name);
64 while (i < len) {
65 size_t ps, pe, eq, ns, ne, vs, ve;
66 while (i < len && (h[i] == ' ' || h[i] == '\t' || h[i] == ';'))
67 i++;
68 ps = i;
69 while (i < len && h[i] != ';')
70 i++;
71 pe = i;
72 if (pe == ps)
73 continue;
74 eq = ps;
75 while (eq < pe && h[eq] != '=')
76 eq++;
77 ns = ps;
78 ne = eq;
79 while (ne > ns && (h[ne - 1] == ' ' || h[ne - 1] == '\t'))
80 ne--;
81 if (eq < pe) {
82 vs = eq + 1;
83 ve = pe;
84 while (vs < ve && (h[vs] == ' ' || h[vs] == '\t'))
85 vs++;
86 while (ve > vs && (h[ve - 1] == ' ' || h[ve - 1] == '\t'))
87 ve--;
88 } else {
89 vs = ve = pe;
90 }
91 if (!_cookie_token(h + ns, ne - ns) || ne - ns != nlen || memcmp(h + ns, name, nlen) != 0)
92 continue;
93 if (count == 0 && first) {
94 *first = h + vs;
95 *first_len = ve - vs;
96 }
97 count++;
98 }
99 return count;
100}
101
110const char* n_http_cookie_get(const char* header, size_t len, const char* name, size_t* value_len) {
111 const char* v = NULL;
112 size_t vl = 0;
113 if (value_len)
114 *value_len = 0;
115 if (!header || !name || name[0] == '\0')
116 return NULL;
117 if (_cookie_scan(header, len, name, &v, &vl) == 0)
118 return NULL;
119 if (value_len)
120 *value_len = vl;
121 return v;
122}
123
131size_t n_http_cookie_count(const char* header, size_t len, const char* name) {
132 if (!header || !name || name[0] == '\0')
133 return 0;
134 return _cookie_scan(header, len, name, NULL, NULL);
135}
136
137/* Path: non-empty, visible ASCII and spaces, no ';'. */
138static int _cookie_path_ok(const char* p) {
139 if (*p == '\0')
140 return 0;
141 for (; *p; p++) {
142 unsigned char c = (unsigned char)*p;
143 if (c < 0x20 || c > 0x7E || c == ';')
144 return 0;
145 }
146 return 1;
147}
148
149static int _cookie_domain_ok(const char* d) {
150 if (*d == '\0')
151 return 0;
152 for (; *d; d++) {
153 unsigned char c = (unsigned char)*d;
154 if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '-'))
155 return 0;
156 }
157 return 1;
158}
159
168 char buf[64];
169 size_t i;
170 const char* value;
171 N_STR* tmp = NULL;
172
173 __n_assert(out, return FALSE);
174 __n_assert(c, return FALSE);
175 if (!c->name || !_cookie_token(c->name, strlen(c->name))) {
176 n_log(LOG_DEBUG, "set-cookie: name is not a token");
177 return FALSE;
178 }
179 value = c->value ? c->value : "";
180 for (i = 0; value[i]; i++)
181 if (!_cookie_octet((unsigned char)value[i])) {
182 n_log(LOG_DEBUG, "set-cookie: value byte 0x%02x refused", (unsigned char)value[i]);
183 return FALSE;
184 }
185 if ((c->path && !_cookie_path_ok(c->path)) || (c->domain && !_cookie_domain_ok(c->domain)))
186 return FALSE;
187 if (c->max_age < 0 && c->max_age != N_HTTP_COOKIE_SESSION)
188 return FALSE;
189 if (strncmp(c->name, "__Host-", 7) == 0 &&
190 (!c->secure || !c->path || strcmp(c->path, "/") != 0 || c->domain))
191 return FALSE;
192 if (strncmp(c->name, "__Secure-", 9) == 0 && !c->secure)
193 return FALSE;
194 if (c->same_site == N_HTTP_SAMESITE_NONE && !c->secure)
195 return FALSE;
196 if ((int)c->same_site < (int)N_HTTP_SAMESITE_UNSET || (int)c->same_site > (int)N_HTTP_SAMESITE_NONE)
197 return FALSE;
198
199 /* built aside, appended only once complete: a failure leaves *out as it was */
200 if (!nstrcat_bytes(tmp, (void*)c->name) || !nstrcat_bytes(tmp, "=") ||
201 (value[0] && !nstrcat_bytes(tmp, (void*)value)))
202 goto fail;
203 if (c->max_age == 0) {
204 if (!nstrcat_bytes(tmp, "; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT"))
205 goto fail;
206 } else if (c->max_age > 0) {
207 snprintf(buf, sizeof(buf), "; Max-Age=%lld", c->max_age);
208 if (!nstrcat_bytes(tmp, buf))
209 goto fail;
210 }
211 if (c->path && (!nstrcat_bytes(tmp, "; Path=") || !nstrcat_bytes(tmp, (void*)c->path)))
212 goto fail;
213 if (c->domain && (!nstrcat_bytes(tmp, "; Domain=") || !nstrcat_bytes(tmp, (void*)c->domain)))
214 goto fail;
215 if (c->secure && !nstrcat_bytes(tmp, "; Secure"))
216 goto fail;
217 if (c->http_only && !nstrcat_bytes(tmp, "; HttpOnly"))
218 goto fail;
219 if (c->same_site == N_HTTP_SAMESITE_LAX && !nstrcat_bytes(tmp, "; SameSite=Lax"))
220 goto fail;
221 if (c->same_site == N_HTTP_SAMESITE_STRICT && !nstrcat_bytes(tmp, "; SameSite=Strict"))
222 goto fail;
223 if (c->same_site == N_HTTP_SAMESITE_NONE && !nstrcat_bytes(tmp, "; SameSite=None"))
224 goto fail;
225 if (!*out) {
226 *out = tmp;
227 return TRUE;
228 }
229 if (!nstrcat_ex(out, tmp->data, tmp->written, 1))
230 goto fail;
231 free_nstr(&tmp);
232 return TRUE;
233fail:
234 free_nstr(&tmp);
235 return FALSE;
236}
static int h(void *ctx, const N_ROUTE_MATCH *m)
a handler that does nothing (the test only routes)
static int eq(double a, double b)
Definition ex_wrap.c:93
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
int http_only
non-zero: HttpOnly
const char * value
cookie value, RFC 6265 cookie-octets only (may be "")
int secure
non-zero: Secure
const char * path
Path attribute, or NULL.
const char * name
cookie name, an RFC 9110 token
const char * domain
Domain attribute, or NULL (host-only cookie).
N_HTTP_SAMESITE same_site
SameSite attribute.
long long max_age
Max-Age in seconds; N_HTTP_COOKIE_SESSION for a session cookie; 0 deletes the cookie (Max-Age=0 plus ...
size_t n_http_cookie_count(const char *header, size_t len, const char *name)
count the pairs named name in a Cookie header value
int n_http_set_cookie_build(N_STR **out, const N_HTTP_SET_COOKIE *c)
append a Set-Cookie header value, refusing anything that could break out of an attribute or that a br...
#define N_HTTP_COOKIE_SESSION
Max-Age value meaning "session cookie": neither Max-Age nor Expires is sent.
const char * n_http_cookie_get(const char *header, size_t len, const char *name, size_t *value_len)
find a cookie in a Cookie header value (first occurrence wins)
@ N_HTTP_SAMESITE_NONE
SameSite=None, needs Secure.
@ N_HTTP_SAMESITE_STRICT
SameSite=Strict.
@ N_HTTP_SAMESITE_LAX
SameSite=Lax.
@ N_HTTP_SAMESITE_UNSET
no SameSite attribute (browser default)
what a Set-Cookie should carry
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
Definition n_str.h:68
char * data
the string
Definition n_str.h:63
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
N_STR * nstrcat_ex(N_STR **dest, void *src, NSTRBYTE size, int resize_flag)
Append data into N_STR using internal N_STR size and cursor position.
Definition n_str.c:1096
#define nstrcat_bytes(__nstr_dst, __void_src)
Macro to quickly concatenate a '\0' ended byte stream to a N_STR.
Definition n_str.h:135
A box including a string and his lenght.
Definition n_str.h:61
Common headers and low-level functions & define.
Generic log system.
N_STR and string function declaration.