2023-03-24 02:47:11 +01:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
#include "http.h"
|
|
|
|
#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;
|
|
|
|
|
|
|
|
struct post
|
|
|
|
{
|
|
|
|
char *path;
|
|
|
|
unsigned long long len, read;
|
|
|
|
} post;
|
|
|
|
|
|
|
|
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;
|
|
|
|
const char *dir;
|
|
|
|
size_t blen, nforms, nfiles;
|
|
|
|
int fd;
|
|
|
|
struct http_post_file *files;
|
|
|
|
|
|
|
|
struct form
|
|
|
|
{
|
|
|
|
char *name, *filename, *tmpname, *value;
|
|
|
|
} *forms;
|
|
|
|
} mf;
|
|
|
|
} 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;
|
|
|
|
size_t n_args;
|
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;
|
|
|
|
} 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-04-30 23:43:10 +02:00
|
|
|
char *enckey = NULL, *encvalue = 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. */
|
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
|
|
|
a = (const struct http_arg)
|
|
|
|
{
|
2023-04-30 23:43:10 +02:00
|
|
|
.key = http_decode_url(enckey, true),
|
|
|
|
.value = http_decode_url(encvalue, true)
|
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
|
|
|
if (!a.key)
|
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
|
|
|
fprintf(stderr, "%s: http_decode_url key failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!a.value)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_decode_url value 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;
|
|
|
|
}
|
|
|
|
else if (!(args = realloc(c->args, (c->n_args + 1) * sizeof *args)))
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
arg_free(&a);
|
|
|
|
|
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;
|
|
|
|
const char *const arg_start = strchr(res, '?'),
|
|
|
|
*const ad_arg = strchr(res, '&');
|
|
|
|
|
|
|
|
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-04-30 23:43:10 +02:00
|
|
|
else if (!(resource = http_decode_url(trimmed_encres, false)))
|
|
|
|
{
|
|
|
|
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-01-09 01:22:54 +01:00
|
|
|
static int start_line(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
const char *const line = (const char *)h->line;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
if (!strncmp(line, "GET", n))
|
|
|
|
c->op = HTTP_OP_GET;
|
|
|
|
else if (!strncmp(line, "POST", n))
|
|
|
|
c->op = HTTP_OP_POST;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
free(m->boundary);
|
|
|
|
|
2023-03-08 18:02:36 +01:00
|
|
|
if (m->fd >= 0 && close(m->fd))
|
|
|
|
fprintf(stderr, "%s: close(2) m->fd: %s\n",
|
|
|
|
__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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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]);
|
|
|
|
|
|
|
|
free(c->args);
|
2023-01-09 01:22:54 +01:00
|
|
|
*c = (const struct ctx){0};
|
|
|
|
}
|
|
|
|
|
|
|
|
static int prepare_headers(struct http_ctx *const h)
|
|
|
|
{
|
|
|
|
struct write_ctx *const w = &h->wctx;
|
|
|
|
struct dynstr *const d = &w->d;
|
|
|
|
|
|
|
|
dynstr_init(d);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < w->r.n_headers; i++)
|
|
|
|
{
|
|
|
|
const struct http_header *const hdr = &w->r.headers[i];
|
|
|
|
|
|
|
|
dynstr_append_or_ret_nonzero(d, "%s: %s\r\n", hdr->header, hdr->value);
|
|
|
|
free(hdr->header);
|
|
|
|
free(hdr->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(w->r.headers);
|
|
|
|
dynstr_append_or_ret_nonzero(d, "\r\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
*w = (const struct write_ctx){0};
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (w->r.n)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
char buf[1024];
|
|
|
|
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-04-28 00:30:44 +02:00
|
|
|
dynstr_append_or_ret_nonzero(&w->d, HTTP_VERSION " %d %s\r\n",
|
|
|
|
c->code, c->descr);
|
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;
|
|
|
|
h->ctx.post.len = strtoull(len, &end, 10);
|
|
|
|
|
|
|
|
if (errno || *end != '\0')
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid length %s: %s\n",
|
|
|
|
__func__, len, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
c->u.mf = (const struct multiform){.fd = -1};
|
|
|
|
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,
|
|
|
|
.n_args = c->n_args
|
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
|
|
|
|
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 int payload_get(struct http_ctx *const h, const char *const line)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
|
|
|
ctx_free(c);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return start_response(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int payload_post(struct http_ctx *const h, const char *const line)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
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
|
|
|
const struct http_payload pl = ctx_to_payload(c);
|
2023-01-09 01:22:54 +01:00
|
|
|
const int ret = h->cfg.payload(&pl, &h->wctx.r, h->cfg.user);
|
|
|
|
|
|
|
|
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;
|
|
|
|
const struct http_payload p =
|
|
|
|
{
|
|
|
|
.u.post.expect_continue = true,
|
|
|
|
.cookie =
|
|
|
|
{
|
|
|
|
.field = c->field,
|
|
|
|
.value = c->value
|
|
|
|
},
|
|
|
|
|
|
|
|
.op = c->op,
|
|
|
|
.resource = c->resource
|
|
|
|
};
|
|
|
|
|
|
|
|
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return start_response(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
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-03-20 03:32:00 +01:00
|
|
|
return h->cfg.length(c->post.len, &cookie, &h->wctx.r, h->cfg.user);
|
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:
|
|
|
|
return payload_get(h, line);
|
|
|
|
|
|
|
|
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-01-09 01:22:54 +01:00
|
|
|
if (!c->post.len)
|
|
|
|
return payload_post(h, line);
|
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
|
|
|
{
|
|
|
|
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-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,
|
|
|
|
int (*const f)(struct http_ctx *), const char b)
|
|
|
|
{
|
|
|
|
int ret = 1;
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
else if (!strncmp(line, "Content-Disposition", n)
|
|
|
|
&& (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 =
|
|
|
|
{
|
|
|
|
.dir = m->dir,
|
|
|
|
.files = m->files,
|
|
|
|
.n = m->nfiles
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
h->ctx.post.read += strlen(h->line) + strlen("\r\n");
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
struct form *const f = &m->forms[m->nforms - 1];
|
|
|
|
|
|
|
|
if (!(f->tmpname = get_tmp(h->cfg.tmpdir)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: get_tmp failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if ((m->fd = mkstemp(f->tmpname)) < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: mkstemp(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_mf_body_to_mem(struct http_ctx *const h, const void *const buf,
|
|
|
|
const size_t n)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
|
|
|
|
if (m->written + n > sizeof h->line)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: maximum length exceeded\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(&h->line[m->written], buf, n);
|
|
|
|
m->written += n;
|
|
|
|
m->len += n;
|
|
|
|
c->post.read += n;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_mf_body_to_file(struct http_ctx *const h, const void *const buf,
|
|
|
|
const size_t n)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
ssize_t res;
|
|
|
|
|
|
|
|
if (m->fd < 0 && generate_mf_file(h))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: generate_mf_file failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if ((res = pwrite(m->fd, buf, n, m->written)) < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: pwrite(2): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
m->written += res;
|
|
|
|
m->len += res;
|
|
|
|
c->post.read += res;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int reset_boundary(struct http_ctx *const h, const void *const buf,
|
|
|
|
const size_t n)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
struct form *const f = &m->forms[m->nforms - 1];
|
|
|
|
const size_t len = strlen(m->boundary);
|
|
|
|
int (*const read_mf)(struct http_ctx *, const void *, size_t) =
|
|
|
|
f->filename ? read_mf_body_to_file : read_mf_body_to_mem;
|
|
|
|
const int res = read_mf(h, m->boundary, len);
|
|
|
|
|
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
|
|
|
memset(m->boundary, '\0', len);
|
|
|
|
m->blen = 0;
|
|
|
|
return read_mf(h, buf, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int apply_from_file(struct http_ctx *const h, struct form *const f)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
|
|
|
|
if (close(m->fd))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
m->fd = -1;
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
const size_t n = m->nfiles + 1;
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
.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;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int apply_from_mem(struct http_ctx *const h, struct form *const f)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
|
|
|
|
if (!(f->value = strndup(h->line, m->written)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (!strcmp(f->name, "dir"))
|
|
|
|
{
|
|
|
|
if (m->dir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: \"dir\" defined more than once\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
m->dir = f->value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_mf_body_boundary_byte(struct http_ctx *const h, const char b,
|
|
|
|
const size_t len)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
|
|
|
|
if (b == c->boundary[m->blen])
|
|
|
|
{
|
|
|
|
m->boundary[len] = b;
|
|
|
|
|
|
|
|
if (++m->blen >= strlen(c->boundary))
|
|
|
|
{
|
|
|
|
/* 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);
|
|
|
|
|
|
|
|
memset(m->boundary, '\0', len + 1);
|
|
|
|
m->blen = 0;
|
|
|
|
m->state = MF_END_BOUNDARY_CR_LINE;
|
|
|
|
m->written = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 size_t len = strlen(a);
|
|
|
|
|
|
|
|
if (len > n)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_mf_body_boundary(struct http_ctx *const h,
|
|
|
|
const char **const buf, size_t *const n)
|
|
|
|
{
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct multiform *const m = &c->u.mf;
|
|
|
|
const char *const boundary = http_memmem(&c->boundary[m->blen], *buf, *n);
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (!boundary)
|
|
|
|
{
|
|
|
|
if ((res = reset_boundary(h, *buf, *n)))
|
|
|
|
return res;
|
|
|
|
|
|
|
|
*n = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t prev = boundary - *buf;
|
|
|
|
|
|
|
|
if ((res = reset_boundary(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;
|
|
|
|
}
|
|
|
|
|
|
|
|
*buf += r;
|
|
|
|
*n -= r;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_multiform_n(struct http_ctx *const h, bool *const close,
|
|
|
|
const char *buf, size_t n)
|
|
|
|
{
|
|
|
|
struct multiform *const m = &h->ctx.u.mf;
|
|
|
|
|
|
|
|
while (n)
|
|
|
|
{
|
|
|
|
int res;
|
|
|
|
|
|
|
|
switch (m->state)
|
|
|
|
{
|
|
|
|
case MF_START_BOUNDARY:
|
|
|
|
/* Fall through. */
|
|
|
|
case MF_HEADER_CR_LINE:
|
|
|
|
/* Fall through. */
|
|
|
|
case MF_END_BOUNDARY_CR_LINE:
|
|
|
|
{
|
|
|
|
if ((res = update_lstate(h, close, process_mf_line, *buf)))
|
|
|
|
return res;
|
|
|
|
|
|
|
|
buf++;
|
|
|
|
n--;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MF_BODY_BOUNDARY_LINE:
|
|
|
|
if ((res = read_mf_body_boundary(h, &buf, &n)))
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_multiform(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
/* Note: the larger the buffer below, the less CPU load. */
|
|
|
|
char buf[sizeof h->line];
|
|
|
|
struct post *const p = &h->ctx.post;
|
|
|
|
const unsigned long long left = p->len - p->read;
|
|
|
|
const size_t rem = left > sizeof buf ? sizeof buf : left;
|
|
|
|
const int r = h->cfg.read(buf, rem, h->cfg.user);
|
|
|
|
|
|
|
|
if (r <= 0)
|
|
|
|
return rw_error(r, close);
|
|
|
|
|
|
|
|
return read_multiform_n(h, close, buf, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_body_to_mem(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
char b;
|
|
|
|
const int r = h->cfg.read(&b, sizeof b, h->cfg.user);
|
|
|
|
|
|
|
|
if (r <= 0)
|
|
|
|
return rw_error(r, close);
|
|
|
|
|
|
|
|
struct ctx *const c = &h->ctx;
|
|
|
|
struct post *const p = &c->post;
|
|
|
|
|
|
|
|
if (p->read >= sizeof h->line)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: exceeded maximum length\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
h->line[p->read++] = b;
|
|
|
|
|
|
|
|
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 =
|
|
|
|
{
|
|
|
|
.data = h->line,
|
|
|
|
.n = p->len
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return send_payload(h, &pl);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int read_body(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
return h->ctx.boundary ? read_multiform(h, close)
|
|
|
|
: read_body_to_mem(h, close);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int http_read(struct http_ctx *const h, bool *const close)
|
|
|
|
{
|
|
|
|
switch (h->ctx.state)
|
|
|
|
{
|
|
|
|
case START_LINE:
|
|
|
|
/* Fall through. */
|
|
|
|
case HEADER_CR_LINE:
|
|
|
|
{
|
|
|
|
char b;
|
|
|
|
const int r = h->cfg.read(&b, sizeof b, h->cfg.user);
|
|
|
|
|
|
|
|
if (r <= 0)
|
|
|
|
return rw_error(r, close);
|
|
|
|
|
|
|
|
return update_lstate(h, close, process_line, b);
|
|
|
|
}
|
|
|
|
|
|
|
|
case BODY_LINE:
|
|
|
|
return read_body(h, close);
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: unexpected state %d\n", __func__, h->ctx.state);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
dynstr_append_or_ret_nonzero(d, "; Expires=%s", s);
|
|
|
|
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-04-30 23:43:10 +02:00
|
|
|
char *http_decode_url(const char *url, const bool spaces)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
char *ret = NULL;
|
|
|
|
size_t n = 0;
|
|
|
|
|
|
|
|
while (*url)
|
|
|
|
{
|
2023-03-04 02:06:19 +01:00
|
|
|
char *const r = realloc(ret, n + 1);
|
|
|
|
|
|
|
|
if (!r)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3) loop: %s\n",
|
|
|
|
__func__, strerror(errno));
|
|
|
|
goto failure;
|
|
|
|
}
|
2023-03-04 02:06:19 +01:00
|
|
|
|
|
|
|
ret = r;
|
|
|
|
|
2023-04-30 23:43:10 +02:00
|
|
|
if (spaces && *url == '+')
|
|
|
|
{
|
|
|
|
ret[n++] = ' ';
|
|
|
|
url++;
|
|
|
|
}
|
|
|
|
else if (*url != '%')
|
2023-01-09 01:22:54 +01:00
|
|
|
ret[n++] = *url++;
|
|
|
|
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);
|
|
|
|
goto failure;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
url += 3;
|
2023-04-30 22:12:57 +02:00
|
|
|
ret[n++] = res;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unterminated %%\n", __func__);
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
char *const r = realloc(ret, n + 1);
|
|
|
|
|
|
|
|
if (!r)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3) end: %s\n", __func__, strerror(errno));
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
2023-03-04 02:06:19 +01:00
|
|
|
ret = r;
|
2023-01-09 01:22:54 +01:00
|
|
|
ret[n] = '\0';
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
failure:
|
|
|
|
free(ret);
|
|
|
|
return NULL;
|
|
|
|
}
|