Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_http_response.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#include "nilorea/n_time.h"
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <time.h>
39#include <unistd.h>
40#ifdef __windows__
41#include <io.h>
42#endif
43
44/* bytes read from a file body per write call */
45#define N_HTTP_RESPONSE_CHUNK 65536
46
52const char* n_http_status_reason(int status) {
53 switch (status) {
54 case 100:
55 return "Continue";
56 case 101:
57 return "Switching Protocols";
58 case 200:
59 return "OK";
60 case 201:
61 return "Created";
62 case 202:
63 return "Accepted";
64 case 204:
65 return "No Content";
66 case 206:
67 return "Partial Content";
68 case 301:
69 return "Moved Permanently";
70 case 302:
71 return "Found";
72 case 303:
73 return "See Other";
74 case 304:
75 return "Not Modified";
76 case 307:
77 return "Temporary Redirect";
78 case 308:
79 return "Permanent Redirect";
80 case 400:
81 return "Bad Request";
82 case 401:
83 return "Unauthorized";
84 case 403:
85 return "Forbidden";
86 case 404:
87 return "Not Found";
88 case 405:
89 return "Method Not Allowed";
90 case 406:
91 return "Not Acceptable";
92 case 408:
93 return "Request Timeout";
94 case 409:
95 return "Conflict";
96 case 410:
97 return "Gone";
98 case 411:
99 return "Length Required";
100 case 412:
101 return "Precondition Failed";
102 case 413:
103 return "Content Too Large";
104 case 414:
105 return "URI Too Long";
106 case 415:
107 return "Unsupported Media Type";
108 case 416:
109 return "Range Not Satisfiable";
110 case 417:
111 return "Expectation Failed";
112 case 421:
113 return "Misdirected Request";
114 case 422:
115 return "Unprocessable Content";
116 case 426:
117 return "Upgrade Required";
118 case 428:
119 return "Precondition Required";
120 case 429:
121 return "Too Many Requests";
122 case 431:
123 return "Request Header Fields Too Large";
124 case 451:
125 return "Unavailable For Legal Reasons";
126 case 500:
127 return "Internal Server Error";
128 case 501:
129 return "Not Implemented";
130 case 502:
131 return "Bad Gateway";
132 case 503:
133 return "Service Unavailable";
134 case 504:
135 return "Gateway Timeout";
136 case 505:
137 return "HTTP Version Not Supported";
138 default:
139 break;
140 }
141 if (status >= 100 && status < 200)
142 return "Informational";
143 if (status >= 200 && status < 300)
144 return "Success";
145 if (status >= 300 && status < 400)
146 return "Redirection";
147 if (status >= 400 && status < 500)
148 return "Client Error";
149 if (status >= 500 && status < 600)
150 return "Server Error";
151 return "";
152}
153
154static int _resp_lc(int c) {
155 return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c;
156}
157
158static int _resp_ieq(const char* a, const char* b) {
159 while (*a && *b) {
160 if (_resp_lc((unsigned char)*a) != _resp_lc((unsigned char)*b))
161 return 0;
162 a++;
163 b++;
164 }
165 return *a == *b;
166}
167
168static int _resp_token(const char* s) {
169 if (!s || !*s)
170 return 0;
171 for (; *s; s++) {
172 unsigned char c = (unsigned char)*s;
173 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
174 continue;
175 if (!strchr("!#$%&'*+-.^_`|~", c))
176 return 0;
177 }
178 return 1;
179}
180
181/* No body and no Content-Length for these (RFC 9110 sections 6.4.1, 15.3.5, 15.4.5). */
182static int _resp_bodiless(int status) {
183 return (status >= 100 && status < 200) || status == 204 || status == 304;
184}
185
192 N_HTTP_RESP* r = NULL;
193 if (status < 100 || status > 599)
194 return NULL;
195 Malloc(r, N_HTTP_RESP, 1);
196 __n_assert(r, return NULL);
197 r->status = status;
198 r->fd = -1;
199 r->version_minor = 1;
200 return r;
201}
202
204 free_nstr(&r->body);
205 if (r->fd >= 0)
206 close(r->fd);
207 r->fd = -1;
208 r->fd_offset = 0;
209 r->fd_length = 0;
210}
211
217 size_t i;
218 if (!r || !*r)
219 return;
220 for (i = 0; i < (*r)->nb_headers; i++) {
221 FreeNoLog((*r)->names[i]);
222 FreeNoLog((*r)->values[i]);
223 }
224 _resp_drop_body(*r);
225 FreeNoLog(*r);
226}
227
235 if (!r || status < 100 || status > 599)
236 return FALSE;
237 r->status = status;
238 return TRUE;
239}
240
248int n_http_response_add_header(N_HTTP_RESP* r, const char* name, const char* value) {
249 size_t vs, ve, i;
250 char *n = NULL, *v = NULL;
251 if (!r || !value || !_resp_token(name))
252 return FALSE;
253 if (_resp_ieq(name, "content-length") || _resp_ieq(name, "transfer-encoding") || _resp_ieq(name, "connection")) {
254 n_log(LOG_DEBUG, "response: %s is set by the builder", name);
255 return FALSE;
256 }
258 return FALSE;
259 ve = strlen(value);
261 return FALSE;
262 for (i = 0; i < ve; i++) {
263 unsigned char c = (unsigned char)value[i];
264 if ((c < 0x20 && c != '\t') || c == 0x7f) {
265 n_log(LOG_DEBUG, "response: control byte 0x%02x in %s refused", c, name);
266 return FALSE;
267 }
268 }
269 vs = 0;
270 while (vs < ve && (value[vs] == ' ' || value[vs] == '\t'))
271 vs++;
272 while (ve > vs && (value[ve - 1] == ' ' || value[ve - 1] == '\t'))
273 ve--;
274 n = strdup(name);
275 Malloc(v, char, ve - vs + 1);
276 if (!n || !v) {
277 FreeNoLog(n);
278 FreeNoLog(v);
279 return FALSE;
280 }
281 memcpy(v, value + vs, ve - vs);
282 v[ve - vs] = '\0';
283 r->names[r->nb_headers] = n;
284 r->values[r->nb_headers] = v;
285 r->nb_headers++;
286 return TRUE;
287}
288
295size_t n_http_response_remove_header(N_HTTP_RESP* r, const char* name) {
296 size_t i, o = 0, removed = 0;
297 if (!r || !name)
298 return 0;
299 for (i = 0; i < r->nb_headers; i++) {
300 if (_resp_ieq(r->names[i], name)) {
301 FreeNoLog(r->names[i]);
302 FreeNoLog(r->values[i]);
303 removed++;
304 continue;
305 }
306 r->names[o] = r->names[i];
307 r->values[o] = r->values[i];
308 o++;
309 }
310 for (i = o; i < r->nb_headers; i++) {
311 r->names[i] = NULL;
312 r->values[i] = NULL;
313 }
314 r->nb_headers = o;
315 return removed;
316}
317
325int n_http_response_set_header(N_HTTP_RESP* r, const char* name, const char* value) {
326 size_t before;
327 if (!r || !name)
328 return FALSE;
329 before = r->nb_headers;
330 if (!n_http_response_add_header(r, name, value))
331 return FALSE;
332 /* the new header is last: drop the older ones of that name before it */
333 {
334 size_t i, o = 0;
335 for (i = 0; i < before; i++) {
336 if (_resp_ieq(r->names[i], name)) {
337 FreeNoLog(r->names[i]);
338 FreeNoLog(r->values[i]);
339 continue;
340 }
341 r->names[o] = r->names[i];
342 r->values[o] = r->values[i];
343 o++;
344 }
345 r->names[o] = r->names[before];
346 r->values[o] = r->values[before];
347 o++;
348 for (i = o; i < r->nb_headers; i++) {
349 r->names[i] = NULL;
350 r->values[i] = NULL;
351 }
352 r->nb_headers = o;
353 }
354 return TRUE;
355}
356
363const char* n_http_response_get_header(const N_HTTP_RESP* r, const char* name) {
364 size_t i;
365 if (!r || !name)
366 return NULL;
367 for (i = 0; i < r->nb_headers; i++)
368 if (_resp_ieq(r->names[i], name))
369 return r->values[i];
370 return NULL;
371}
372
380int n_http_response_append(N_HTTP_RESP* r, const void* data, size_t len) {
381 if (!r || (!data && len > 0))
382 return FALSE;
383 if (r->fd >= 0)
385 if (!r->body) {
386 r->body = new_nstr(len + 1);
387 if (!r->body)
388 return FALSE;
389 }
390 if (len == 0)
391 return TRUE;
392 return nstrcat_ex(&r->body, (void*)data, len, 1) ? TRUE : FALSE;
393}
394
403int n_http_response_file(N_HTTP_RESP* r, int fd, long long offset, long long length) {
404 if (!r || fd < 0 || offset < 0 || length < 0) {
405 if (fd >= 0)
406 close(fd);
407 return FALSE;
408 }
410 r->fd = fd;
411 r->fd_offset = offset;
412 r->fd_length = length;
413 return TRUE;
414}
415
424int n_http_response_set_connection(N_HTTP_RESP* r, int keep_alive, int version_minor, int head_only) {
425 if (!r || (version_minor != 0 && version_minor != 1))
426 return FALSE;
427 r->keep_alive = keep_alive ? 1 : 0;
428 r->version_minor = version_minor;
429 r->head_only = head_only ? 1 : 0;
430 return TRUE;
431}
432
439 if (!r)
440 return -1;
441 if (_resp_bodiless(r->status))
442 return 0;
443 if (r->fd >= 0)
444 return r->fd_length;
445 return r->body ? (long long)r->body->written : 0;
446}
447
448/* Append a C string; unlike nstrcat_bytes(), an empty string is a no-op
449 * success (an empty header value is legal). */
450static int _resp_cat(N_STR** h, const char* str) {
451 size_t n = strlen(str);
452 if (n == 0)
453 return *h != NULL || (*h = new_nstr(1)) != NULL;
454 return nstrcat_ex(h, (void*)str, n, 1) != NULL;
455}
456
464 char line[128];
465 size_t i;
466 N_STR* h = NULL;
467 if (!r || !out)
468 return FALSE;
469 snprintf(line, sizeof(line), "HTTP/1.1 %d %s\r\n", r->status, n_http_status_reason(r->status));
470 if (!_resp_cat(&h, line))
471 goto fail;
472 if (!n_http_response_get_header(r, "date")) {
473 char date[N_TIME_HTTP_DATE_LEN];
474 if (n_time_http_date(time(NULL), date, sizeof(date)) &&
475 (!_resp_cat(&h, "Date: ") || !_resp_cat(&h, date) || !_resp_cat(&h, "\r\n")))
476 goto fail;
477 }
478 for (i = 0; i < r->nb_headers; i++)
479 if (!_resp_cat(&h, r->names[i]) || !_resp_cat(&h, ": ") || !_resp_cat(&h, r->values[i]) ||
480 !_resp_cat(&h, "\r\n"))
481 goto fail;
482 if (!_resp_bodiless(r->status)) {
483 snprintf(line, sizeof(line), "Content-Length: %lld\r\n", n_http_response_body_length(r));
484 if (!_resp_cat(&h, line))
485 goto fail;
486 }
487 if (!r->keep_alive) {
488 if (!_resp_cat(&h, "Connection: close\r\n"))
489 goto fail;
490 } else if (r->version_minor == 0) {
491 if (!_resp_cat(&h, "Connection: keep-alive\r\n"))
492 goto fail;
493 }
494 if (!_resp_cat(&h, "\r\n"))
495 goto fail;
496 if (!*out) {
497 *out = h;
498 return TRUE;
499 }
500 if (!nstrcat_ex(out, h->data, h->written, 1))
501 goto fail;
502 free_nstr(&h);
503 return TRUE;
504fail:
505 free_nstr(&h);
506 return FALSE;
507}
508
509static int _resp_write_all(n_http_write_fn fn, void* ctx, const char* buf, size_t len) {
510 while (len > 0) {
511 ssize_t w = fn(ctx, buf, len);
512 if (w <= 0 || (size_t)w > len)
513 return FALSE;
514 buf += w;
515 len -= (size_t)w;
516 }
517 return TRUE;
518}
519
520/* Read up to len bytes at offset, without moving a shared file position
521 * where the platform allows it. */
522static ssize_t _resp_pread(int fd, char* buf, size_t len, long long offset) {
523#ifdef __windows__
524 if (_lseeki64(fd, offset, SEEK_SET) < 0)
525 return -1;
526 return (ssize_t)_read(fd, buf, (unsigned)len);
527#else
528 return pread(fd, buf, len, (off_t)offset);
529#endif
530}
531
540 N_STR* head = NULL;
541 int ok;
542 if (!r || !fn)
543 return FALSE;
544 if (!n_http_response_head(r, &head))
545 return FALSE;
546 ok = _resp_write_all(fn, ctx, head->data, head->written);
547 free_nstr(&head);
548 if (!ok)
549 return FALSE;
550 if (r->head_only || _resp_bodiless(r->status))
551 return TRUE;
552 if (r->fd >= 0) {
553 char* chunk = NULL;
554 long long done = 0;
555 Malloc(chunk, char, N_HTTP_RESPONSE_CHUNK);
556 __n_assert(chunk, return FALSE);
557 while (done < r->fd_length) {
558 long long want = r->fd_length - done;
559 ssize_t got;
560 if (want > N_HTTP_RESPONSE_CHUNK)
562 got = _resp_pread(r->fd, chunk, (size_t)want, r->fd_offset + done);
563 if (got <= 0 || !_resp_write_all(fn, ctx, chunk, (size_t)got)) {
564 n_log(LOG_DEBUG, "response: file body stopped at %lld of %lld bytes", done, r->fd_length);
565 Free(chunk);
566 return FALSE;
567 }
568 done += got;
569 }
570 Free(chunk);
571 return TRUE;
572 }
573 if (r->body && r->body->written > 0)
574 return _resp_write_all(fn, ctx, r->body->data, r->body->written);
575 return TRUE;
576}
577
578/* Parse a non-negative decimal [s, s+n) without overflow. */
579static int _resp_num(const char* s, size_t n, long long* v) {
580 size_t i;
581 *v = 0;
582 if (n == 0 || n > 18)
583 return FALSE;
584 for (i = 0; i < n; i++) {
585 if (s[i] < '0' || s[i] > '9')
586 return FALSE;
587 *v = *v * 10 + (s[i] - '0');
588 }
589 return TRUE;
590}
591
601N_HTTP_RANGE n_http_parse_range(const char* value, size_t len, long long total, long long* first, long long* last) {
602 size_t dash;
603 long long a, b;
604 if (!value || !first || !last || total < 0 || len < 7 || memcmp(value, "bytes=", 6) != 0)
605 return N_HTTP_RANGE_NONE;
606 value += 6;
607 len -= 6;
608 for (dash = 0; dash < len && value[dash] != '-'; dash++);
609 if (dash == len)
610 return N_HTTP_RANGE_NONE;
611 if (dash == 0) {
612 /* suffix range: the last n bytes */
613 if (!_resp_num(value + 1, len - 1, &b))
614 return N_HTTP_RANGE_NONE;
615 if (b == 0 || total == 0)
617 *first = b >= total ? 0 : total - b;
618 *last = total - 1;
619 return N_HTTP_RANGE_OK;
620 }
621 if (!_resp_num(value, dash, &a))
622 return N_HTTP_RANGE_NONE;
623 if (dash + 1 == len) {
624 b = total - 1;
625 } else if (!_resp_num(value + dash + 1, len - dash - 1, &b) || b < a) {
626 return N_HTTP_RANGE_NONE;
627 }
628 if (a >= total)
630 if (b >= total)
631 b = total - 1;
632 *first = a;
633 *last = b;
634 return N_HTTP_RANGE_OK;
635}
static N_GUI_MOUSE_EVENT last
the last event the widget was told of, and how many
static int h(void *ctx, const N_ROUTE_MATCH *m)
a handler that does nothing (the test only routes)
volatile sig_atomic_t done
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a line
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:272
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:204
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:263
#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 version_minor
HTTP/1.x minor version of the request (0 or 1).
long long fd_length
file body length
int status
status code, 100..599
int fd
file body descriptor (owned, closed on free), or -1
size_t nb_headers
number of headers
char * names[64]
header names, nb_headers of them
long long fd_offset
file body start offset
int head_only
non-zero: send headers only (HEAD request)
char * values[64]
header values
N_STR * body
memory body, or NULL
int keep_alive
non-zero: the connection stays open after this response
N_HTTP_RANGE n_http_parse_range(const char *value, size_t len, long long total, long long *first, long long *last)
interpret a single-range Range header for a body of total bytes
#define N_HTTP_RESPONSE_VALUE_MAX
longest header value accepted, in bytes
#define N_HTTP_RESPONSE_HEADERS_MAX
most headers a response may carry
int n_http_response_append(N_HTTP_RESP *r, const void *data, size_t len)
append bytes to the memory body (drops a file body)
const char * n_http_status_reason(int status)
reason phrase of a status code, a class phrase for an unlisted one
int n_http_response_head(const N_HTTP_RESP *r, N_STR **out)
append the status line and headers, through the blank line
int n_http_response_set_header(N_HTTP_RESP *r, const char *name, const char *value)
set a header: remove every header of that name, then add it
ssize_t(* n_http_write_fn)(void *ctx, const void *buf, size_t len)
a write function: write up to len bytes, return how many were written (at least 1) or -1 on a fatal e...
int n_http_response_add_header(N_HTTP_RESP *r, const char *name, const char *value)
add a header line after validating name and value
int n_http_response_file(N_HTTP_RESP *r, int fd, long long offset, long long length)
use a file range as the body; the response owns fd from now on
int n_http_response_set_status(N_HTTP_RESP *r, int status)
change the status code
long long n_http_response_body_length(const N_HTTP_RESP *r)
body length the response declares, 0 for a bodiless status
size_t n_http_response_remove_header(N_HTTP_RESP *r, const char *name)
remove every header of that name (ASCII case-insensitive)
N_HTTP_RANGE
result of n_http_parse_range()
int n_http_response_write(const N_HTTP_RESP *r, n_http_write_fn fn, void *ctx)
write the whole response (head, then body unless HEAD or bodiless)
int n_http_response_set_connection(N_HTTP_RESP *r, int keep_alive, int version_minor, int head_only)
connection handling for this response
void n_http_response_free(N_HTTP_RESP **r)
free a response (closing its file body) and set *r to NULL
const char * n_http_response_get_header(const N_HTTP_RESP *r, const char *name)
value of the first header of that name (ASCII case-insensitive)
N_HTTP_RESP * n_http_response_new(int status)
new response with a status, keep-alive off, HTTP/1.1
@ N_HTTP_RANGE_NONE
no usable Range: send the whole body (200)
@ N_HTTP_RANGE_UNSATISFIABLE
send 416, with a Content-Range that gives only the total
@ N_HTTP_RANGE_OK
one satisfiable range: send 206
a response under construction (fields are private, use the functions)
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
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
Definition n_str.c:209
A box including a string and his lenght.
Definition n_str.h:61
#define N_TIME_HTTP_DATE_LEN
buffer size for n_time_http_date(): "Thu, 01 Jan 1970 00:00:00 GMT" + NUL
Definition n_time.h:90
int n_time_http_date(time_t t, char *out, size_t size)
format t as an HTTP date (RFC 9110 IMF-fixdate, always GMT)
Definition n_time.c:237
Common headers and low-level functions & define.
static int _resp_write_all(n_http_write_fn fn, void *ctx, const char *buf, size_t len)
static int _resp_cat(N_STR **h, const char *str)
static int _resp_bodiless(int status)
static int _resp_token(const char *s)
#define N_HTTP_RESPONSE_CHUNK
static int _resp_ieq(const char *a, const char *b)
static ssize_t _resp_pread(int fd, char *buf, size_t len, long long offset)
static int _resp_num(const char *s, size_t n, long long *v)
static void _resp_drop_body(N_HTTP_RESP *r)
static int _resp_lc(int c)
HTTP/1.1 response: validated headers, memory or file body, framing.
Generic log system.
N_STR and string function declaration.
Timing utilities.