Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_http_form.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
28
29#include "nilorea/n_http_form.h"
30
31#include "nilorea/n_common.h"
32#include "nilorea/n_log.h"
33#include "nilorea/n_str.h"
34
35#include <stdlib.h>
36#include <string.h>
37
38/* Value of a hex digit, or -1. */
39static int _form_hex(unsigned char c) {
40 if (c >= '0' && c <= '9')
41 return c - '0';
42 if (c >= 'a' && c <= 'f')
43 return c - 'a' + 10;
44 if (c >= 'A' && c <= 'F')
45 return c - 'A' + 10;
46 return -1;
47}
48
49/* Decode [s, s+len) into dst (at least len + 1 bytes). Returns the decoded
50 * length, or (size_t)-1 on a malformed escape or a decoded NUL. */
51static size_t _form_decode_into(const char* s, size_t len, int flags, char* dst) {
52 size_t i, o = 0;
53 for (i = 0; i < len; i++) {
54 unsigned char c = (unsigned char)s[i];
55 if (c == '%') {
56 int hi, lo;
57 if (len - i < 3)
58 return (size_t)-1;
59 hi = _form_hex((unsigned char)s[i + 1]);
60 lo = _form_hex((unsigned char)s[i + 2]);
61 if (hi < 0 || lo < 0)
62 return (size_t)-1;
63 c = (unsigned char)((hi << 4) | lo);
64 i += 2;
65 } else if (c == '+' && (flags & N_HTTP_FORM_PLUS_SPACE)) {
66 c = ' ';
67 }
68 if (c == '\0')
69 return (size_t)-1;
70 dst[o++] = (char)c;
71 }
72 dst[o] = '\0';
73 return o;
74}
75
76/* Decode into a new NUL-terminated heap string. Returns NULL on invalid input. */
77static char* _form_decode_dup(const char* s, size_t len, int flags, size_t* out_len) {
78 char* dst = NULL;
79 size_t n;
80 Malloc(dst, char, len + 1);
81 __n_assert(dst, return NULL);
82 n = _form_decode_into(s, len, flags, dst);
83 if (n == (size_t)-1 ||
84 (!(flags & N_HTTP_FORM_RAW_BYTES) && !n_str_utf8_valid(dst, n))) {
85 Free(dst);
86 return NULL;
87 }
88 *out_len = n;
89 return dst;
90}
91
101int n_http_percent_decode(const char* s, size_t len, int flags, N_STR** out) {
102 char* dec = NULL;
103 size_t n = 0;
104 __n_assert(out, return FALSE);
105 if (!s && len > 0)
106 return FALSE;
107 dec = _form_decode_dup(s ? s : "", len, flags, &n);
108 if (!dec)
109 return FALSE;
110 if (!*out) {
111 *out = new_nstr(n + 1);
112 if (!*out) {
113 Free(dec);
114 return FALSE;
115 }
116 }
117 if (n > 0 && !nstrcat_ex(out, dec, n, 1)) {
118 Free(dec);
119 return FALSE;
120 }
121 Free(dec);
122 return TRUE;
123}
124
130 size_t i;
131 if (!form || !*form)
132 return;
133 for (i = 0; i < (*form)->nb_fields; i++) {
134 FreeNoLog((*form)->fields[i].name);
135 FreeNoLog((*form)->fields[i].value);
136 }
137 FreeNoLog((*form)->fields);
138 FreeNoLog((*form));
139}
140
149N_HTTP_FORM* n_http_form_parse(const char* s, size_t len, size_t max_fields, int flags) {
150 N_HTTP_FORM* form = NULL;
151 size_t i, start, npairs = 0;
152 int dflags = (flags & N_HTTP_FORM_RAW_BYTES) | N_HTTP_FORM_PLUS_SPACE;
153
154 if ((!s && len > 0) || max_fields == 0)
155 return NULL;
156 if (!s)
157 s = "";
158 /* count the non-empty pairs first: the cap is checked before any field
159 * is allocated, so a flood of '&' cannot cost memory */
160 for (i = 0, start = 0; i <= len; i++) {
161 if (i == len || s[i] == '&') {
162 if (i > start && ++npairs > max_fields) {
163 n_log(LOG_DEBUG, "form: more than %zu fields", max_fields);
164 return NULL;
165 }
166 start = i + 1;
167 }
168 }
169 Malloc(form, N_HTTP_FORM, 1);
170 __n_assert(form, return NULL);
171 if (npairs > 0) {
172 Malloc(form->fields, N_HTTP_FIELD, npairs);
173 if (!form->fields) {
174 Free(form);
175 return NULL;
176 }
177 }
178 for (i = 0, start = 0; i <= len; i++) {
179 size_t eq, nlen, vlen;
180 char *name, *value;
181 if (i < len && s[i] != '&')
182 continue;
183 if (i == start) {
184 start = i + 1;
185 continue;
186 }
187 eq = start;
188 while (eq < i && s[eq] != '=')
189 eq++;
190 name = _form_decode_dup(s + start, eq - start, dflags, &nlen);
191 value = eq < i ? _form_decode_dup(s + eq + 1, i - eq - 1, dflags, &vlen)
192 : _form_decode_dup("", 0, dflags, &vlen);
193 start = i + 1;
194 if (!name || !value || nlen == 0) {
195 FreeNoLog(name);
196 FreeNoLog(value);
197 if (flags & N_HTTP_FORM_SKIP_INVALID)
198 continue;
199 n_http_form_free(&form);
200 return NULL;
201 }
202 form->fields[form->nb_fields].name = name;
203 form->fields[form->nb_fields].name_len = nlen;
204 form->fields[form->nb_fields].value = value;
205 form->fields[form->nb_fields].value_len = vlen;
206 form->nb_fields++;
207 }
208 return form;
209}
210
218const char* n_http_form_get(const N_HTTP_FORM* form, const char* name, size_t* value_len) {
219 size_t i;
220 if (value_len)
221 *value_len = 0;
222 if (!form || !name)
223 return NULL;
224 for (i = form->nb_fields; i > 0; i--) {
225 const N_HTTP_FIELD* f = &form->fields[i - 1];
226 if (strcmp(f->name, name) == 0) {
227 if (value_len)
228 *value_len = f->value_len;
229 return f->value;
230 }
231 }
232 return NULL;
233}
234
241size_t n_http_form_count(const N_HTTP_FORM* form, const char* name) {
242 size_t i, count = 0;
243 if (!form || !name)
244 return 0;
245 for (i = 0; i < form->nb_fields; i++)
246 if (strcmp(form->fields[i].name, name) == 0)
247 count++;
248 return count;
249}
250
251/* Multipart/form-data, strict and zero-copy. */
252
253/* most bytes of preamble accepted before the first delimiter */
254#define _MP_PREAMBLE_MAX 1024
255
256static int _mp_lc(int c) {
257 return (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c;
258}
259
260/* ASCII case-insensitive compare of n bytes against a lowercase literal. */
261static int _mp_ieq(const char* s, size_t n, const char* lit) {
262 size_t i;
263 if (strlen(lit) != n)
264 return 0;
265 for (i = 0; i < n; i++)
266 if (_mp_lc((unsigned char)s[i]) != lit[i])
267 return 0;
268 return 1;
269}
270
271static int _mp_tchar(unsigned char c) {
272 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
273 return 1;
274 return c != '\0' && strchr("!#$%&'*+-.^_`|~", c) != NULL;
275}
276
277/* An RFC 2046 bchar (space allowed, not as the last character). */
278static int _mp_bchar(unsigned char c) {
279 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
280 return 1;
281 return c != '\0' && strchr("'()+_,-./:=? ", c) != NULL;
282}
283
284/* One parameter of a header value: name and (unquoted) value. */
285typedef struct {
286 const char* name;
287 size_t name_len;
289 size_t value_len;
290} _mp_param;
291
292/* Parse the next "; name=value" parameter at *pos of [v, v+n). Returns 1 with
293 * p filled, 0 at the end, -1 when malformed (or a value over the cap). A
294 * quoted value takes backslash escapes. */
295static int _mp_next_param(const char* v, size_t n, size_t* pos, _mp_param* p) {
296 size_t i = *pos, ns;
297 while (i < n && (v[i] == ' ' || v[i] == '\t'))
298 i++;
299 if (i == n)
300 return 0;
301 if (v[i] != ';')
302 return -1;
303 i++;
304 while (i < n && (v[i] == ' ' || v[i] == '\t'))
305 i++;
306 ns = i;
307 while (i < n && _mp_tchar((unsigned char)v[i]))
308 i++;
309 if (i == ns || i == n || v[i] != '=')
310 return -1;
311 p->name = v + ns;
312 p->name_len = i - ns;
313 i++;
314 p->value_len = 0;
315 if (i < n && v[i] == '"') {
316 i++;
317 for (;;) {
318 char c;
319 if (i >= n)
320 return -1; /* unterminated quoted string */
321 c = v[i++];
322 if (c == '"')
323 break;
324 if (c == '\\') {
325 if (i >= n)
326 return -1;
327 c = v[i++];
328 }
329 if (p->value_len >= sizeof(p->value) - 1)
330 return -1;
331 p->value[p->value_len++] = c;
332 }
333 } else {
334 while (i < n && v[i] != ';' && v[i] != ' ' && v[i] != '\t') {
335 if (!_mp_tchar((unsigned char)v[i]) || p->value_len >= sizeof(p->value) - 1)
336 return -1;
337 p->value[p->value_len++] = v[i++];
338 }
339 if (p->value_len == 0)
340 return -1;
341 }
342 p->value[p->value_len] = '\0';
343 *pos = i;
344 return 1;
345}
346
354int n_http_form_multipart_boundary(const char* content_type, size_t len, char* out) {
355 size_t i = 0, ts, pos;
356 int found = 0, r;
357 _mp_param p;
358 if (!content_type || !out)
359 return FALSE;
360 out[0] = '\0';
361 while (i < len && (content_type[i] == ' ' || content_type[i] == '\t'))
362 i++;
363 ts = i;
364 while (i < len && content_type[i] != ';' && content_type[i] != ' ' && content_type[i] != '\t')
365 i++;
366 if (!_mp_ieq(content_type + ts, i - ts, "multipart/form-data"))
367 return FALSE;
368 pos = i;
369 while ((r = _mp_next_param(content_type, len, &pos, &p)) == 1) {
370 if (!_mp_ieq(p.name, p.name_len, "boundary"))
371 continue;
372 if (found || p.value_len == 0 || p.value_len > N_HTTP_FORM_BOUNDARY_MAX || p.value[p.value_len - 1] == ' ')
373 return FALSE;
374 for (i = 0; i < p.value_len; i++)
375 if (!_mp_bchar((unsigned char)p.value[i]))
376 return FALSE;
377 memcpy(out, p.value, p.value_len + 1);
378 found = 1;
379 }
380 if (r < 0 || !found) {
381 out[0] = '\0';
382 return FALSE;
383 }
384 return TRUE;
385}
386
387/* Text fit for a name or filename: UTF-8, no control byte. */
388static int _mp_text_ok(const char* s, size_t n) {
389 size_t i;
390 for (i = 0; i < n; i++) {
391 unsigned char c = (unsigned char)s[i];
392 if (c < 0x20 || c == 0x7f)
393 return 0;
394 }
395 return n_str_utf8_valid(s, n);
396}
397
398/* First offset of the delimiter "\r\n--boundary" followed by "--", optional
399 * padding then CRLF, at or after from; *after receives the offset past the
400 * delimiter line (or past "--" for the closing one), *closing 1 for it.
401 * Returns the offset, or (size_t)-1 when there is none. */
402static size_t _mp_find_delim(const char* b, size_t len, size_t from, const char* boundary, size_t blen, size_t* after, int* closing) {
403 size_t i;
404 for (i = from; i + 4 + blen <= len; i++) {
405 size_t k;
406 if (b[i] != '\r' || b[i + 1] != '\n' || b[i + 2] != '-' || b[i + 3] != '-' || memcmp(b + i + 4, boundary, blen) != 0)
407 continue;
408 k = i + 4 + blen;
409 if (k + 2 <= len && b[k] == '-' && b[k + 1] == '-') {
410 *after = k + 2;
411 *closing = 1;
412 return i;
413 }
414 while (k < len && (b[k] == ' ' || b[k] == '\t'))
415 k++;
416 if (k + 2 <= len && b[k] == '\r' && b[k + 1] == '\n') {
417 *after = k + 2;
418 *closing = 0;
419 return i;
420 }
421 /* "--boundary" followed by anything else is body data */
422 }
423 return (size_t)-1;
424}
425
426/* Parse the header block [h, h+n) of one part into part. */
427static int _mp_part_headers(const char* h, size_t n, N_HTTP_FORM_PART* part) {
428 size_t i = 0;
429 int have_disp = 0, have_type = 0;
430 while (i < n) {
431 size_t le = i, colon, vs, ve;
432 while (le + 1 < n && !(h[le] == '\r' && h[le + 1] == '\n'))
433 le++;
434 if (le + 1 >= n)
435 le = n; /* last line, without its CRLF (the block ends before CRLFCRLF) */
436 colon = i;
437 while (colon < le && h[colon] != ':')
438 colon++;
439 if (colon == i || colon >= le)
440 return FALSE;
441 {
442 size_t k;
443 for (k = i; k < colon; k++)
444 if (!_mp_tchar((unsigned char)h[k]))
445 return FALSE; /* also refuses obs-fold continuation lines */
446 }
447 vs = colon + 1;
448 while (vs < le && (h[vs] == ' ' || h[vs] == '\t'))
449 vs++;
450 ve = le;
451 while (ve > vs && (h[ve - 1] == ' ' || h[ve - 1] == '\t'))
452 ve--;
453 {
454 size_t k;
455 for (k = vs; k < ve; k++)
456 if (((unsigned char)h[k] < 0x20 && h[k] != '\t') || h[k] == 0x7f)
457 return FALSE;
458 }
459 if (_mp_ieq(h + i, colon - i, "content-disposition")) {
460 size_t pos = vs, ts = vs;
461 int r, have_name = 0;
462 _mp_param p;
463 if (have_disp)
464 return FALSE;
465 have_disp = 1;
466 while (pos < ve && h[pos] != ';' && h[pos] != ' ' && h[pos] != '\t')
467 pos++;
468 if (!_mp_ieq(h + ts, pos - ts, "form-data"))
469 return FALSE;
470 while ((r = _mp_next_param(h, ve, &pos, &p)) == 1) {
471 if (_mp_ieq(p.name, p.name_len, "name")) {
472 if (have_name || p.value_len == 0 || p.value_len > N_HTTP_FORM_PART_NAME_MAX || !_mp_text_ok(p.value, p.value_len))
473 return FALSE;
474 memcpy(part->name, p.value, p.value_len + 1);
475 have_name = 1;
476 } else if (_mp_ieq(p.name, p.name_len, "filename")) {
478 return FALSE;
479 memcpy(part->filename, p.value, p.value_len + 1);
480 part->has_filename = 1;
481 } else if (_mp_ieq(p.name, p.name_len, "filename*")) {
482 return FALSE; /* browsers never send it for form-data; refuse the ambiguity */
483 }
484 }
485 if (r < 0 || !have_name)
486 return FALSE;
487 } else if (_mp_ieq(h + i, colon - i, "content-type")) {
488 if (have_type || ve - vs > N_HTTP_FORM_PART_TYPE_MAX)
489 return FALSE;
490 have_type = 1;
491 memcpy(part->content_type, h + vs, ve - vs);
492 part->content_type[ve - vs] = '\0';
493 }
494 i = le + 2;
495 }
496 return have_disp;
497}
498
507N_HTTP_FORM_MULTIPART* n_http_form_multipart_parse(const char* buf, size_t len, const char* boundary, size_t max_parts) {
508 N_HTTP_FORM_MULTIPART* mp = NULL;
509 size_t blen, cursor, cap = 8, after = 0;
510 int closing = 0;
511
512 if (!buf || !boundary || max_parts == 0)
513 return NULL;
514 blen = strlen(boundary);
515 if (blen == 0 || blen > N_HTTP_FORM_BOUNDARY_MAX)
516 return NULL;
517 /* the first delimiter: at the very start, or after a short preamble */
518 if (len >= 2 + blen && buf[0] == '-' && buf[1] == '-' && memcmp(buf + 2, boundary, blen) == 0) {
519 size_t k = 2 + blen;
520 while (k < len && (buf[k] == ' ' || buf[k] == '\t'))
521 k++;
522 if (k + 2 > len || buf[k] != '\r' || buf[k + 1] != '\n')
523 return NULL;
524 cursor = k + 2;
525 } else {
526 size_t d = _mp_find_delim(buf, len < _MP_PREAMBLE_MAX + 4 + blen + 2 ? len : _MP_PREAMBLE_MAX + 4 + blen + 2, 0, boundary, blen, &after, &closing);
527 if (d == (size_t)-1 || closing)
528 return NULL;
529 cursor = after;
530 }
532 __n_assert(mp, return NULL);
533 Malloc(mp->parts, N_HTTP_FORM_PART, cap);
534 if (!mp->parts) {
535 Free(mp);
536 return NULL;
537 }
538 for (;;) {
539 N_HTTP_FORM_PART* part;
540 size_t hend, d, scan;
541 /* the part's header block ends at the first CRLFCRLF */
542 scan = len - cursor < N_HTTP_FORM_PART_HEAD_MAX + 4 ? len - cursor : N_HTTP_FORM_PART_HEAD_MAX + 4;
543 for (hend = 0; hend + 3 < scan; hend++)
544 if (buf[cursor + hend] == '\r' && buf[cursor + hend + 1] == '\n' && buf[cursor + hend + 2] == '\r' && buf[cursor + hend + 3] == '\n')
545 break;
546 if (hend + 3 >= scan)
547 goto refuse; /* no blank line within the cap */
548 if (mp->nb_parts >= max_parts)
549 goto refuse;
550 if (mp->nb_parts == cap) {
551 N_HTTP_FORM_PART* grown = NULL;
552 size_t ncap = cap * 2 < max_parts ? cap * 2 : max_parts;
553 Malloc(grown, N_HTTP_FORM_PART, ncap);
554 if (!grown)
555 goto refuse;
556 memcpy(grown, mp->parts, cap * sizeof(*grown));
557 Free(mp->parts);
558 mp->parts = grown;
559 cap = ncap;
560 }
561 part = &mp->parts[mp->nb_parts];
562 memset(part, 0, sizeof(*part));
563 if (!_mp_part_headers(buf + cursor, hend, part))
564 goto refuse;
565 part->body_off = cursor + hend + 4;
566 /* the body runs to the next delimiter; an empty body has the
567 * delimiter's CRLF right after the blank line */
568 d = _mp_find_delim(buf, len, part->body_off - 2, boundary, blen, &after, &closing);
569 if (d == (size_t)-1 || d + 2 < part->body_off)
570 goto refuse; /* truncated: no delimiter after the part */
571 part->body_len = d + 2 == part->body_off ? 0 : d - part->body_off;
572 mp->nb_parts++;
573 if (closing)
574 return mp; /* the epilogue, if any, is ignored */
575 cursor = after;
576 }
577refuse:
579 return NULL;
580}
581
589 size_t i;
590 if (!mp || !name)
591 return NULL;
592 for (i = mp->nb_parts; i > 0; i--)
593 if (strcmp(mp->parts[i - 1].name, name) == 0)
594 return &mp->parts[i - 1];
595 return NULL;
596}
597
604size_t n_http_form_multipart_count(const N_HTTP_FORM_MULTIPART* mp, const char* name) {
605 size_t i, n = 0;
606 if (!mp || !name)
607 return 0;
608 for (i = 0; i < mp->nb_parts; i++)
609 if (strcmp(mp->parts[i].name, name) == 0)
610 n++;
611 return n;
612}
613
619 if (!mp || !*mp)
620 return;
621 FreeNoLog((*mp)->parts);
622 FreeNoLog(*mp);
623}
static int h(void *ctx, const N_ROUTE_MATCH *m)
a handler that does nothing (the test only routes)
static N_HTTP_SERVER * start(N_HTTP_SERVER_CONFIG *cfg, char *port, int base)
start a server on the first free port from base
static int eq(double a, double b)
Definition ex_wrap.c:93
#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
size_t nb_parts
number of parts
size_t body_len
body length
size_t value_len
length of value
Definition n_http_form.h:74
char content_type[127+1]
the part's Content-Type as sent, "" when absent: untrusted
char * value
decoded value, "" for "name" or "name="
Definition n_http_form.h:72
int has_filename
non-zero when the part carried a filename parameter (a file field)
char * name
decoded name
Definition n_http_form.h:68
size_t name_len
length of name
Definition n_http_form.h:70
size_t body_off
body offset in the parsed buffer
N_HTTP_FIELD * fields
the fields, nb_fields of them
Definition n_http_form.h:80
char name[200+1]
field name (non-empty, UTF-8, no control byte)
char filename[255+1]
the filename as sent (UTF-8, no control byte, may be ""): untrusted, never use it as a path
N_HTTP_FORM_PART * parts
the parts, in order
size_t nb_fields
number of fields
Definition n_http_form.h:82
#define N_HTTP_FORM_PART_TYPE_MAX
longest part Content-Type kept
#define N_HTTP_FORM_SKIP_INVALID
n_http_form_parse(): drop a pair that fails to decode instead of failing
Definition n_http_form.h:63
int n_http_form_multipart_boundary(const char *content_type, size_t len, char *out)
boundary of a multipart/form-data Content-Type, parsed strictly
N_HTTP_FORM_MULTIPART * n_http_form_multipart_parse(const char *buf, size_t len, const char *boundary, size_t max_parts)
parse a multipart/form-data body strictly, without copying it
#define N_HTTP_FORM_PART_HEAD_MAX
largest header block of one part
void n_http_form_multipart_free(N_HTTP_FORM_MULTIPART **mp)
free a parsed body and set *mp to NULL
size_t n_http_form_count(const N_HTTP_FORM *form, const char *name)
number of fields named name (exact, case-sensitive)
const N_HTTP_FORM_PART * n_http_form_multipart_get(const N_HTTP_FORM_MULTIPART *mp, const char *name)
the LAST part named name (PHP semantics)
const char * n_http_form_get(const N_HTTP_FORM *form, const char *name, size_t *value_len)
value of the LAST field named name (PHP semantics)
N_HTTP_FORM * n_http_form_parse(const char *s, size_t len, size_t max_fields, int flags)
decode a query string or an urlencoded body into fields
int n_http_percent_decode(const char *s, size_t len, int flags, N_STR **out)
percent-decode [s, s+len), appending to *out; malformed escapes, a decoded NUL and (without N_HTTP_FO...
size_t n_http_form_multipart_count(const N_HTTP_FORM_MULTIPART *mp, const char *name)
number of parts named name
#define N_HTTP_FORM_PLUS_SPACE
decode '+' as a space (query strings and urlencoded forms, not paths)
Definition n_http_form.h:59
#define N_HTTP_FORM_BOUNDARY_MAX
longest boundary RFC 2046 allows
#define N_HTTP_FORM_PART_NAME_MAX
longest multipart field name accepted
#define N_HTTP_FORM_PART_FILENAME_MAX
longest multipart filename accepted
#define N_HTTP_FORM_RAW_BYTES
accept decoded bytes that are not well-formed UTF-8 (NUL stays refused)
Definition n_http_form.h:61
void n_http_form_free(N_HTTP_FORM **form)
free a form and set *form to NULL
one decoded name=value pair; both are NUL-terminated and hold no NUL
Definition n_http_form.h:66
decoded fields, in the order they were sent
Definition n_http_form.h:78
a parsed multipart/form-data body
one part of a multipart/form-data body; the body is NOT copied
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
int n_str_utf8_valid(const char *s, size_t len)
Test that [s, s+len) is well-formed UTF-8 (RFC 3629): no overlong form, no surrogate,...
Definition n_str.c:1855
A box including a string and his lenght.
Definition n_str.h:61
Common headers and low-level functions & define.
static size_t _mp_find_delim(const char *b, size_t len, size_t from, const char *boundary, size_t blen, size_t *after, int *closing)
size_t value_len
size_t name_len
static int _mp_next_param(const char *v, size_t n, size_t *pos, _mp_param *p)
const char * name
#define _MP_PREAMBLE_MAX
static int _mp_part_headers(const char *h, size_t n, N_HTTP_FORM_PART *part)
static int _form_hex(unsigned char c)
Definition n_http_form.c:39
char value[255+2]
static int _mp_tchar(unsigned char c)
static int _mp_text_ok(const char *s, size_t n)
static char * _form_decode_dup(const char *s, size_t len, int flags, size_t *out_len)
Definition n_http_form.c:77
static int _mp_ieq(const char *s, size_t n, const char *lit)
static int _mp_lc(int c)
static size_t _form_decode_into(const char *s, size_t len, int flags, char *dst)
Definition n_http_form.c:51
static int _mp_bchar(unsigned char c)
Strict percent-decoding, query strings and urlencoded forms.
Generic log system.
static uint64_t le(const unsigned char *p, int n)
read a little endian unsigned value
Definition n_pth.c:233
N_STR and string function declaration.