2023-03-24 02:47:11 +01:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
|
2023-10-10 23:21:35 +02:00
|
|
|
#include "libweb/http.h"
|
2023-01-09 01:22:54 +01:00
|
|
|
#include <dynstr.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
2023-03-24 02:46:55 +01:00
|
|
|
#include <strings.h>
|
2023-03-04 04:02:14 +01:00
|
|
|
#include <time.h>
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-04-28 00:30:44 +02:00
|
|
|
#define HTTP_VERSION "HTTP/1.1"
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
struct http_ctx
|
|
|
|
{
|
|
|
|
struct ctx
|
|
|
|
{
|
|
|
|
enum state
|
|
|
|
{
|
|
|
|
START_LINE,
|
|
|
|
HEADER_CR_LINE,
|
|
|
|
BODY_LINE
|
|
|
|
} state;
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
LINE_CR,
|
|
|
|
LINE_LF
|
|
|
|
} lstate;
|
|
|
|
|
|
|
|
enum http_op op;
|
|
|
|
char *resource, *field, *value, *boundary;
|
|
|
|
size_t len;
|
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
struct payload
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
char *path;
|
|
|
|
unsigned long long len, read;
|
2023-11-17 12:53:59 +01:00
|
|
|
} payload;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
union
|
|
|
|
{
|
|
|
|
struct start_line
|
|
|
|
{
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
START_LINE_OP,
|
|
|
|
START_LINE_RESOURCE,
|
|
|
|
START_LINE_PROTOCOL
|
|
|
|
} state;
|
|
|
|
} sl;
|
|
|
|
|
|
|
|
struct multiform
|
|
|
|
{
|
|
|
|
enum mf_state
|
|
|
|
{
|
|
|
|
MF_START_BOUNDARY,
|
|
|
|
MF_HEADER_CR_LINE,
|
|
|
|
MF_BODY_BOUNDARY_LINE,
|
|
|
|
MF_END_BOUNDARY_CR_LINE
|
|
|
|
} state;
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
BODY_CR,
|
|
|
|
BODY_LF,
|
|
|
|
BODY_DATA
|
|
|
|
} bstate;
|
|
|
|
|
|
|
|
off_t len, written;
|
|
|
|
char *boundary;
|
2023-09-09 00:19:39 +02:00
|
|
|
size_t blen, nforms, nfiles, npairs;
|
2023-11-24 00:52:50 +01:00
|
|
|
FILE *f;
|
2023-01-09 01:22:54 +01:00
|
|
|
struct http_post_file *files;
|
2023-09-09 00:19:39 +02:00
|
|
|
struct http_post_pair *pairs;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
struct form
|
|
|
|
{
|
|
|
|
char *name, *filename, *tmpname, *value;
|
|
|
|
} *forms;
|
|
|
|
} mf;
|
2023-11-17 12:53:59 +01:00
|
|
|
|
|
|
|
struct put
|
|
|
|
{
|
|
|
|
char *tmpname;
|
2023-11-24 00:52:50 +01:00
|
|
|
FILE *f;
|
2023-11-17 12:53:59 +01:00
|
|
|
} put;
|
2023-01-09 01:22:54 +01:00
|
|
|
} u;
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
|
|
|
|
struct http_arg *args;
|
2023-11-18 00:56:04 +01:00
|
|
|
size_t n_args, n_headers;
|
|
|
|
struct http_header *headers;
|
2023-11-17 12:53:59 +01:00
|
|
|
bool has_length, expect_continue;
|
2023-01-09 01:22:54 +01:00
|
|
|
} ctx;
|
|
|
|
|
|
|
|
struct write_ctx
|
|
|
|
{
|
2023-03-20 03:32:00 +01:00
|
|
|
bool pending, close;
|
2023-01-09 01:22:54 +01:00
|
|
|
enum state state;
|
|
|
|
struct http_response r;
|
|
|
|
off_t n;
|
|
|
|
struct dynstr d;
|
2023-11-12 01:28:48 +01:00
|
|
|
enum http_op op;
|
2023-01-09 01:22:54 +01:00
|
|
|
} wctx;
|
|
|
|
|
|
|
|
/* From RFC9112, section 3 (Request line):
|
|
|
|
* It is RECOMMENDED that all HTTP senders and recipients support,
|
|
|
|
* at a minimum, request-line lengths of 8000 octets. */
|
|
|
|
char line[8000];
|
|
|
|
struct http_cfg cfg;
|
|
|
|
};
|
|
|
|
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
static void arg_free(struct http_arg *const a)
|
|
|
|
{
|
|
|
|
if (a)
|
|
|
|
{
|
|
|
|
free(a->key);
|
|
|
|
free(a->value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t chrcnt(const char *s, const int c)
|
|
|
|
{
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
while (*s++ == c)
|
|
|
|
ret++;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_arg(struct ctx *const c, const char *const arg,
|
|
|
|
const size_t n)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct http_arg a = {0}, *args = NULL;
|
|
|
|
const char *sep = memchr(arg, '=', n);
|
2023-11-12 00:14:05 +01:00
|
|
|
char *enckey = NULL, *encvalue = NULL, *deckey = NULL, *decvalue = NULL;
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
|
|
|
|
if (!sep)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected '='\n", __func__);
|
|
|
|
ret = 1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (sep == arg)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected key\n", __func__);
|
|
|
|
ret = 1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const value = sep + 1;
|
|
|
|
|
|
|
|
if (!*value)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing value: %.*s\n", __func__, (int)n, arg);
|
|
|
|
ret = 1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t keylen = sep - arg, valuelen = n - keylen - 1;
|
|
|
|
|
2023-04-30 23:43:10 +02:00
|
|
|
if (!(enckey = strndup(arg, keylen)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3) key: %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(encvalue = strndup(value, valuelen)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3) value: %s\n",
|
|
|
|
__func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
/* URL parameters use '+' for whitespace, rather than %20. */
|
2023-11-12 00:14:05 +01:00
|
|
|
else if ((ret = http_decode_url(enckey, true, &deckey)))
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
{
|
2023-11-12 00:14:05 +01:00
|
|
|
fprintf(stderr, "%s: http_decode_url enckey failed\n", __func__);
|
2023-04-30 23:43:10 +02:00
|
|
|
goto end;
|
|
|
|
}
|
2023-11-12 00:14:05 +01:00
|
|
|
else if ((ret = http_decode_url(encvalue, true, &decvalue)))
|
2023-04-30 23:43:10 +02:00
|
|
|
{
|
2023-11-12 00:14:05 +01:00
|
|
|
fprintf(stderr, "%s: http_decode_url encvalue failed\n", __func__);
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
goto end;
|
|
|
|
}
|
2023-11-12 00:14:05 +01:00
|
|
|
|
|
|
|
a = (const struct http_arg)
|
|
|
|
{
|
|
|
|
.key = deckey,
|
|
|
|
.value = decvalue
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!(args = realloc(c->args, (c->n_args + 1) * sizeof *args)))
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
args[c->n_args++] = a;
|
|
|
|
c->args = args;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (ret)
|
2023-11-12 00:14:05 +01:00
|
|
|
{
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
arg_free(&a);
|
2023-11-12 00:14:05 +01:00
|
|
|
free(deckey);
|
|
|
|
free(decvalue);
|
|
|
|
}
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
|
2023-04-30 23:43:10 +02:00
|
|
|
free(enckey);
|
|
|
|
free(encvalue);
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_first_arg(struct ctx *const c, const char *const arg,
|
|
|
|
const char *const ad_arg, const char *const res)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
const char *const next = arg + 1;
|
|
|
|
|
|
|
|
if (chrcnt(next, '?'))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: more than one argument indicator '?' found: %s\n",
|
|
|
|
__func__, res);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t n = ad_arg ? ad_arg - next : strlen(next);
|
|
|
|
|
|
|
|
if (!n)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unterminated argument: %s\n", __func__, res);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if ((error = parse_arg(c, next, n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: parse_arg failed: %s\n", __func__, res);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_adargs(struct ctx *const c, const char *const start,
|
|
|
|
const char *const res)
|
|
|
|
{
|
|
|
|
for (const char *arg = start, *next; arg; arg = next)
|
|
|
|
{
|
|
|
|
next = strchr(++arg, '&');
|
|
|
|
|
|
|
|
int error;
|
|
|
|
const size_t n = next ? next - arg : strlen(arg);
|
|
|
|
|
|
|
|
if ((error = parse_arg(c, arg, n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: parse_arg failed: %s\n", __func__, res);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_args(struct ctx *const c, const char *const res,
|
|
|
|
size_t *const reslen)
|
|
|
|
{
|
|
|
|
int error;
|
2023-07-05 23:34:36 +02:00
|
|
|
const char *const tmp_arg_start = strrchr(res, '?'),
|
|
|
|
*const arg_start = tmp_arg_start && *(tmp_arg_start + 1) ?
|
|
|
|
tmp_arg_start : NULL,
|
|
|
|
*const ad_arg = arg_start ? strchr(arg_start, '&') : NULL;
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
|
|
|
|
if (!arg_start)
|
|
|
|
{
|
|
|
|
if (!ad_arg)
|
|
|
|
{
|
|
|
|
*reslen = strlen(res);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected argument indicator '?': %s\n",
|
|
|
|
__func__, res);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (arg_start == res)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected resource: %s\n", __func__, res);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (ad_arg && ad_arg <= arg_start)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected '?' before '&': %s\n", __func__, res);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if ((error = parse_first_arg(c, arg_start, ad_arg, res)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: parse_first_arg failed\n", __func__);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
else if ((error = parse_adargs(c, ad_arg, res)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: parse_adargs failed\n", __func__);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
*reslen = arg_start - res;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_resource(struct ctx *const c, const char *const enc_res)
|
|
|
|
{
|
|
|
|
int ret = -1, error;
|
|
|
|
size_t reslen;
|
|
|
|
char *trimmed_encres = NULL, *resource = NULL;
|
|
|
|
|
2023-04-30 23:43:10 +02:00
|
|
|
if ((error = parse_args(c, enc_res, &reslen)))
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: parse_args failed\n", __func__);
|
|
|
|
ret = error;
|
|
|
|
goto end;
|
|
|
|
}
|
2023-04-30 23:43:10 +02:00
|
|
|
else if (!(trimmed_encres = strndup(enc_res, reslen)))
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
2023-11-12 00:14:05 +01:00
|
|
|
else if ((ret = http_decode_url(trimmed_encres, false, &resource)))
|
2023-04-30 23:43:10 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_decode_url failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
|
2023-04-30 23:43:10 +02:00
|
|
|
c->resource = resource;
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
2023-04-30 23:43:10 +02:00
|
|
|
free(trimmed_encres);
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
static int get_op(const char *const line, const size_t n,
|
|
|
|
enum http_op *const out)
|
|
|
|
{
|
|
|
|
static const char *const ops[] =
|
|
|
|
{
|
|
|
|
[HTTP_OP_GET] = "GET",
|
|
|
|
[HTTP_OP_POST] = "POST",
|
|
|
|
[HTTP_OP_HEAD] = "HEAD",
|
|
|
|
[HTTP_OP_PUT] = "PUT"
|
|
|
|
};
|
|
|
|
|
|
|
|
for (enum http_op op = 0; op < sizeof ops / sizeof *ops; op++)
|
|
|
|
if (!strncmp(line, ops[op], n))
|
|
|
|
{
|
|
|
|
*out = op;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int start_line(struct http_ctx *const h)
|
|
|
|
{
|
2023-08-01 02:24:28 +02:00
|
|
|
const char *const line = h->line;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (!*line)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-empty line\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const op = strchr(line, ' ');
|
|
|
|
|
|
|
|
if (!op || op == line)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected resource\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
const size_t n = op - line;
|
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
if (get_op(line, n, &c->op))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unsupported HTTP op %.*s\n",
|
|
|
|
__func__, (int)n, line);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const resource = op + 1,
|
|
|
|
*const res_end = strchr(resource, ' ');
|
|
|
|
|
|
|
|
if (!res_end)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected protocol version\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t res_n = res_end - resource;
|
|
|
|
|
|
|
|
if (memchr(resource, '*', res_n))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: illegal character * in resource %.*s\n",
|
|
|
|
__func__, (int)res_n, resource);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const protocol = res_end + 1;
|
|
|
|
|
|
|
|
if (!*protocol)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected protocol version\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (strchr(protocol, ' '))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unexpected field after protocol version\n",
|
|
|
|
__func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
int ret = 1, error;
|
2023-01-09 01:22:54 +01:00
|
|
|
char *enc_res = NULL;
|
|
|
|
|
2023-04-28 00:30:44 +02:00
|
|
|
if (strcmp(protocol, HTTP_VERSION))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unsupported protocol %s\n", __func__, protocol);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(enc_res = strndup(resource, res_n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
|
|
|
|
ret = -1;
|
|
|
|
goto end;
|
|
|
|
}
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
else if ((error = parse_resource(c, enc_res)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
fprintf(stderr, "%s: parse_resource failed\n", __func__);
|
|
|
|
ret = error;
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("%.*s %s %s\n", (int)n, line, c->resource, protocol);
|
|
|
|
ret = 0;
|
|
|
|
c->state = HEADER_CR_LINE;
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(enc_res);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ctx_free(struct ctx *const c)
|
|
|
|
{
|
|
|
|
if (c->boundary)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
|
|
|
|
free(m->files);
|
2023-09-09 00:19:39 +02:00
|
|
|
free(m->pairs);
|
2023-01-09 01:22:54 +01:00
|
|
|
free(m->boundary);
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
if (m->f && fclose(m->f))
|
|
|
|
fprintf(stderr, "%s: fclose(3) m->f: %s\n",
|
2023-03-08 18:02:36 +01:00
|
|
|
__func__, strerror(errno));
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
for (size_t i = 0; i < m->nforms; i++)
|
|
|
|
{
|
|
|
|
struct form *const f = &m->forms[i];
|
|
|
|
|
|
|
|
free(f->name);
|
|
|
|
free(f->filename);
|
|
|
|
free(f->value);
|
2023-03-08 18:02:36 +01:00
|
|
|
|
|
|
|
if (f->tmpname && remove(f->tmpname) && errno != ENOENT)
|
|
|
|
fprintf(stderr, "%s: remove(3) %s: %s\n",
|
|
|
|
__func__, f->tmpname, strerror(errno));
|
|
|
|
|
|
|
|
free(f->tmpname);
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
free(m->forms);
|
|
|
|
}
|
2023-11-17 12:53:59 +01:00
|
|
|
else if (c->op == HTTP_OP_PUT)
|
|
|
|
{
|
|
|
|
struct put *const p = &c->u.put;
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
if (p->f >= 0 && fclose(p->f))
|
|
|
|
fprintf(stderr, "%s: fclose(3) p->f: %s\n",
|
2023-11-17 12:53:59 +01:00
|
|
|
__func__, strerror(errno));
|
|
|
|
|
|
|
|
free(c->u.put.tmpname);
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
free(c->field);
|
|
|
|
free(c->value);
|
|
|
|
free(c->resource);
|
|
|
|
free(c->boundary);
|
|
|
|
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
for (size_t i = 0; i < c->n_args; i++)
|
|
|
|
arg_free(&c->args[i]);
|
|
|
|
|
2023-11-18 00:56:04 +01:00
|
|
|
for (size_t i = 0; i < c->n_headers; i++)
|
|
|
|
{
|
|
|
|
const struct http_header *const hdr = &c->headers[i];
|
|
|
|
|
|
|
|
free(hdr->header);
|
|
|
|
free(hdr->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(c->headers);
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
free(c->args);
|
2023-01-09 01:22:54 +01:00
|
|
|
*c = (const struct ctx){0};
|
|
|
|
}
|
|
|
|
|
2023-11-12 00:50:06 +01:00
|
|
|
static void free_response_headers(struct http_response *const r)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < r->n_headers; i++)
|
|
|
|
{
|
|
|
|
const struct http_header *const hdr = &r->headers[i];
|
|
|
|
|
|
|
|
free(hdr->header);
|
|
|
|
free(hdr->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(r->headers);
|
|
|
|
r->headers = NULL;
|
|
|
|
r->n_headers = 0;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int prepare_headers(struct http_ctx *const h)
|
|
|
|
{
|
2023-11-12 00:50:06 +01:00
|
|
|
int ret = -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
struct dynstr *const d = &w->d;
|
2023-11-12 00:50:06 +01:00
|
|
|
struct http_response *const r = &w->r;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
dynstr_init(d);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < w->r.n_headers; i++)
|
|
|
|
{
|
2023-11-12 00:50:06 +01:00
|
|
|
const struct http_header *const hdr = &r->headers[i];
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-12 00:50:06 +01:00
|
|
|
if (dynstr_append(d, "%s: %s\r\n", hdr->header, hdr->value))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append hdr failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-11-12 00:50:06 +01:00
|
|
|
if (dynstr_append(d, "\r\n"))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append crlf failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
end:
|
|
|
|
free_response_headers(r);
|
|
|
|
return ret;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int rw_error(const int r, bool *const close)
|
|
|
|
{
|
|
|
|
if (r < 0)
|
|
|
|
{
|
|
|
|
switch (errno)
|
|
|
|
{
|
2023-05-01 03:06:34 +02:00
|
|
|
case EPIPE:
|
|
|
|
/* Fall through. */
|
2023-01-09 01:22:54 +01:00
|
|
|
case ECONNRESET:
|
|
|
|
*close = true;
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
case EAGAIN:
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (!r)
|
|
|
|
{
|
|
|
|
*close = true;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: unexpected value %d\n", __func__, r);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_start_line(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
struct dynstr *const d = &w->d;
|
|
|
|
const size_t rem = d->len - w->n;
|
2023-03-08 18:52:36 +01:00
|
|
|
const int res = h->cfg.write(d->str + w->n, rem, h->cfg.user);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (res <= 0)
|
|
|
|
return rw_error(res, close);
|
|
|
|
else if ((w->n += res) >= d->len)
|
|
|
|
{
|
|
|
|
char len[sizeof "18446744073709551615"];
|
|
|
|
const int res = snprintf(len, sizeof len, "%llu", w->r.n);
|
|
|
|
|
|
|
|
dynstr_free(d);
|
|
|
|
|
|
|
|
if (res < 0 || res >= sizeof len)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: snprintf(3) failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (http_response_add_header(&w->r, "Content-Length", len))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (prepare_headers(h))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: prepare_headers failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
w->state = HEADER_CR_LINE;
|
|
|
|
w->n = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_ctx_free(struct write_ctx *const w)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
const struct http_response *const r = &w->r;
|
|
|
|
|
|
|
|
if (r->free)
|
|
|
|
r->free(r->buf.rw);
|
|
|
|
|
|
|
|
if (r->f && (ret = fclose(r->f)))
|
|
|
|
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
|
|
|
|
|
|
|
|
dynstr_free(&w->d);
|
2023-11-12 00:50:06 +01:00
|
|
|
free_response_headers(&w->r);
|
2023-01-09 01:22:54 +01:00
|
|
|
*w = (const struct write_ctx){0};
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-11-12 01:28:48 +01:00
|
|
|
static bool must_write_body(const struct write_ctx *const w)
|
|
|
|
{
|
|
|
|
return w->op != HTTP_OP_HEAD;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int write_header_cr_line(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
struct dynstr *const d = &w->d;
|
|
|
|
const size_t rem = d->len - w->n;
|
|
|
|
const int res = h->cfg.write(d->str + w->n, rem, h->cfg.user);
|
|
|
|
|
|
|
|
if (res <= 0)
|
|
|
|
return rw_error(res, close);
|
|
|
|
else if ((w->n += res) >= d->len)
|
|
|
|
{
|
2023-03-20 03:32:00 +01:00
|
|
|
const bool close_pending = w->close;
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
dynstr_free(d);
|
|
|
|
|
2023-11-12 01:28:48 +01:00
|
|
|
if (w->r.n && must_write_body(w))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
w->state = BODY_LINE;
|
|
|
|
w->n = 0;
|
|
|
|
}
|
|
|
|
else if (write_ctx_free(w))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-03-20 03:32:00 +01:00
|
|
|
else if (close_pending)
|
|
|
|
*close = true;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_body_mem(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
const struct http_response *const r = &w->r;
|
|
|
|
const size_t rem = r->n - w->n;
|
2023-04-29 00:31:12 +02:00
|
|
|
const int res = h->cfg.write((const char *)r->buf.ro + w->n, rem,
|
2023-01-09 01:22:54 +01:00
|
|
|
h->cfg.user);
|
|
|
|
|
|
|
|
if (res <= 0)
|
|
|
|
return rw_error(res, close);
|
|
|
|
else if ((w->n += res) >= r->n)
|
|
|
|
{
|
2023-03-20 03:32:00 +01:00
|
|
|
const bool close_pending = w->close;
|
|
|
|
|
2023-04-29 00:31:12 +02:00
|
|
|
if (write_ctx_free(w))
|
|
|
|
{
|
2023-03-20 03:32:00 +01:00
|
|
|
fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
|
2023-04-29 00:31:12 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2023-03-20 03:32:00 +01:00
|
|
|
else if (close_pending)
|
|
|
|
*close = true;
|
|
|
|
|
|
|
|
return res;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_body_file(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
const struct http_response *const r = &w->r;
|
|
|
|
const unsigned long long left = r->n - w->n;
|
2023-09-07 16:01:37 +02:00
|
|
|
char buf[BUFSIZ];
|
2023-01-09 01:22:54 +01:00
|
|
|
const size_t rem = left > sizeof buf ? sizeof buf : left;
|
|
|
|
|
|
|
|
if (!fread(buf, 1, rem, r->f))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fread(3) failed, ferror=%d, feof=%d\n",
|
|
|
|
__func__, ferror(r->f), feof(r->f));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-04-29 00:31:12 +02:00
|
|
|
const int res = h->cfg.write(buf, rem, h->cfg.user);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (res <= 0)
|
|
|
|
return rw_error(res, close);
|
|
|
|
else if ((w->n += res) >= r->n)
|
|
|
|
{
|
2023-03-20 03:32:00 +01:00
|
|
|
const bool close_pending = w->close;
|
|
|
|
|
2023-04-29 00:31:12 +02:00
|
|
|
if (write_ctx_free(w))
|
|
|
|
{
|
2023-03-20 03:32:00 +01:00
|
|
|
fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
|
2023-04-29 00:31:12 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2023-03-20 03:32:00 +01:00
|
|
|
else if (close_pending)
|
|
|
|
*close = true;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int write_body_line(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
const struct http_response *const r = &h->wctx.r;
|
|
|
|
|
|
|
|
if (r->buf.ro)
|
|
|
|
return write_body_mem(h, close);
|
|
|
|
else if (r->f)
|
|
|
|
return write_body_file(h, close);
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: expected either buffer or file path\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int http_write(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
static int (*const fn[])(struct http_ctx *, bool *) =
|
|
|
|
{
|
|
|
|
[START_LINE] = write_start_line,
|
|
|
|
[HEADER_CR_LINE] = write_header_cr_line,
|
|
|
|
[BODY_LINE] = write_body_line,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
|
|
|
|
const int ret = fn[w->state](h, close);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
write_ctx_free(w);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int http_response_add_header(struct http_response *const r,
|
|
|
|
const char *const header, const char *const value)
|
|
|
|
{
|
2023-03-04 02:06:19 +01:00
|
|
|
const size_t n = r->n_headers + 1;
|
|
|
|
struct http_header *const headers = realloc(r->headers,
|
|
|
|
n * sizeof *r->headers), *h = NULL;
|
|
|
|
|
|
|
|
if (!headers)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
h = &headers[r->n_headers];
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
*h = (const struct http_header)
|
|
|
|
{
|
|
|
|
.header = strdup(header),
|
|
|
|
.value = strdup(value)
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!h->header || !h->value)
|
|
|
|
{
|
2023-03-04 02:06:19 +01:00
|
|
|
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
free(h->header);
|
|
|
|
free(h->value);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
r->headers = headers;
|
|
|
|
r->n_headers = n;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int start_response(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
static const struct code
|
|
|
|
{
|
|
|
|
const char *descr;
|
|
|
|
int code;
|
|
|
|
} codes[] =
|
|
|
|
{
|
|
|
|
#define X(x, y, z) [HTTP_STATUS_##x] = {.descr = y, .code = z},
|
|
|
|
HTTP_STATUSES
|
|
|
|
#undef X
|
|
|
|
};
|
|
|
|
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
const struct code *const c = &codes[w->r.status];
|
|
|
|
|
|
|
|
w->pending = true;
|
|
|
|
dynstr_init(&w->d);
|
2023-11-12 00:50:06 +01:00
|
|
|
|
|
|
|
if (dynstr_append(&w->d, HTTP_VERSION " %d %s\r\n", c->code, c->descr))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_cookie(struct http_ctx *const h, const char *const cookie)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
const char *const value = strchr(cookie, '=');
|
|
|
|
|
|
|
|
if (!value)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected field=value for cookie %s\n",
|
|
|
|
__func__, cookie);
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
else if (!*(value + 1))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-empty value for cookie %s\n",
|
|
|
|
__func__, cookie);
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
else if (!(c->field = strndup(cookie, value - cookie)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3) field: %s\n", __func__, strerror(errno));
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
else if (!(c->value = strdup(value + 1)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3) value: %s\n", __func__, strerror(errno));
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
failure:
|
|
|
|
free(c->field);
|
|
|
|
free(c->value);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_length(struct http_ctx *const h, const char *const len)
|
|
|
|
{
|
2023-03-04 02:34:55 +01:00
|
|
|
char *end;
|
|
|
|
|
|
|
|
errno = 0;
|
2023-11-17 12:53:59 +01:00
|
|
|
const unsigned long long value = strtoull(len, &end, 10);
|
2023-03-04 02:34:55 +01:00
|
|
|
|
|
|
|
if (errno || *end != '\0')
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid length %s: %s\n",
|
|
|
|
__func__, len, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
|
|
|
|
switch (c->op)
|
|
|
|
{
|
|
|
|
case HTTP_OP_POST:
|
|
|
|
/* Fall through. */
|
|
|
|
case HTTP_OP_PUT:
|
|
|
|
c->payload.len = value;
|
2023-11-24 00:52:50 +01:00
|
|
|
c->u.put = (const struct put){0};
|
2023-11-17 12:53:59 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case HTTP_OP_GET:
|
|
|
|
/* Fall through. */
|
|
|
|
case HTTP_OP_HEAD:
|
|
|
|
fprintf(stderr, "%s: unexpected header for HTTP op %d\n",
|
|
|
|
__func__, c->op);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->has_length = true;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_content_type(struct http_ctx *const h, const char *const type)
|
|
|
|
{
|
|
|
|
const char *const sep = strchr(type, ';');
|
|
|
|
|
|
|
|
if (!sep)
|
|
|
|
/* No multipart/form-data expected. */
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
const size_t n = sep - type;
|
|
|
|
|
|
|
|
if (strncmp(type, "multipart/form-data", n))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unsupported Content-Type %.*s\n",
|
|
|
|
__func__, (int)n, type);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-11-17 14:56:07 +01:00
|
|
|
else if (h->ctx.op != HTTP_OP_POST)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: multipart/form-data only expected for POST\n",
|
|
|
|
__func__);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
const char *boundary = sep + 1;
|
|
|
|
|
|
|
|
while (*boundary == ' ')
|
|
|
|
boundary++;
|
|
|
|
|
|
|
|
if (!*boundary)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected boundary\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const eq = strchr(boundary, '=');
|
|
|
|
|
|
|
|
if (!eq)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected = after boundary\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t bn = eq - boundary;
|
|
|
|
|
|
|
|
if (strncmp(boundary, "boundary", bn))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected boundary, got %.*s\n",
|
|
|
|
__func__, (int)bn, boundary);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *val = eq + 1;
|
|
|
|
|
|
|
|
while (*val == ' ')
|
|
|
|
val++;
|
|
|
|
|
|
|
|
if (!*val)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected value after boundary\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct dynstr b;
|
|
|
|
|
|
|
|
dynstr_init(&b);
|
|
|
|
|
|
|
|
if (dynstr_append(&b, "\r\n--%s", val))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->boundary = b.str;
|
2023-11-24 00:52:50 +01:00
|
|
|
c->u.mf = (const struct multiform){0};
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
static struct http_payload ctx_to_payload(const struct ctx *const c)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
return (const struct http_payload)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
.cookie =
|
|
|
|
{
|
|
|
|
.field = c->field,
|
|
|
|
.value = c->value
|
|
|
|
},
|
|
|
|
|
|
|
|
.op = c->op,
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
.resource = c->resource,
|
|
|
|
.args = c->args,
|
2023-11-18 00:56:04 +01:00
|
|
|
.n_args = c->n_args,
|
|
|
|
.headers = c->headers,
|
|
|
|
.n_headers = c->n_headers
|
2023-01-09 01:22:54 +01:00
|
|
|
};
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
static int process_payload(struct http_ctx *const h)
|
Support URL parameters
Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:
/test?key1=value1&key2=value2
This will generate two parameters, with the following values:
{
.args =
{
[0] = {.key = "key1", .value = "value1"},
[1] = {.key = "key2", .value = "value2"}
},
.n_args = 2
}
As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:
{.args = {...}, .resource = "/test"}
Limitations:
- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
2023-04-23 05:09:53 +02:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
const struct http_payload p = ctx_to_payload(c);
|
2023-01-09 01:22:54 +01:00
|
|
|
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
|
|
|
|
|
2023-11-12 01:28:48 +01:00
|
|
|
h->wctx.op = c->op;
|
2023-01-09 01:22:54 +01:00
|
|
|
ctx_free(c);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return start_response(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_field_value(const char *const line, size_t *const n,
|
|
|
|
const char **const value)
|
|
|
|
{
|
|
|
|
const char *const field = strchr(line, ':');
|
|
|
|
|
|
|
|
if (!field || line == field)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected field:value\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*n = field - line;
|
|
|
|
*value = field + 1;
|
|
|
|
|
|
|
|
if (!**value)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected value\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (**value == ' ')
|
|
|
|
(*value)++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int expect(struct http_ctx *const h, const char *const value)
|
|
|
|
{
|
|
|
|
if (!strcmp(value, "100-continue"))
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
if (!c->has_length)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: 100-continue without expected content\n",
|
|
|
|
__func__);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
switch (c->op)
|
|
|
|
{
|
|
|
|
case HTTP_OP_POST:
|
|
|
|
/* Fall through. */
|
|
|
|
case HTTP_OP_PUT:
|
|
|
|
c->expect_continue = true;
|
|
|
|
break;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
case HTTP_OP_GET:
|
|
|
|
/* Fall through. */
|
|
|
|
case HTTP_OP_HEAD:
|
|
|
|
fprintf(stderr, "%s: unexpected op %d\n", __func__, c->op);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-11-18 00:56:04 +01:00
|
|
|
static int append_header(struct http_ctx *const h, const char *const line,
|
|
|
|
const size_t n, const char *const value)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
|
|
|
|
if (c->n_headers >= h->cfg.max_headers)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
struct http_header *const headers = realloc(c->headers,
|
|
|
|
(c->n_headers + 1) * sizeof *headers);
|
|
|
|
char *headerdup = NULL, *valuedup = NULL;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
if (!headers)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->headers = headers;
|
|
|
|
|
|
|
|
if (!(headerdup = strndup(line, n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(valuedup = strdup(value)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->headers[c->n_headers++] = (const struct http_header)
|
|
|
|
{
|
|
|
|
.header = headerdup,
|
|
|
|
.value = valuedup
|
|
|
|
};
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (ret)
|
|
|
|
{
|
|
|
|
free(headerdup);
|
|
|
|
free(valuedup);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int process_header(struct http_ctx *const h, const char *const line,
|
|
|
|
const size_t n, const char *const value)
|
|
|
|
{
|
|
|
|
static const struct header
|
|
|
|
{
|
|
|
|
const char *header;
|
|
|
|
int (*f)(struct http_ctx *, const char *);
|
|
|
|
} headers[] =
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.header = "Cookie",
|
|
|
|
.f = set_cookie
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
.header = "Content-Length",
|
|
|
|
.f = set_length
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
.header = "Expect",
|
|
|
|
.f = expect
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
.header = "Content-Type",
|
|
|
|
.f = set_content_type
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof headers / sizeof *headers; i++)
|
|
|
|
{
|
|
|
|
const struct header *const hdr = &headers[i];
|
|
|
|
int ret;
|
|
|
|
|
2023-03-04 04:04:09 +01:00
|
|
|
if (!strncasecmp(line, hdr->header, n) && (ret = hdr->f(h, value)))
|
2023-01-09 01:22:54 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-11-18 00:56:04 +01:00
|
|
|
return append_header(h, line, n, value);
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
static int check_length(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
const struct http_cookie cookie =
|
|
|
|
{
|
|
|
|
.field = c->field,
|
|
|
|
.value = c->value
|
|
|
|
};
|
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
return h->cfg.length(c->payload.len, &cookie, &h->wctx.r, h->cfg.user);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int process_expect(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
const int res = check_length(h);
|
|
|
|
|
|
|
|
if (res)
|
|
|
|
{
|
|
|
|
if (res < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: check_length failed\n", __func__);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
h->wctx.close = true;
|
|
|
|
return start_response(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct http_payload p = ctx_to_payload(c);
|
|
|
|
|
|
|
|
p.expect_continue = true;
|
|
|
|
|
|
|
|
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
|
|
|
|
|
|
|
|
h->wctx.op = c->op;
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
c->state = BODY_LINE;
|
|
|
|
return start_response(h);
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int header_cr_line(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
const char *const line = (const char *)h->line;
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
|
|
|
|
if (!*line)
|
|
|
|
{
|
|
|
|
switch (c->op)
|
|
|
|
{
|
|
|
|
case HTTP_OP_GET:
|
2023-10-02 23:37:44 +02:00
|
|
|
/* Fall through. */
|
|
|
|
case HTTP_OP_HEAD:
|
2023-11-17 12:53:59 +01:00
|
|
|
return process_payload(h);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
case HTTP_OP_POST:
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
{
|
2023-11-17 12:53:59 +01:00
|
|
|
if (!c->payload.len)
|
|
|
|
return process_payload(h);
|
|
|
|
else if (c->expect_continue)
|
|
|
|
return process_expect(h);
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
else if (c->boundary)
|
|
|
|
{
|
|
|
|
const int res = check_length(h);
|
|
|
|
|
|
|
|
if (res)
|
2023-03-20 03:32:00 +01:00
|
|
|
{
|
2023-09-07 13:40:11 +02:00
|
|
|
if (res < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: check_length failed\n",
|
|
|
|
__func__);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2023-03-20 03:32:00 +01:00
|
|
|
h->wctx.close = true;
|
|
|
|
return start_response(h);
|
|
|
|
}
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
c->state = BODY_LINE;
|
|
|
|
return 0;
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
}
|
2023-11-17 12:53:59 +01:00
|
|
|
|
|
|
|
case HTTP_OP_PUT:
|
|
|
|
if (!c->has_length)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected Content-Length header\n",
|
|
|
|
__func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (!c->payload.len)
|
|
|
|
return process_payload(h);
|
|
|
|
else if (c->expect_continue)
|
|
|
|
return process_expect(h);
|
|
|
|
|
|
|
|
c->state = BODY_LINE;
|
|
|
|
return 0;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *value;
|
|
|
|
size_t n;
|
|
|
|
const int ret = get_field_value(line, &n, &value);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return process_header(h, line, n, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int send_payload(struct http_ctx *const h,
|
|
|
|
const struct http_payload *const p)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
const int ret = h->cfg.payload(p, &h->wctx.r, h->cfg.user);
|
|
|
|
|
|
|
|
ctx_free(c);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return start_response(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int update_lstate(struct http_ctx *const h, bool *const close,
|
2024-01-20 01:05:05 +01:00
|
|
|
int (*const f)(struct http_ctx *), const char *const buf,
|
|
|
|
size_t *const sz)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
int ret = 1;
|
|
|
|
struct ctx *const c = &h->ctx;
|
2024-01-20 01:05:05 +01:00
|
|
|
const char b = *buf;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
switch (c->lstate)
|
|
|
|
{
|
|
|
|
case LINE_CR:
|
|
|
|
if (b == '\r')
|
|
|
|
c->lstate = LINE_LF;
|
|
|
|
else if (c->len < sizeof h->line - 2)
|
|
|
|
h->line[c->len++] = b;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: line too long\n", __func__);
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LINE_LF:
|
|
|
|
if (b == '\n')
|
|
|
|
{
|
|
|
|
h->line[c->len] = '\0';
|
|
|
|
|
|
|
|
if ((ret = f(h)))
|
|
|
|
goto failure;
|
|
|
|
|
|
|
|
c->len = 0;
|
|
|
|
}
|
|
|
|
else if (c->len < sizeof h->line - 3)
|
|
|
|
{
|
|
|
|
h->line[c->len++] = '\r';
|
|
|
|
h->line[c->len++] = b;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: line too long\n", __func__);
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
c->lstate = LINE_CR;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
(*sz)--;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
failure:
|
|
|
|
ctx_free(c);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int start_boundary_line(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
const char *const line = h->line;
|
|
|
|
|
|
|
|
if (strcmp(line, c->boundary + strlen("\r\n")))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected boundary %s, got %s\n",
|
|
|
|
__func__, c->boundary, line);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
m->state = MF_HEADER_CR_LINE;
|
|
|
|
m->len = strlen(line) + strlen("\r\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cd_fields(struct http_ctx *const h, struct form *const f,
|
|
|
|
const char *sep)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
|
|
|
while (*++sep == ' ')
|
|
|
|
;
|
|
|
|
|
|
|
|
if (!*sep)
|
|
|
|
break;
|
|
|
|
|
|
|
|
const char *const op = strchr(sep, '=');
|
|
|
|
|
|
|
|
if (!op)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected attr=value\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const value = op + 1, *const end = strchr(value, ';');
|
|
|
|
const size_t vlen = end ? end - value : strlen(value);
|
|
|
|
|
|
|
|
if (*value != '\"' || value[vlen - 1] != '\"')
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected \"-enclosed value, got %.*s\n",
|
|
|
|
__func__, (int)vlen, value);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const evalue = value + 1;
|
|
|
|
const size_t evlen = vlen - 2;
|
|
|
|
|
|
|
|
if (!strncmp(sep, "name", op - sep))
|
|
|
|
{
|
|
|
|
if (!evlen)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-empty name\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (!(f->name = strndup(evalue, evlen)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3): %s\n",
|
|
|
|
__func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!strncmp(sep, "filename", op - sep))
|
|
|
|
{
|
|
|
|
if (!evlen)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-empty filename\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (!(f->filename = strndup(evalue, evlen)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
2023-07-09 04:16:31 +02:00
|
|
|
else if (!strcmp(f->filename, ".")
|
|
|
|
|| !strcmp(f->filename, "..")
|
|
|
|
|| strpbrk(f->filename, "/*"))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid filename %s\n",
|
|
|
|
__func__, f->filename);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
} while ((sep = strchr(sep, ';')));
|
|
|
|
|
|
|
|
if (!f->name)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected name\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_content_disposition(struct http_ctx *const h,
|
|
|
|
const char *const c)
|
|
|
|
{
|
|
|
|
const char *const sep = strchr(c, ';');
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
|
|
|
|
if (!sep)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: no fields found\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (strncmp(c, "form-data", sep - c))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected form-data, got %.*s\n",
|
|
|
|
__func__, (int)(sep - c), c);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-03-04 02:06:19 +01:00
|
|
|
|
|
|
|
const size_t n = m->nforms + 1;
|
|
|
|
struct form *const forms = realloc(m->forms, n * sizeof *m->forms);
|
|
|
|
|
|
|
|
if (!forms)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
struct form *const f = &forms[m->nforms];
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
*f = (const struct form){0};
|
2023-03-04 02:06:19 +01:00
|
|
|
m->nforms = n;
|
|
|
|
m->forms = forms;
|
2023-01-09 01:22:54 +01:00
|
|
|
return cd_fields(h, f, sep);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mf_header_cr_line(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
const char *const line = h->line;
|
|
|
|
|
|
|
|
m->len += strlen(line) + strlen("\r\n");
|
|
|
|
|
|
|
|
if (!*line)
|
|
|
|
{
|
|
|
|
const size_t n = strlen("\r\n") + strlen(c->boundary) + 1;
|
|
|
|
|
|
|
|
if (!m->boundary && !(m->boundary = calloc(1, n)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
m->state = MF_BODY_BOUNDARY_LINE;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *value;
|
|
|
|
size_t n;
|
|
|
|
int ret = get_field_value(line, &n, &value);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2023-07-09 04:14:14 +02:00
|
|
|
else if (!strncasecmp(line, "Content-Disposition", n)
|
2023-01-09 01:22:54 +01:00
|
|
|
&& (ret = set_content_disposition(h, value)))
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int end_boundary_line(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
const char *const line = h->line;
|
|
|
|
|
|
|
|
if (!*line)
|
|
|
|
{
|
|
|
|
h->ctx.u.mf.state = MF_HEADER_CR_LINE;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (!strcmp(line, "--"))
|
|
|
|
{
|
|
|
|
/* Found end boundary. */
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
|
|
|
|
const struct http_payload p =
|
|
|
|
{
|
|
|
|
.cookie =
|
|
|
|
{
|
|
|
|
.field = c->field,
|
|
|
|
.value = c->value
|
|
|
|
},
|
|
|
|
|
|
|
|
.op = c->op,
|
|
|
|
.resource = c->resource,
|
|
|
|
.u.post =
|
|
|
|
{
|
|
|
|
.files = m->files,
|
2023-09-09 00:19:39 +02:00
|
|
|
.pairs = m->pairs,
|
|
|
|
.nfiles = m->nfiles,
|
|
|
|
.npairs = m->npairs
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return send_payload(h, &p);
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: unexpected line after boundary: %s\n",
|
|
|
|
__func__, line);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int process_mf_line(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
static int (*const state[])(struct http_ctx *) =
|
|
|
|
{
|
|
|
|
[MF_START_BOUNDARY] = start_boundary_line,
|
|
|
|
[MF_HEADER_CR_LINE] = mf_header_cr_line,
|
|
|
|
[MF_END_BOUNDARY_CR_LINE] = end_boundary_line
|
|
|
|
};
|
|
|
|
|
2023-11-17 12:53:59 +01:00
|
|
|
h->ctx.payload.read += strlen(h->line) + strlen("\r\n");
|
2023-01-09 01:22:54 +01:00
|
|
|
return state[h->ctx.u.mf.state](h);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *get_tmp(const char *const tmpdir)
|
|
|
|
{
|
|
|
|
struct dynstr d;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
if (dynstr_append(&d, "%s/tmp.XXXXXX", tmpdir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.str;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int generate_mf_file(struct http_ctx *const h)
|
|
|
|
{
|
2023-11-24 00:52:50 +01:00
|
|
|
int ret = -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
struct form *const f = &m->forms[m->nforms - 1];
|
2023-11-24 00:52:50 +01:00
|
|
|
int fd = -1;
|
|
|
|
FILE *fp;
|
|
|
|
char *const tmpname = get_tmp(h->cfg.tmpdir);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
if (!tmpname)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: get_tmp failed\n", __func__);
|
2023-11-24 00:52:50 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
2023-11-24 00:52:50 +01:00
|
|
|
else if ((fd = mkstemp(tmpname)) < 0)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: mkstemp(3): %s\n", __func__, strerror(errno));
|
2023-11-24 00:52:50 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(fp = fdopen(fd, "wb")))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fdopen(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
f->tmpname = tmpname;
|
|
|
|
m->f = fp;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (ret)
|
|
|
|
{
|
|
|
|
if (fd >= 0 && close(fd))
|
|
|
|
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
|
|
|
|
|
|
|
|
free(tmpname);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int read_mf_body_to_mem(struct http_ctx *const h,
|
|
|
|
const char *const buf, const size_t n)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
if (m->written + n > sizeof h->line - 1)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: maximum length exceeded\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
memcpy(h->line, buf, n);
|
|
|
|
m->written += n;
|
|
|
|
m->len += n;
|
|
|
|
c->payload.read += n;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int read_mf_body_to_file(struct http_ctx *const h,
|
|
|
|
const char *const buf, const size_t n)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
if (!m->f && generate_mf_file(h))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: generate_mf_file failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
2024-01-20 01:05:05 +01:00
|
|
|
else if (n && !fwrite(buf, n, 1, m->f))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-24 00:52:50 +01:00
|
|
|
fprintf(stderr, "%s: fwrite(3) failed, feof=%d, ferror=%d\n",
|
|
|
|
__func__, feof(m->f), ferror(m->f));
|
2023-01-09 01:22:54 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
m->written += n;
|
|
|
|
m->len += n;
|
|
|
|
c->payload.read += n;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-11-12 23:31:57 +01:00
|
|
|
static int reset_boundary(struct http_ctx *const h)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
struct form *const f = &m->forms[m->nforms - 1];
|
2024-01-20 01:05:05 +01:00
|
|
|
int (*const read_mf)(struct http_ctx *, const char *, size_t) =
|
2023-11-12 06:16:26 +01:00
|
|
|
f->filename ? read_mf_body_to_file : read_mf_body_to_mem;
|
2023-11-12 23:31:57 +01:00
|
|
|
const size_t len = strlen(m->boundary);
|
2024-01-20 01:05:05 +01:00
|
|
|
const int res = read_mf(h, m->boundary, len);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
if (res)
|
|
|
|
return res;
|
2023-11-12 06:16:26 +01:00
|
|
|
|
2023-11-12 23:31:57 +01:00
|
|
|
memset(m->boundary, '\0', len);
|
|
|
|
m->blen = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
2023-11-12 06:16:26 +01:00
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int dump_body(struct http_ctx *const h, const char *const buf,
|
|
|
|
const size_t n)
|
2023-11-12 23:31:57 +01:00
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
struct form *const f = &m->forms[m->nforms - 1];
|
2024-01-20 01:05:05 +01:00
|
|
|
int (*const read_mf)(struct http_ctx *, const char *, size_t) =
|
2023-11-12 23:31:57 +01:00
|
|
|
f->filename ? read_mf_body_to_file : read_mf_body_to_mem;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
return read_mf(h, buf, n);
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int apply_from_file(struct http_ctx *const h, struct form *const f)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
if (fclose(m->f))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-24 00:52:50 +01:00
|
|
|
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
m->f = NULL;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2024-02-19 23:00:56 +01:00
|
|
|
const struct http_cfg_post *const cfg = &h->cfg.post;
|
2023-03-04 02:06:19 +01:00
|
|
|
const size_t n = m->nfiles + 1;
|
2024-02-19 23:00:56 +01:00
|
|
|
|
|
|
|
if (n > cfg->max_files)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: exceeded maximum number of files (%zu)\n",
|
|
|
|
__func__, cfg->max_files);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
struct http_post_file *const files = realloc(m->files,
|
|
|
|
n * sizeof *m->files);
|
|
|
|
|
|
|
|
if (!files)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
struct http_post_file *const pf = &files[m->nfiles];
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
*pf = (const struct http_post_file)
|
|
|
|
{
|
2023-09-09 00:36:26 +02:00
|
|
|
.name = f->name,
|
2023-01-09 01:22:54 +01:00
|
|
|
.tmpname = f->tmpname,
|
|
|
|
.filename = f->filename
|
|
|
|
};
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
m->files = files;
|
|
|
|
m->nfiles = n;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-09-09 00:19:39 +02:00
|
|
|
static bool name_exists(const struct multiform *const m,
|
|
|
|
const struct form *const f)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < m->npairs; i++)
|
|
|
|
{
|
|
|
|
const struct http_post_pair *const p = &m->pairs[i];
|
|
|
|
|
|
|
|
if (!strcmp(f->name, p->name))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: \"%s\" defined more than once\n",
|
|
|
|
f->name, __func__);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int apply_from_mem(struct http_ctx *const h, struct form *const f)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
|
2023-09-09 00:19:39 +02:00
|
|
|
if (name_exists(m, f))
|
|
|
|
return 1;
|
|
|
|
|
2024-02-19 23:00:56 +01:00
|
|
|
const struct http_cfg_post *const cfg = &h->cfg.post;
|
2023-09-09 00:19:39 +02:00
|
|
|
struct http_post_pair *pairs, *p;
|
|
|
|
const size_t n = m->npairs + 1;
|
|
|
|
|
2024-02-19 23:00:56 +01:00
|
|
|
if (n > cfg->max_pairs)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: exceeded maximum number of pairs (%zu)\n",
|
|
|
|
__func__, cfg->max_pairs);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (!(f->value = strndup(h->line, m->written)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
2023-09-09 00:19:39 +02:00
|
|
|
else if (!(pairs = realloc(m->pairs, n * sizeof *m->pairs)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-09-09 00:19:39 +02:00
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-09-09 00:19:39 +02:00
|
|
|
pairs[m->npairs++] = (const struct http_post_pair)
|
|
|
|
{
|
|
|
|
.name = f->name,
|
|
|
|
.value = f->value
|
|
|
|
};
|
|
|
|
|
|
|
|
m->pairs = pairs;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int read_mf_body_boundary_byte(struct http_ctx *const h, const char b,
|
|
|
|
const size_t len)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
2023-11-12 06:16:26 +01:00
|
|
|
const size_t clen = strlen(c->boundary);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (b == c->boundary[m->blen])
|
|
|
|
{
|
2023-11-12 06:16:26 +01:00
|
|
|
m->boundary[m->blen++] = b;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-12 06:16:26 +01:00
|
|
|
if (m->blen >= clen)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
/* Found intermediate boundary. */
|
|
|
|
struct form *const f = &m->forms[m->nforms - 1];
|
|
|
|
const int ret = f->filename ? apply_from_file(h, f)
|
|
|
|
: apply_from_mem(h, f);
|
|
|
|
|
2023-11-12 06:16:26 +01:00
|
|
|
memset(m->boundary, '\0', clen);
|
2023-01-09 01:22:54 +01:00
|
|
|
m->blen = 0;
|
|
|
|
m->state = MF_END_BOUNDARY_CR_LINE;
|
|
|
|
m->written = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
2023-11-12 23:31:57 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected %hhx, got %hhx\n", __func__,
|
|
|
|
c->boundary[m->blen], b);
|
|
|
|
return 1;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Similar to memmem(3), provided here to avoid the use of GNU extensions. */
|
|
|
|
static const char *http_memmem(const char *const a, const void *const b,
|
|
|
|
const size_t n)
|
|
|
|
{
|
|
|
|
const char *s = a, *st = NULL;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
const char *bc = b;
|
|
|
|
const char c = bc[i];
|
|
|
|
|
|
|
|
if (*s == c)
|
|
|
|
{
|
|
|
|
if (!st)
|
|
|
|
st = &bc[i];
|
|
|
|
|
|
|
|
if (!*++s)
|
|
|
|
return st;
|
|
|
|
}
|
|
|
|
else if (*(s = a) != c)
|
|
|
|
st = NULL;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
st = &bc[i];
|
|
|
|
|
|
|
|
if (!*++s)
|
|
|
|
return st;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-12 06:16:26 +01:00
|
|
|
return st;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int read_mf_body_boundary(struct http_ctx *const h,
|
|
|
|
const char *buf, size_t *const n)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
int res;
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
if (m->blen
|
|
|
|
&& *buf != c->boundary[m->blen]
|
|
|
|
&& (res = reset_boundary(h)))
|
|
|
|
return res;
|
|
|
|
|
|
|
|
const char *const boundary = http_memmem(&c->boundary[m->blen], buf, *n);
|
|
|
|
|
|
|
|
if (!boundary)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-12 23:31:57 +01:00
|
|
|
if ((res = reset_boundary(h))
|
2024-01-20 01:05:05 +01:00
|
|
|
|| (res = dump_body(h, buf, *n)))
|
2023-01-09 01:22:54 +01:00
|
|
|
return res;
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
*n = 0;
|
2023-01-09 01:22:54 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
const size_t prev = boundary - buf;
|
|
|
|
|
|
|
|
if (prev && (res = reset_boundary(h)))
|
|
|
|
return res;
|
|
|
|
else if ((res = dump_body(h, buf, prev)))
|
|
|
|
return res;
|
|
|
|
|
|
|
|
buf += prev;
|
|
|
|
*n -= prev;
|
|
|
|
|
|
|
|
const size_t len = strlen(m->boundary),
|
|
|
|
rem = strlen(c->boundary) - len,
|
|
|
|
r = rem > *n ? *n : rem;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < r; i++)
|
|
|
|
{
|
|
|
|
const char *const b = buf;
|
|
|
|
|
|
|
|
if ((res = read_mf_body_boundary_byte(h, b[i], len)))
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
*n -= r;
|
|
|
|
return 0;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
static int read_multiform(struct http_ctx *const h, bool *const close,
|
2024-01-20 01:05:05 +01:00
|
|
|
const char *const buf, size_t *const sz)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
2023-11-24 00:52:50 +01:00
|
|
|
int res;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
switch (m->state)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-24 00:52:50 +01:00
|
|
|
case MF_START_BOUNDARY:
|
|
|
|
/* Fall through. */
|
|
|
|
case MF_HEADER_CR_LINE:
|
|
|
|
/* Fall through. */
|
|
|
|
case MF_END_BOUNDARY_CR_LINE:
|
2024-01-20 01:05:05 +01:00
|
|
|
if ((res = update_lstate(h, close, process_mf_line, buf, sz)))
|
2023-11-24 00:52:50 +01:00
|
|
|
return res;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
break;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
case MF_BODY_BOUNDARY_LINE:
|
2024-01-20 01:05:05 +01:00
|
|
|
if ((res = read_mf_body_boundary(h, buf, sz)))
|
2023-11-24 00:52:50 +01:00
|
|
|
return res;
|
2024-01-20 01:05:05 +01:00
|
|
|
|
|
|
|
break;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
static int read_body_to_mem(struct http_ctx *const h, bool *const close,
|
2024-01-20 01:05:05 +01:00
|
|
|
const char *const buf, size_t *const sz)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
2023-11-17 12:53:59 +01:00
|
|
|
struct payload *const p = &c->payload;
|
2024-01-20 01:05:05 +01:00
|
|
|
const char b = *buf;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-09-09 02:22:00 +02:00
|
|
|
if (p->read >= sizeof h->line - 1)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: exceeded maximum length\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
h->line[p->read++] = b;
|
2024-01-20 01:05:05 +01:00
|
|
|
(*sz)--;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (p->read >= p->len)
|
|
|
|
{
|
|
|
|
const struct http_payload pl =
|
|
|
|
{
|
|
|
|
.cookie =
|
|
|
|
{
|
|
|
|
.field = c->field,
|
|
|
|
.value = c->value
|
|
|
|
},
|
|
|
|
|
|
|
|
.op = c->op,
|
|
|
|
.resource = c->resource,
|
|
|
|
.u.post =
|
|
|
|
{
|
2023-09-09 02:22:00 +02:00
|
|
|
.data = h->line
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-09-09 02:22:00 +02:00
|
|
|
h->line[p->len] = '\0';
|
2023-01-09 01:22:54 +01:00
|
|
|
return send_payload(h, &pl);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
static int ensure_put_tmp(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct put *const put = &h->ctx.u.put;
|
|
|
|
char *const tmpname = get_tmp(h->cfg.tmpdir);
|
|
|
|
|
|
|
|
if (!tmpname)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: get_tmp failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
put->tmpname = tmpname;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
free(tmpname);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ensure_put_file(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct put *const put = &h->ctx.u.put;
|
|
|
|
int fd = -1;
|
|
|
|
FILE *f = NULL;
|
|
|
|
|
|
|
|
if ((fd = mkstemp(put->tmpname)) < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: mkstemp(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(f = fdopen(fd, "wb")))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fdopen(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
put->f = f;
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (ret)
|
|
|
|
{
|
|
|
|
if (f && fclose(f))
|
|
|
|
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
|
|
|
|
else if (fd >= 0 && close(fd))
|
|
|
|
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int read_put_body_to_file(struct http_ctx *const h,
|
|
|
|
const void *const buf, size_t *const sz)
|
2023-11-17 12:53:59 +01:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct put *const put = &c->u.put;
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
if (!put->tmpname && ensure_put_tmp(h))
|
2023-11-17 12:53:59 +01:00
|
|
|
{
|
2023-11-24 00:52:50 +01:00
|
|
|
fprintf(stderr, "%s: ensure_put_tmp failed\n", __func__);
|
2023-11-17 12:53:59 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2023-11-24 00:52:50 +01:00
|
|
|
else if (!put->f && ensure_put_file(h))
|
2023-11-17 12:53:59 +01:00
|
|
|
{
|
2023-11-24 00:52:50 +01:00
|
|
|
fprintf(stderr, "%s: ensure_put_file failed\n", __func__);
|
2023-11-17 12:53:59 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2024-01-20 01:05:05 +01:00
|
|
|
else if (!fwrite(buf, *sz, 1, put->f))
|
2023-11-17 12:53:59 +01:00
|
|
|
{
|
2023-11-24 00:52:50 +01:00
|
|
|
fprintf(stderr, "%s: fwrite(3) failed, feof=%d ferror=%d\n",
|
|
|
|
__func__, feof(put->f), ferror(put->f));
|
2023-11-17 12:53:59 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
c->payload.read += *sz;
|
|
|
|
*sz = 0;
|
2023-11-17 12:53:59 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
static int read_to_file(struct http_ctx *const h, bool *const close,
|
2024-01-20 01:05:05 +01:00
|
|
|
const char *const buf, size_t *const sz)
|
2023-11-17 12:53:59 +01:00
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct payload *const p = &c->payload;
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
if (read_put_body_to_file(h, buf, sz))
|
2023-11-17 12:53:59 +01:00
|
|
|
return -1;
|
|
|
|
else if (p->read >= p->len)
|
|
|
|
{
|
|
|
|
const struct http_payload pl =
|
|
|
|
{
|
|
|
|
.cookie =
|
|
|
|
{
|
|
|
|
.field = c->field,
|
|
|
|
.value = c->value
|
|
|
|
},
|
|
|
|
|
|
|
|
.op = c->op,
|
|
|
|
.resource = c->resource,
|
|
|
|
.u.put =
|
|
|
|
{
|
|
|
|
.tmpname = c->u.put.tmpname
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return send_payload(h, &pl);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int read_body(struct http_ctx *const h, bool *const close,
|
|
|
|
const char *const buf, size_t *const sz)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-17 12:53:59 +01:00
|
|
|
const struct ctx *const c = &h->ctx;
|
|
|
|
|
|
|
|
switch (c->op)
|
|
|
|
{
|
|
|
|
case HTTP_OP_POST:
|
2024-01-20 01:05:05 +01:00
|
|
|
return c->boundary ? read_multiform(h, close, buf, sz)
|
|
|
|
: read_body_to_mem(h, close, buf, sz);
|
2023-11-17 12:53:59 +01:00
|
|
|
|
|
|
|
case HTTP_OP_PUT:
|
2024-01-20 01:05:05 +01:00
|
|
|
return read_to_file(h, close, buf, sz);
|
2023-11-17 12:53:59 +01:00
|
|
|
|
|
|
|
case HTTP_OP_GET:
|
|
|
|
/* Fall through. */
|
|
|
|
case HTTP_OP_HEAD:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: unexpected op %d\n", __func__, c->op);
|
|
|
|
return -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int process_line(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
static int (*const state[])(struct http_ctx *) =
|
|
|
|
{
|
|
|
|
[START_LINE] = start_line,
|
|
|
|
[HEADER_CR_LINE] = header_cr_line
|
|
|
|
};
|
|
|
|
|
|
|
|
return state[h->ctx.state](h);
|
|
|
|
}
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
static int read_buf(struct http_ctx *const h, bool *const close,
|
|
|
|
const char *const buf, size_t *const sz)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
switch (h->ctx.state)
|
|
|
|
{
|
|
|
|
case START_LINE:
|
|
|
|
/* Fall through. */
|
|
|
|
case HEADER_CR_LINE:
|
2024-01-20 01:05:05 +01:00
|
|
|
return update_lstate(h, close, process_line, buf, sz);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
case BODY_LINE:
|
2024-01-20 01:05:05 +01:00
|
|
|
return read_body(h, close, buf, sz);
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: unexpected state %d\n", __func__, h->ctx.state);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-11-24 00:52:50 +01:00
|
|
|
int http_read(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
char buf[BUFSIZ];
|
|
|
|
const int r = h->cfg.read(buf, sizeof buf, h->cfg.user);
|
|
|
|
|
|
|
|
if (r <= 0)
|
|
|
|
return rw_error(r, close);
|
|
|
|
|
2024-01-20 01:05:05 +01:00
|
|
|
size_t rem = r;
|
|
|
|
|
|
|
|
while (rem)
|
2023-11-24 00:52:50 +01:00
|
|
|
{
|
2024-01-20 01:05:05 +01:00
|
|
|
const int ret = read_buf(h, close, &buf[r - rem], &rem);
|
2023-11-24 00:52:50 +01:00
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-04 04:02:14 +01:00
|
|
|
static int append_expire(struct dynstr *const d)
|
|
|
|
{
|
|
|
|
time_t t = time(NULL);
|
|
|
|
|
|
|
|
if (t == (time_t)-1)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: time(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
t += 365 * 24 * 60 * 60;
|
|
|
|
|
|
|
|
struct tm tm;
|
|
|
|
|
|
|
|
if (!localtime_r(&t, &tm))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: localtime_r(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char s[sizeof "Thu, 01 Jan 1970 00:00:00 GMT"];
|
|
|
|
|
|
|
|
if (!strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S GMT", &tm))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strftime(3) failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-11-12 00:50:06 +01:00
|
|
|
else if (dynstr_append(d, "; Expires=%s", s))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-03-04 04:02:14 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
char *http_cookie_create(const char *const key, const char *const value)
|
|
|
|
{
|
|
|
|
struct dynstr d;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
dynstr_append_or_ret_null(&d, "%s=%s; HttpOnly", key, value);
|
2023-03-04 04:02:14 +01:00
|
|
|
|
|
|
|
if (append_expire(&d))
|
|
|
|
{
|
|
|
|
dynstr_free(&d);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
return d.str;
|
|
|
|
}
|
|
|
|
|
|
|
|
int http_update(struct http_ctx *const h, bool *const write, bool *const close)
|
|
|
|
{
|
|
|
|
*close = false;
|
|
|
|
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
const int ret = w->pending ? http_write(h, close) : http_read(h, close);
|
|
|
|
|
|
|
|
*write = w->pending;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void http_free(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
if (h)
|
|
|
|
{
|
|
|
|
ctx_free(&h->ctx);
|
|
|
|
write_ctx_free(&h->wctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct http_ctx *http_alloc(const struct http_cfg *const cfg)
|
|
|
|
{
|
|
|
|
struct http_ctx *const h = malloc(sizeof *h);
|
|
|
|
|
|
|
|
if (!h)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
*h = (const struct http_ctx)
|
|
|
|
{
|
|
|
|
.cfg = *cfg
|
|
|
|
};
|
|
|
|
|
|
|
|
return h;
|
|
|
|
|
|
|
|
failure:
|
|
|
|
http_free(h);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *http_encode_url(const char *url)
|
|
|
|
{
|
|
|
|
struct dynstr d;
|
|
|
|
char c;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
while ((c = *url++))
|
|
|
|
{
|
|
|
|
/* Unreserved characters must not be percent-encoded. */
|
|
|
|
if ((c >= 'A' && c <= 'Z')
|
|
|
|
|| (c >= 'a' && c <= 'z')
|
|
|
|
|| (c == '-')
|
|
|
|
|| (c == '_')
|
|
|
|
|| (c == '.')
|
|
|
|
|| (c == '/')
|
|
|
|
|| (c == '~'))
|
|
|
|
{
|
|
|
|
if (dynstr_append(&d, "%c", c))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "%%%hhx", c))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.str;
|
|
|
|
|
|
|
|
failure:
|
|
|
|
dynstr_free(&d);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-11-12 00:14:05 +01:00
|
|
|
int http_decode_url(const char *url, const bool spaces, char **out)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-12 00:14:05 +01:00
|
|
|
int ret = -1;
|
|
|
|
char *str = NULL;
|
2023-01-09 01:22:54 +01:00
|
|
|
size_t n = 0;
|
|
|
|
|
|
|
|
while (*url)
|
|
|
|
{
|
2023-11-12 00:14:05 +01:00
|
|
|
char *const r = realloc(str, n + 1);
|
2023-03-04 02:06:19 +01:00
|
|
|
|
|
|
|
if (!r)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3) loop: %s\n",
|
|
|
|
__func__, strerror(errno));
|
2023-11-12 00:14:05 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
2023-03-04 02:06:19 +01:00
|
|
|
|
2023-11-12 00:14:05 +01:00
|
|
|
str = r;
|
2023-03-04 02:06:19 +01:00
|
|
|
|
2023-04-30 23:43:10 +02:00
|
|
|
if (spaces && *url == '+')
|
|
|
|
{
|
2023-11-12 00:14:05 +01:00
|
|
|
str[n++] = ' ';
|
2023-04-30 23:43:10 +02:00
|
|
|
url++;
|
|
|
|
}
|
|
|
|
else if (*url != '%')
|
2023-11-12 00:14:05 +01:00
|
|
|
str[n++] = *url++;
|
2023-01-09 01:22:54 +01:00
|
|
|
else if (*(url + 1) && *(url + 2))
|
|
|
|
{
|
|
|
|
const char buf[sizeof "00"] = {*(url + 1), *(url + 2)};
|
2023-04-30 22:12:57 +02:00
|
|
|
char *endptr;
|
|
|
|
const unsigned long res = strtoul(buf, &endptr, 16);
|
|
|
|
|
|
|
|
if (*endptr)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid number %s\n", __func__, buf);
|
2023-11-12 00:14:05 +01:00
|
|
|
ret = 1;
|
|
|
|
goto end;
|
2023-04-30 22:12:57 +02:00
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
url += 3;
|
2023-11-12 00:14:05 +01:00
|
|
|
str[n++] = res;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unterminated %%\n", __func__);
|
2023-11-12 00:14:05 +01:00
|
|
|
ret = 1;
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-12 00:14:05 +01:00
|
|
|
char *const r = realloc(str, n + 1);
|
2023-03-04 02:06:19 +01:00
|
|
|
|
|
|
|
if (!r)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3) end: %s\n", __func__, strerror(errno));
|
2023-11-12 00:14:05 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-11-12 00:14:05 +01:00
|
|
|
str = r;
|
|
|
|
str[n] = '\0';
|
|
|
|
*out = str;
|
|
|
|
ret = 0;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-12 00:14:05 +01:00
|
|
|
end:
|
|
|
|
if (ret)
|
|
|
|
free(str);
|
|
|
|
|
|
|
|
return ret;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|