64 while ((opt = getopt(argc, argv,
"V:")) != -1) {
79static int decodes_to(
const char* in,
int flags,
const char* want) {
86 res = ok == TRUE && out && out->
written == strlen(want) && memcmp(out->
data, want, out->
written) == 0;
126 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);
129 const char* v = NULL;
133 expect_true(
"last occurrence wins, like PHP", v && strcmp(v,
"last") == 0 && vlen == 4);
137 expect_true(
"pair without '=' has an empty value", v && v[0] ==
'\0' && vlen == 0);
139 expect_true(
"'name=' has an empty value", v && v[0] ==
'\0' && vlen == 0);
147 expect_true(
"bad escape fails the whole form", f == NULL);
157 expect_true(
"more pairs than max_fields refused, even with SKIP_INVALID", f == NULL);
195#define BND(ct) n_http_form_multipart_boundary((ct), strlen(ct), b)
201 static const char ok_body[] =
203 "Content-Disposition: form-data; name=\"title\"\r\n\r\n"
206 "Content-Disposition: form-data; name=\"file\"; filename=\"a \\\"q\\\".png\"\r\n"
207 "Content-Type: image/png\r\n\r\n"
208 "\x89PNG\r\n--XyZnot-a-delimiter\r\n\r\n\x00\x01\r\n"
210 "Content-Disposition: form-data; name=\"empty\"\r\n\r\n"
213 "content-disposition: FORM-DATA; filename=\"\"; name=\"tag[]\"\r\n\r\n"
216 "Content-Disposition: form-data; name=\"title\"\r\n\r\n"
218 "--XyZ--\r\nepilogue ignored";
219 const size_t ok_len =
sizeof(ok_body) - 1;
222 expect_true(
"boundary token",
BND(
"multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxk") &&
223 strcmp(b,
"----WebKitFormBoundary7MA4YWxk") == 0);
224 expect_true(
"boundary quoted, any case",
BND(
"Multipart/Form-Data; charset=utf-8; BOUNDARY=\"a b:c\"") &&
225 strcmp(b,
"a b:c") == 0);
226 expect_true(
"boundary inside another quoted parameter not taken",
227 BND(
"multipart/form-data; x=\"boundary=evil\"; boundary=real") && strcmp(b,
"real") == 0);
228 expect_true(
"two boundaries refused", !
BND(
"multipart/form-data; boundary=a; boundary=b"));
229 expect_true(
"other media type refused", !
BND(
"multipart/mixed; boundary=a"));
232 !
BND(
"multipart/form-data; boundary=12345678901234567890123456789012345678901234567890123456789012345678901"));
233 expect_true(
"boundary with a bad character refused", !
BND(
"multipart/form-data; boundary=\"a<b\""));
234 expect_true(
"boundary ending in a space refused", !
BND(
"multipart/form-data; boundary=\"ab \""));
235 expect_true(
"unterminated quote refused", !
BND(
"multipart/form-data; boundary=\"ab"));
243 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);
245 expect_true(
"header names and form-data any case; filename=\"\" is a file field",
254 mp =
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; filename=\"evil\"; name=\"real\"\r\n\r\nv\r\n--XyZ--", 4);
257 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);
258 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);
259 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);
261 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);
262 expect_true(
"no Content-Disposition refused",
mp_parse(
"--XyZ\r\nContent-Type: text/plain\r\n\r\nv\r\n--XyZ--", 4) == NULL);
263 expect_true(
"not form-data refused",
mp_parse(
"--XyZ\r\nContent-Disposition: attachment; name=\"a\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
264 expect_true(
"empty name refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"\"\r\n\r\nv\r\n--XyZ--", 4) == NULL);
265 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);
266 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);
267 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);
268 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);
270 expect_true(
"missing closing delimiter (truncated upload) refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\nvalue", 4) == NULL);
271 expect_true(
"no blank line refused",
mp_parse(
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\n", 4) == NULL);
273 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);
276 size_t o = (size_t)snprintf(big,
sizeof(big),
"--XyZ\r\nContent-Disposition: form-data; name=\"a\"\r\nX-Pad: ");
279 memcpy(big + o,
"\r\n\r\nv\r\n--XyZ--", 15);
284 static char body[600];
287 snprintf(body,
sizeof(body),
"--XyZ\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\nv\r\n--XyZ--", longname);
291 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);
294 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);
306int main(
int argc,
char** argv) {
308 fprintf(stderr,
"usage: %s [-V LOG_LEVEL]\n", argv[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.