cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
query.c
Go to the documentation of this file.
1#define _POSIX_C_SOURCE 200809L
6#include <stdlib.h>
7#include <string.h>
8#include <ctype.h>
9#include <stdio.h>
10
16#define CWIST_QUERY_MAP_DEFAULT_SIZE 16
17
24/* Arena maps carve from the request arena, which is a fixed 8 KiB
25 * generation: a query of a few KiB used to exhaust it, and every later
26 * key/value was dropped silently. Allocations now fall back to the heap,
27 * and every release path frees whatever the arena does not own. */
28static void *query_alloc(void *arena, size_t size) {
29 if (arena) {
30 void *p = cwist_arena_alloc((cwist_arena_t *)arena, size);
31 if (p) return p;
32 }
33 return cwist_alloc(size);
34}
35
36static void query_release(void *arena, void *ptr) {
37 if (!ptr) return;
38 if (arena && cwist_arena_owns((cwist_arena_t *)arena, ptr)) return;
39 cwist_free(ptr);
40}
41
42static char *query_strdup(void *arena, const char *str) {
43 if (!str) return NULL;
44 size_t len = strlen(str);
45 char *copy = (char *)query_alloc(arena, len + 1);
46 if (copy) memcpy(copy, str, len + 1);
47 return copy;
48}
49
57
62 cwist_query_map *map = (cwist_query_map *)query_alloc(arena, sizeof(cwist_query_map));
63 if (!map) return NULL;
65 map->buckets = (cwist_query_bucket **)query_alloc(arena, map->size * sizeof(cwist_query_bucket *));
66 if (!map->buckets) {
67 query_release(arena, map);
68 return NULL;
69 }
70 memset(map->buckets, 0, map->size * sizeof(cwist_query_bucket *));
71 map->arena = arena;
72 map->count = 0;
74 return map;
75}
76
82 if (!map) return;
83 void *arena = map->arena;
85 query_release(arena, map->buckets);
86 query_release(arena, map);
87}
88
94 if (!map) return;
95 for (size_t i = 0; i < map->size; i++) {
96 cwist_query_bucket *curr = map->buckets[i];
97 while (curr) {
99 query_release(map->arena, curr->key);
100 query_release(map->arena, curr->value);
101 query_release(map->arena, curr);
102 curr = next;
103 }
104 map->buckets[i] = NULL;
105 }
106 map->count = 0;
107}
108
117static void query_map_grow(cwist_query_map *map) {
118 if (map->size && map->count + 1 <= map->size - map->size / 4) return;
119 size_t new_size = map->size ? map->size * 2 : CWIST_QUERY_MAP_DEFAULT_SIZE;
120 if (new_size <= map->size || new_size > SIZE_MAX / sizeof(cwist_query_bucket *)) return;
121 cwist_query_bucket **buckets =
122 (cwist_query_bucket **)query_alloc(map->arena, new_size * sizeof(cwist_query_bucket *));
123 if (!buckets) return;
124 memset(buckets, 0, new_size * sizeof(cwist_query_bucket *));
125 for (size_t i = 0; i < map->size; i++) {
126 cwist_query_bucket *curr = map->buckets[i];
127 while (curr) {
129 size_t index = siphash24(curr->key, strlen(curr->key), map->seed) % new_size;
130 curr->next = buckets[index];
131 buckets[index] = curr;
132 curr = next;
133 }
134 }
135 query_release(map->arena, map->buckets);
136 map->buckets = buckets;
137 map->size = new_size;
138}
139
146void cwist_query_map_set(cwist_query_map *map, const char *key, const char *value) {
147 if (!map || !key || !value) return;
148 if (!map->size) {
149 query_map_grow(map);
150 if (!map->size) return;
151 }
152
153 uint64_t hash = siphash24(key, strlen(key), map->seed);
154 size_t index = hash % map->size;
155
156 cwist_query_bucket *curr = map->buckets[index];
157 while (curr) {
158 if (strcmp(curr->key, key) == 0) {
159 // Update existing; keep the old value if the copy fails
160 char *copy = query_strdup(map->arena, value);
161 if (!copy) return;
162 query_release(map->arena, curr->value);
163 curr->value = copy;
164 return;
165 }
166 curr = curr->next;
167 }
168
169 // Insert new
170 if (map->count + 1 > map->size - map->size / 4) {
171 query_map_grow(map);
172 index = hash % map->size;
173 }
174 cwist_query_bucket *node = (cwist_query_bucket *)query_alloc(map->arena, sizeof(cwist_query_bucket));
175 if (!node) return;
176 node->key = query_strdup(map->arena, key);
177 node->value = query_strdup(map->arena, value);
178 if (!node->key || !node->value) {
179 /* A NULL key in a bucket would crash the next strcmp(). */
180 query_release(map->arena, node->key);
181 query_release(map->arena, node->value);
182 query_release(map->arena, node);
183 return;
184 }
185 node->next = map->buckets[index];
186 map->buckets[index] = node;
187 map->count++;
188}
189
196const char *cwist_query_map_get(cwist_query_map *map, const char *key) {
197 if (!map || !key || !map->size) return NULL;
198
199 uint64_t hash = siphash24(key, strlen(key), map->seed);
200 size_t index = hash % map->size;
201
202 cwist_query_bucket *curr = map->buckets[index];
203 while (curr) {
204 if (strcmp(curr->key, key) == 0) {
205 return curr->value;
206 }
207 curr = curr->next;
208 }
209 return NULL;
210}
211
221 if (!map || !key || !map->size) return;
222 uint64_t hash = siphash24(key, strlen(key), map->seed);
223 size_t index = hash % map->size;
224
225 cwist_query_bucket *curr = map->buckets[index];
226 cwist_query_bucket *prev = NULL;
227 while (curr) {
228 if (strcmp(curr->key, key) == 0) {
229 if (prev) {
230 prev->next = curr->next;
231 } else {
232 map->buckets[index] = curr->next;
233 }
234 query_release(map->arena, curr->key);
235 query_release(map->arena, curr->value);
236 query_release(map->arena, curr);
237 map->count--;
238 return;
239 }
240 prev = curr;
241 curr = curr->next;
242 }
243}
244
253 if (!map || !cb) return;
254 for (size_t i = 0; i < map->size; i++) {
255 cwist_query_bucket *curr = map->buckets[i];
256 while (curr) {
257 cb(curr->key, curr->value, ctx);
258 curr = curr->next;
259 }
260 }
261}
262
263/* Hex nibble values for URL percent-decoding. Only consulted after
264 * isxdigit validation, so non-hex entries are never read. */
265static const uint8_t url_decode_hex[256] = {
266 ['0'] = 0, 1, 2, 3, 4, 5, 6, 7,
267 ['8'] = 8, 9, ['A'] = 10, 11, 12, 13, 14, 15,
268 ['a'] = 10, 11, 12, 13, 14, 15,
269};
270
279static char *url_decode(void *arena, const char *src) {
280 if (!src) return NULL;
281
282 size_t len = strlen(src);
283 char *out = (char *)query_alloc(arena, len + 1);
284 if (!out) return NULL;
285
286 size_t j = 0;
287 size_t i = 0;
288 while (i < len) {
289 /* Copy the plain run up to the next '%' or '+' in one shot. */
290 size_t plain = strcspn(src + i, "%+");
291 if (plain > 0) {
292 memcpy(out + j, src + i, plain);
293 j += plain;
294 i += plain;
295 if (i >= len) break;
296 }
297 if (src[i] == '+') {
298 out[j++] = 0x20; /* SPACE (U+0020) */
299 i++;
300 } else if (i + 2 < len && isxdigit((unsigned char)src[i + 1]) &&
301 isxdigit((unsigned char)src[i + 2])) {
302 out[j++] = (char)((url_decode_hex[(unsigned char)src[i + 1]] << 4) |
303 url_decode_hex[(unsigned char)src[i + 2]]);
304 i += 3;
305 } else {
306 out[j++] = src[i];
307 i++;
308 }
309 }
310 out[j] = '\0';
311 return out;
312}
313
320 if (!map || !raw_query || strlen(raw_query) == 0) return;
321
322 char *buffer = query_strdup(map->arena, raw_query);
323 if (!buffer) return;
324
325 /* Single pass over buffer splitting on '&' and '='; strtok_r's empty
326 * token collapsing is reproduced by skipping zero-length segments. */
327 char *seg = buffer;
328 char *end = buffer + strlen(buffer);
329 while (seg < end) {
330 char *amp = memchr(seg, '&', (size_t)(end - seg));
331 char *seg_end = amp ? amp : end;
332 if (amp) *amp = '\0';
333
334 char *token = seg;
335 seg = seg_end + (amp ? 1 : 0);
336
337 if (seg_end == token) continue; /* strtok_r skips empty tokens */
338
339 char *eq = memchr(token, '=', (size_t)(seg_end - token));
340 if (!eq) continue;
341
342 *eq = '\0';
343 const char *key_raw = token;
344 const char *value_raw = eq + 1;
345
346 char *key_dec = url_decode(map->arena, key_raw);
347 char *value_dec = url_decode(map->arena, value_raw);
348
351 }
352
353 query_release(map->arena, key_dec);
354 query_release(map->arena, value_dec);
355 }
356
357 query_release(map->arena, buffer);
358}
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
Request-scoped arena allocator backed by libttak arena generations.
struct cwist_arena cwist_arena_t
Definition arena.h:22
void * cwist_arena_alloc(cwist_arena_t *arena, size_t size)
Bump-allocate size bytes from the active generation.
Definition arena.c:44
bool cwist_arena_owns(const cwist_arena_t *arena, const void *ptr)
Check whether ptr lies inside the active generation buffer.
Definition arena.c:54
uint64_t siphash24(const void *src, size_t len, const uint8_t key[16])
Compute SipHash-2-4 over a byte range using a 16-byte secret key.
Definition siphash.c:52
void cwist_generate_hash_seed(uint8_t key[16])
Generates a random SipHash seed.
Definition siphash.c:223
Query string parser and map.
void(* cwist_query_map_iter_func)(const char *key, const char *value, void *ctx)
Iterator callback signature.
Definition query.h:78
size_t cap
Definition otel.c:56
void cwist_query_map_delete(cwist_query_map *map, const char *key)
Remove a key/value pair from the map if present.
Definition query.c:220
const char * cwist_query_map_get(cwist_query_map *map, const char *key)
Look up a decoded query parameter by key.
Definition query.c:196
cwist_query_map * cwist_query_map_create_in_arena(void *arena)
Allocate a query map within a memory arena to avoid GC/heap allocations.
Definition query.c:61
#define CWIST_QUERY_MAP_DEFAULT_SIZE
Definition query.c:16
void cwist_query_map_set(cwist_query_map *map, const char *key, const char *value)
Insert or replace a decoded query parameter in the map.
Definition query.c:146
void cwist_query_map_foreach(cwist_query_map *map, cwist_query_map_iter_func cb, void *ctx)
Invoke a callback for every key/value pair stored in the map.
Definition query.c:252
void cwist_query_map_clear(cwist_query_map *map)
Remove every key/value pair while keeping the bucket array allocated.
Definition query.c:93
cwist_query_map * cwist_query_map_create(void)
Allocate a query map and seed its SipHash key material.
Definition query.c:54
void cwist_query_map_destroy(cwist_query_map *map)
Release every bucket node and the map container itself.
Definition query.c:81
void cwist_query_map_parse(cwist_query_map *map, const char *raw_query)
Parse a raw a=1&b=2 query string into the existing map.
Definition query.c:319
struct cwist_query_bucket * next
Definition query.h:15