n_http_form: strict percent-decoding, query strings and urlencoded forms; malformed escapes, NUL bytes, ill-formed UTF-8 and field floods refused
n_http_form: strict percent-decoding, query strings and urlencoded forms; malformed escapes, NUL bytes, ill-formed UTF-8 and field floods refused
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
if (!ok) {
} else {
}
}
int opt = 0;
while ((opt = getopt(argc, argv, "V:")) != -1) {
if (opt == 'V') {
} else {
return -1;
}
}
return 0;
}
static int decodes_to(
const char* in,
int flags,
const char* want) {
int res;
if (!want)
res = (ok == FALSE);
else
res = ok == TRUE && out && out->
written == strlen(want) && memcmp(out->
data, want, out->
written) == 0;
return res;
}
}
size_t vlen = 0;
f =
n_http_form_parse(
"s=hello+world&page=2&&tag%5B%5D=a&tag%5B%5D=b&flag&e=&s=last", 60, 50, 0);
if (f) {
const char* v = NULL;
expect_true(
"last occurrence wins, like PHP", v && strcmp(v,
"last") == 0 && vlen == 4);
expect_true(
"pair without '=' has an empty value", v && v[0] ==
'\0' && vlen == 0);
expect_true(
"'name=' has an empty value", v && v[0] ==
'\0' && vlen == 0);
}
expect_true(
"bad escape fails the whole form", f == NULL);
if (f) {
}
expect_true(
"more pairs than max_fields refused, even with SKIP_INVALID", f == NULL);
}
}
}
#define BND(ct) n_http_form_multipart_boundary((ct), strlen(ct), b)
static const char ok_body[] =
"--XyZ\r\n"
"Content-Disposition: form-data; name=\"title\"\r\n\r\n"
"Hello\r\n"
"--XyZ\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"a \\\"q\\\".png\"\r\n"
"Content-Type: image/png\r\n\r\n"
"\x89PNG\r\n--XyZnot-a-delimiter\r\n\r\n\x00\x01\r\n"
"--XyZ\r\n"
"Content-Disposition: form-data; name=\"empty\"\r\n\r\n"
"\r\n"
"--XyZ\r\n"
"content-disposition: FORM-DATA; filename=\"\"; name=\"tag[]\"\r\n\r\n"
"x\r\n"
"--XyZ\r\n"
"Content-Disposition: form-data; name=\"title\"\r\n\r\n"
"Second\r\n"
"--XyZ--\r\nepilogue ignored";
const size_t ok_len = sizeof(ok_body) - 1;
expect_true(
"boundary token",
BND(
"multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxk") &&
strcmp(b, "----WebKitFormBoundary7MA4YWxk") == 0);
expect_true(
"boundary quoted, any case",
BND(
"Multipart/Form-Data; charset=utf-8; BOUNDARY=\"a b:c\"") &&
strcmp(b, "a b:c") == 0);
expect_true(
"boundary inside another quoted parameter not taken",
BND(
"multipart/form-data; x=\"boundary=evil\"; boundary=real") && strcmp(b,
"real") == 0);
expect_true(
"two boundaries refused", !
BND(
"multipart/form-data; boundary=a; boundary=b"));
expect_true(
"other media type refused", !
BND(
"multipart/mixed; boundary=a"));
!
BND(
"multipart/form-data; boundary=12345678901234567890123456789012345678901234567890123456789012345678901"));
expect_true(
"boundary with a bad character refused", !
BND(
"multipart/form-data; boundary=\"a<b\""));
expect_true(
"boundary ending in a space refused", !
BND(
"multipart/form-data; boundary=\"ab \""));
expect_true(
"unterminated quote refused", !
BND(
"multipart/form-data; boundary=\"ab"));
if (mp) {
expect_true(
"file body binary-safe, look-alike delimiter kept", f && f->
body_len == 4 + 2 + 20 + 4 + 2 && memcmp(ok_body + f->
body_off,
"\x89PNG\r\n--XyZnot-a-delimiter", 26) == 0);
expect_true(
"header names and form-data any case; filename=\"\" is a file field",
}
mp =
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; filename=\"evil\"; name=\"real\"\r\n\r\nv\r\n--XyZ--", 4);
expect_true(
"two name= refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"; name=\"b\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"two filename= refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"; filename=\"x\"; filename=\"y\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"filename* refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"; filename*=UTF-8''x\r\n\r\nv\r\n--XyZ--", 4) == NULL);
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\nContent-Disposition: form-data; name=\"b\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"no Content-Disposition refused",
mp_parse(
"--XyZ\r\nContent-Type: text/plain\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"not form-data refused",
mp_parse(
"--XyZ\r\nContent-Disposition: attachment; name=\"a\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"empty name refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"CR in a name refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\rb\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"ill-formed UTF-8 filename refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"; filename=\"\xe9.png\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"folded header refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data;\r\n name=\"a\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"header line without colon refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\nbogus\r\n\r\nv\r\n--XyZ--", 4) == NULL);
expect_true(
"missing closing delimiter (truncated upload) refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\nvalue", 4) == NULL);
expect_true(
"no blank line refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\n", 4) == NULL);
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\n1\r\n--XyZ\r\nContent-Disposition: form-data; name=\"b\"\r\n\r\n2\r\n--XyZ--", 1) == NULL);
{
size_t o = (size_t)snprintf(big, sizeof(big), "--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\nX-Pad: ");
memcpy(big + o, "\r\n\r\nv\r\n--XyZ--", 15);
}
{
char longname[300];
static char body[600];
snprintf(body, sizeof(body), "--XyZ\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\nv\r\n--XyZ--", longname);
}
mp =
mp_parse(
"preamble text\r\n--XyZ \r\nContent-Disposition: form-data; name=\"a\"\r\n\r\nv\r\n--XyZ\t\r\nContent-Disposition: form-data; name=b\r\n\r\nw\r\n--XyZ--", 4);
expect_true(
"delimiter not at the start and no CRLF before it refused",
mp_parse(
"junk--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
}
#undef BND
int main(
int argc,
char** argv) {
fprintf(stderr, "usage: %s [-V LOG_LEVEL]\n", argv[0]);
return 1;
}
return 1;
}
return 0;
}
void process_args(int argc, char **argv)
static void expect_true(const char *label, int cond)
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
#define LOG_NOTICE
normal but significant condition
#define LOG_INFO
informational
size_t nb_parts
number of parts
size_t body_len
body length
size_t value_len
length of value
char content_type[127+1]
the part's Content-Type as sent, "" when absent: untrusted
char * value
decoded value, "" for "name" or "name="
int has_filename
non-zero when the part carried a filename parameter (a file field)
size_t body_off
body offset in the parsed buffer
N_HTTP_FIELD * fields
the fields, nb_fields of them
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
#define N_HTTP_FORM_SKIP_INVALID
n_http_form_parse(): drop a pair that fails to decode instead of failing
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)
#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_RAW_BYTES
accept decoded bytes that are not well-formed UTF-8 (NUL stays refused)
void n_http_form_free(N_HTTP_FORM **form)
free a form and set *form to NULL
decoded fields, in the order they were sent
a parsed multipart/form-data body
one part of a multipart/form-data body; the body is NOT copied
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
N_STR * char_to_nstr(const char *src)
Convert a char into a N_STR, short version.
A box including a string and his lenght.
Common headers and low-level functions & define.
N_STR and string function declaration.