mirror of
https://github.com/servalproject/serval-dna.git
synced 2025-01-31 00:23:50 +00:00
HTTP server: parse query parameters
This commit is contained in:
parent
0a40d9849c
commit
ce7a6ba988
135
http_server.c
135
http_server.c
@ -114,8 +114,9 @@ void http_request_init(struct http_request *r, int sockfd)
|
|||||||
r->alarm.poll.events = POLLIN;
|
r->alarm.poll.events = POLLIN;
|
||||||
r->phase = RECEIVE;
|
r->phase = RECEIVE;
|
||||||
r->reserved = r->buffer;
|
r->reserved = r->buffer;
|
||||||
// Put aside a few bytes for reserving strings, so that the path can be reserved ok.
|
// Put aside a few bytes for reserving strings, so that the path and query parameters can be
|
||||||
r->received = r->end = r->parsed = r->cursor = r->buffer + 32;
|
// reserved ok.
|
||||||
|
r->received = r->end = r->parsed = r->cursor = r->buffer + sizeof(void*) * (1 + NELS(r->query_parameters));
|
||||||
r->parser = http_request_parse_verb;
|
r->parser = http_request_parse_verb;
|
||||||
watch(&r->alarm);
|
watch(&r->alarm);
|
||||||
http_request_set_idle_timeout(r);
|
http_request_set_idle_timeout(r);
|
||||||
@ -215,11 +216,9 @@ static void *read_pointer(const unsigned char *mem)
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate space from the start of the request buffer to hold the given substring plus a
|
/* Allocate space from the start of the request buffer to hold a given number of bytes plus a
|
||||||
* terminating NUL. Enough bytes must have already been marked as parsed in order to make room,
|
* terminating NUL. Enough bytes must have already been marked as parsed in order to make room,
|
||||||
* otherwise the reservation fails and returns 0. If successful, copies the substring plus a
|
* otherwise the reservation fails and returns 0. If successful, returns 1.
|
||||||
* terminating NUL into the reserved space, places a pointer to the reserved area into '*resp', and
|
|
||||||
* returns 1.
|
|
||||||
*
|
*
|
||||||
* Keeps a copy to the pointer 'resp', so that when the reserved area is released, all pointers into
|
* Keeps a copy to the pointer 'resp', so that when the reserved area is released, all pointers into
|
||||||
* it can be set to NULL automatically. This provides some safety: if the pointer is accidentally
|
* it can be set to NULL automatically. This provides some safety: if the pointer is accidentally
|
||||||
@ -229,14 +228,11 @@ static void *read_pointer(const unsigned char *mem)
|
|||||||
*
|
*
|
||||||
* @author Andrew Bettison <andrew@servalproject.com>
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
*/
|
*/
|
||||||
static int _reserve(struct http_request *r, const char **resp, struct substring str)
|
static int _reserve(struct http_request *r, const char **resp, const char *src, size_t len, void (*mover)(char *, const char *, size_t))
|
||||||
{
|
{
|
||||||
// Reserved string pointer must lie within this http_request struct.
|
// Reserved string pointer must lie within this http_request struct.
|
||||||
assert((char*)resp >= (char*)r);
|
assert((char*)resp >= (char*)r);
|
||||||
assert((char*)resp < (char*)(r + 1));
|
assert((char*)resp < (char*)(r + 1));
|
||||||
size_t len = str.end - str.start;
|
|
||||||
// Substring must contain no NUL chars.
|
|
||||||
assert(strnchr(str.start, len, '\0') == NULL);
|
|
||||||
char *reslim = r->buffer + sizeof r->buffer - 1024; // always leave this much unreserved space
|
char *reslim = r->buffer + sizeof r->buffer - 1024; // always leave this much unreserved space
|
||||||
assert(r->reserved <= reslim);
|
assert(r->reserved <= reslim);
|
||||||
size_t siz = sizeof(char**) + len + 1;
|
size_t siz = sizeof(char**) + len + 1;
|
||||||
@ -245,17 +241,15 @@ static int _reserve(struct http_request *r, const char **resp, struct substring
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (r->reserved + siz > r->parsed) {
|
if (r->reserved + siz > r->parsed) {
|
||||||
WARNF("Error during HTTP parsing, unparsed content %s would be overwritten by reserving %s",
|
WARNF("Error during HTTP parsing, unparsed content %s would be overwritten by reserving %zu bytes",
|
||||||
alloca_toprint(30, r->parsed, r->end - r->parsed),
|
alloca_toprint(30, r->parsed, r->end - r->parsed), len + 1
|
||||||
alloca_substring_toprint(str)
|
|
||||||
);
|
);
|
||||||
r->response.result_code = 500;
|
r->response.result_code = 500;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const char ***respp = (const char ***) r->reserved;
|
const char ***respp = (const char ***) r->reserved;
|
||||||
char *restr = (char *)(respp + 1);
|
char *restr = (char *)(respp + 1);
|
||||||
if (restr != str.start)
|
mover(restr, src, len);
|
||||||
memmove(restr, str.start, len);
|
|
||||||
restr[len] = '\0';
|
restr[len] = '\0';
|
||||||
r->reserved += siz;
|
r->reserved += siz;
|
||||||
assert(r->reserved == &restr[len+1]);
|
assert(r->reserved == &restr[len+1]);
|
||||||
@ -269,6 +263,25 @@ static int _reserve(struct http_request *r, const char **resp, struct substring
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _mover_mem(char *dst, const char *src, size_t len)
|
||||||
|
{
|
||||||
|
if (dst != src)
|
||||||
|
memmove(dst, src, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate space from the start of the request buffer to hold the given substring plus a
|
||||||
|
* terminating NUL.
|
||||||
|
*
|
||||||
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
|
*/
|
||||||
|
static int _reserve_substring(struct http_request *r, const char **resp, struct substring str)
|
||||||
|
{
|
||||||
|
size_t len = str.end - str.start;
|
||||||
|
// Substring must contain no NUL chars.
|
||||||
|
assert(strnchr(str.start, len, '\0') == NULL);
|
||||||
|
return _reserve(r, resp, str.start, len, _mover_mem);
|
||||||
|
}
|
||||||
|
|
||||||
/* The same as _reserve(), but takes a NUL-terminated string as a source argument instead of a
|
/* The same as _reserve(), but takes a NUL-terminated string as a source argument instead of a
|
||||||
* substring.
|
* substring.
|
||||||
*
|
*
|
||||||
@ -276,8 +289,26 @@ static int _reserve(struct http_request *r, const char **resp, struct substring
|
|||||||
*/
|
*/
|
||||||
static int _reserve_str(struct http_request *r, const char **resp, const char *str)
|
static int _reserve_str(struct http_request *r, const char **resp, const char *str)
|
||||||
{
|
{
|
||||||
struct substring sub = { .start = str, .end = str + strlen(str) };
|
return _reserve(r, resp, str, strlen(str), _mover_mem);
|
||||||
return _reserve(r, resp, sub);
|
}
|
||||||
|
|
||||||
|
/* The same as _reserve(), but decodes the source bytes using www-form-urlencoding.
|
||||||
|
*
|
||||||
|
* @author Andrew Bettison <andrew@servalproject.com>
|
||||||
|
*/
|
||||||
|
static void _mover_www_form_uri_decode(char *, const char *, size_t);
|
||||||
|
static int _reserve_www_form_uriencoded(struct http_request *r, const char **resp, struct substring str)
|
||||||
|
{
|
||||||
|
assert(str.end > str.start);
|
||||||
|
const char *after = NULL;
|
||||||
|
size_t len = www_form_uri_decode(NULL, -1, (char *)str.start, str.end - str.start, &after);
|
||||||
|
assert(len <= (size_t)(str.end - str.start)); // decoded must not be longer than encoded
|
||||||
|
assert(after == str.end);
|
||||||
|
return _reserve(r, resp, str.start, len, _mover_www_form_uri_decode);
|
||||||
|
}
|
||||||
|
static void _mover_www_form_uri_decode(char *dst, const char *src, size_t len)
|
||||||
|
{
|
||||||
|
www_form_uri_decode(dst, len, src, -1, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Release all the strings reserved by _reserve(), returning the space to the request buffer, and
|
/* Release all the strings reserved by _reserve(), returning the space to the request buffer, and
|
||||||
@ -457,17 +488,17 @@ static inline int _skip_space(struct http_request *r)
|
|||||||
return r->cursor > start;
|
return r->cursor > start;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t _skip_word_printable(struct http_request *r, struct substring *str)
|
static size_t _skip_word_printable(struct http_request *r, struct substring *str, char until)
|
||||||
{
|
{
|
||||||
if (_run_out(r) || isspace(*r->cursor) || !isprint(*r->cursor))
|
if (_run_out(r) || isspace(*r->cursor) || !isprint(*r->cursor) || *r->cursor == until)
|
||||||
return 0;
|
return 0;
|
||||||
const char *start = r->cursor;
|
const char *start = r->cursor;
|
||||||
for (++r->cursor; !_run_out(r) && !isspace(*r->cursor) && isprint(*r->cursor); ++r->cursor)
|
for (++r->cursor; !_run_out(r) && !isspace(*r->cursor) && isprint(*r->cursor) && *r->cursor != until; ++r->cursor)
|
||||||
;
|
;
|
||||||
if (_run_out(r))
|
if (_run_out(r))
|
||||||
return 0;
|
return 0;
|
||||||
assert(r->cursor > start);
|
assert(r->cursor > start);
|
||||||
assert(isspace(*r->cursor));
|
assert(isspace(*r->cursor) || *r->cursor == until);
|
||||||
if (str) {
|
if (str) {
|
||||||
str->start = start;
|
str->start = start;
|
||||||
str->end = r->cursor;
|
str->end = r->cursor;
|
||||||
@ -782,19 +813,71 @@ static int http_request_parse_path(struct http_request *r)
|
|||||||
// Parse path: word immediately following verb, delimited by spaces.
|
// Parse path: word immediately following verb, delimited by spaces.
|
||||||
assert(r->path == NULL);
|
assert(r->path == NULL);
|
||||||
struct substring path;
|
struct substring path;
|
||||||
if (!(_skip_word_printable(r, &path) && _skip_literal(r, " "))) {
|
struct {
|
||||||
|
struct substring name;
|
||||||
|
struct substring value;
|
||||||
|
} params[NELS(r->query_parameters)];
|
||||||
|
unsigned count = 0;
|
||||||
|
if (_skip_word_printable(r, &path, '?')) {
|
||||||
|
struct substring param;
|
||||||
|
while ( count < NELS(params)
|
||||||
|
&& (_skip_literal(r, "?") || _skip_literal(r, "&"))
|
||||||
|
&& _skip_word_printable(r, ¶m, '&')
|
||||||
|
) {
|
||||||
|
const char *eq = strnchr(param.start, param.end - param.start, '=');
|
||||||
|
params[count].name.start = param.start;
|
||||||
|
if (eq) {
|
||||||
|
params[count].name.end = eq;
|
||||||
|
params[count].value.start = eq + 1;
|
||||||
|
params[count].value.end = param.end;
|
||||||
|
} else {
|
||||||
|
params[count].name.end = param.end;
|
||||||
|
params[count].value.start = NULL;
|
||||||
|
params[count].value.end = NULL;
|
||||||
|
}
|
||||||
|
IDEBUGF(r->debug, "Query parameter: %s%s%s",
|
||||||
|
alloca_substring_toprint(params[count].name),
|
||||||
|
params[count].value.start ? "=" : "",
|
||||||
|
params[count].value.start ? alloca_substring_toprint(params[count].value) : ""
|
||||||
|
);
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!_skip_literal(r, " ")) {
|
||||||
if (_run_out(r))
|
if (_run_out(r))
|
||||||
return 100; // read more and try again
|
return 100; // read more and try again
|
||||||
IDEBUGF(r->debug, "Malformed HTTP %s request at path: %s", r->verb, alloca_toprint(20, r->parsed, r->end - r->parsed));
|
if (count == NELS(params))
|
||||||
|
IDEBUGF(r->debug, "Unsupported HTTP %s request, too many query parameters: %s", r->verb, alloca_toprint(20, r->parsed, r->end - r->parsed));
|
||||||
|
else
|
||||||
|
IDEBUGF(r->debug, "Malformed HTTP %s request at path: %s", r->verb, alloca_toprint(20, r->parsed, r->end - r->parsed));
|
||||||
return 400;
|
return 400;
|
||||||
}
|
}
|
||||||
_commit(r);
|
_commit(r);
|
||||||
if (!_reserve(r, &r->path, path))
|
if (!_reserve_www_form_uriencoded(r, &r->path, path))
|
||||||
return 0; // error
|
return 0; // error
|
||||||
|
unsigned i;
|
||||||
|
for (i = 0; i != count; ++i) {
|
||||||
|
if (!_reserve_www_form_uriencoded(r, &r->query_parameters[i].name, params[i].name))
|
||||||
|
return 0; // error
|
||||||
|
if (params[i].value.start && !_reserve_www_form_uriencoded(r, &r->query_parameters[i].value, params[i].value))
|
||||||
|
return 0; // error
|
||||||
|
}
|
||||||
r->parser = http_request_parse_http_version;
|
r->parser = http_request_parse_http_version;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char HTTP_REQUEST_PARAM_NOVALUE[] = "";
|
||||||
|
|
||||||
|
const char *http_request_get_query_param(struct http_request *r, const char *name)
|
||||||
|
{
|
||||||
|
unsigned i;
|
||||||
|
for (i = 0; i != NELS(r->query_parameters) && r->query_parameters[i].name; ++i) {
|
||||||
|
if (strcmp(r->query_parameters[i].name, name) == 0)
|
||||||
|
return r->query_parameters[i].value ? r->query_parameters[i].value : HTTP_REQUEST_PARAM_NOVALUE;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* If parsing completes, then sets r->parser to the next parsing function and returns 0. If parsing
|
/* If parsing completes, then sets r->parser to the next parsing function and returns 0. If parsing
|
||||||
* cannot complete due to running out of data, returns 100 without changing r->parser, so this
|
* cannot complete due to running out of data, returns 100 without changing r->parser, so this
|
||||||
* function will be called again once more data has been read. Returns a 4nn or 5nn HTTP result
|
* function will be called again once more data has been read. Returns a 4nn or 5nn HTTP result
|
||||||
@ -986,12 +1069,12 @@ static int http_request_parse_header(struct http_request *r)
|
|||||||
}
|
}
|
||||||
_skip_optional_space(r);
|
_skip_optional_space(r);
|
||||||
struct substring origin;
|
struct substring origin;
|
||||||
if (_skip_word_printable(r, &origin)
|
if (_skip_word_printable(r, &origin, ' ')
|
||||||
&& _skip_optional_space(r)
|
&& _skip_optional_space(r)
|
||||||
&& r->cursor == eol) {
|
&& r->cursor == eol) {
|
||||||
r->cursor = nextline;
|
r->cursor = nextline;
|
||||||
_commit(r);
|
_commit(r);
|
||||||
_reserve(r, &r->request_header.origin, origin);
|
_reserve_substring(r, &r->request_header.origin, origin);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
goto malformed;
|
goto malformed;
|
||||||
|
@ -196,6 +196,11 @@ struct http_request {
|
|||||||
// The parsed HTTP request is accumulated into the following fields.
|
// The parsed HTTP request is accumulated into the following fields.
|
||||||
const char *verb; // points to nul terminated static string, "GET", "PUT", etc.
|
const char *verb; // points to nul terminated static string, "GET", "PUT", etc.
|
||||||
const char *path; // points into buffer; nul terminated
|
const char *path; // points into buffer; nul terminated
|
||||||
|
struct query_parameter {
|
||||||
|
const char *name; // points into buffer; nul terminated
|
||||||
|
const char *value; // points into buffer; nul terminated
|
||||||
|
}
|
||||||
|
query_parameters[10]; // can make this as big as needed, but not dynamic
|
||||||
uint8_t version_major; // m from from HTTP/m.n
|
uint8_t version_major; // m from from HTTP/m.n
|
||||||
uint8_t version_minor; // n from HTTP/m.n
|
uint8_t version_minor; // n from HTTP/m.n
|
||||||
struct http_request_headers request_header;
|
struct http_request_headers request_header;
|
||||||
@ -237,4 +242,11 @@ struct http_request {
|
|||||||
char buffer[8 * 1024];
|
char buffer[8 * 1024];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Return the nul-terminated string value of a given query parameter: NULL if
|
||||||
|
* no such parameter was supplied; HTTP_REQUEST_PARAM_NOVALUE if the parameter
|
||||||
|
* was supplied without an '=value' part.
|
||||||
|
*/
|
||||||
|
const char *http_request_get_query_param(struct http_request *r, const char *name);
|
||||||
|
extern const char HTTP_REQUEST_PARAM_NOVALUE[];
|
||||||
|
|
||||||
#endif // __SERVAL_DNA__HTTP_SERVER_H
|
#endif // __SERVAL_DNA__HTTP_SERVER_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user