kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

commit 25e8f93331225be0c73e6b6d0d141592d9da65b6
parent 8566c32da814effb344349f65b4b6b9b9b00bb49
Author: Joris Vink <joris@coders.se>
Date:   Tue, 10 Sep 2013 11:02:59 +0200

Add support for multipart forms.

New API functions (docs need to be updated):
	- http_file_lookup()
	- http_file_add()
	- http_argument_add()
	- kore_strip_chars()
	- kore_mem_find()

- Add an example under the example module on how files can be read.

Diffstat:
includes/http.h | 32++++++++++++++++++++++++++------
includes/kore.h | 2++
modules/example/media/upload.html | 2++
modules/example/module.conf | 8+++++---
modules/example/src/example.c | 22++++++++++++++++++----
src/http.c | 258+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
src/utils.c | 40++++++++++++++++++++++++++++++++++++++++
7 files changed, 326 insertions(+), 38 deletions(-)

diff --git a/includes/http.h b/includes/http.h @@ -37,6 +37,16 @@ struct http_arg { TAILQ_ENTRY(http_arg) list; }; +struct http_file { + char *name; + char *filename; + + u_int8_t *data; + u_int32_t len; + + TAILQ_ENTRY(http_file) list; +}; + #define HTTP_METHOD_GET 0 #define HTTP_METHOD_POST 1 @@ -56,12 +66,14 @@ struct http_request { struct spdy_stream *stream; struct kore_buf *post_data; void *hdlr_extra; - - TAILQ_HEAD(, http_header) req_headers; - TAILQ_HEAD(, http_header) resp_headers; - TAILQ_HEAD(, http_arg) arguments; - TAILQ_ENTRY(http_request) list; - TAILQ_ENTRY(http_request) olist; + u_int8_t *multipart_body; + + TAILQ_HEAD(, http_header) req_headers; + TAILQ_HEAD(, http_header) resp_headers; + TAILQ_HEAD(, http_arg) arguments; + TAILQ_HEAD(, http_file) files; + TAILQ_ENTRY(http_request) list; + TAILQ_ENTRY(http_request) olist; }; extern int http_request_count; @@ -81,10 +93,18 @@ int http_argument_urldecode(char *); int http_header_recv(struct netbuf *); int http_generic_404(struct http_request *); char *http_post_data_text(struct http_request *); +u_int8_t *http_post_data_bytes(struct http_request *, u_int32_t *); int http_populate_arguments(struct http_request *); +int http_populate_multipart_form(struct http_request *, int *); void http_argument_multiple_free(struct http_arg *); +void http_file_add(struct http_request *, char *, char *, + u_int8_t *, u_int32_t); +void http_argument_add(struct http_request *, char *, + char *, u_int32_t); int http_argument_lookup(struct http_request *, const char *, char **); +int http_file_lookup(struct http_request *, char *, char **, + u_int8_t **, u_int32_t *); int http_argument_multiple_lookup(struct http_request *, struct http_arg *); diff --git a/includes/kore.h b/includes/kore.h @@ -315,9 +315,11 @@ void kore_log(int, const char *, ...); void kore_strlcpy(char *, const char *, size_t); void kore_server_disconnect(struct connection *); int kore_split_string(char *, char *, char **, size_t); +void kore_strip_chars(char *, char, char **); long long kore_strtonum(const char *, int, long long, long long, int *); int kore_base64_encode(u_int8_t *, u_int32_t, char **); int kore_base64_decode(char *, u_int8_t **, u_int32_t *); +void *kore_mem_find(void *, size_t, void *, u_int32_t); void kore_domain_init(void); int kore_domain_new(char *); diff --git a/modules/example/media/upload.html b/modules/example/media/upload.html @@ -9,11 +9,13 @@ <div class="content"> <form method="POST" enctype="multipart/form-data"> + <input type="input" name="firstname"> <input type="file" name="file"> <input type="submit" value="upload"> </form> <p style="font-size: 12px; font-weight: normal">$upload$</p> + <p style="font-size: 12px; font-weight: normal">$firstname$</p> </div> </body> diff --git a/modules/example/module.conf b/modules/example/module.conf @@ -1,8 +1,9 @@ # Example Kore configuration # Server configuration. -bind 127.0.0.1 443 -bind ::1 443 +#bind 127.0.0.1 443 +#bind ::1 443 +bind 10.11.1.174 443 # The path worker processes will chroot too after starting. chroot /home/joris/src/kore @@ -68,7 +69,8 @@ load modules/example/example.module # handler path module_callback # Example domain that responds to localhost. -domain localhost { +#domain localhost { +domain 10.11.1.174 { certfile cert/server.crt certkey cert/server.key accesslog /var/log/kore_access.log diff --git a/modules/example/src/example.c b/modules/example/src/example.c @@ -126,20 +126,34 @@ int serve_file_upload(struct http_request *req) { int r; - char *p; u_int8_t *d; struct kore_buf *b; u_int32_t len; + char *name, buf[BUFSIZ]; b = kore_buf_create(static_len_html_upload); kore_buf_append(b, static_html_upload, static_len_html_upload); if (req->method == HTTP_METHOD_POST) { - p = http_post_data_text(req); - kore_buf_replace_string(b, "$upload$", p, strlen(p)); - kore_mem_free(p); + http_populate_multipart_form(req, &r); + if (http_argument_lookup(req, "firstname", &name)) { + kore_buf_replace_string(b, "$firstname$", + name, strlen(name)); + kore_mem_free(name); + } else { + kore_buf_replace_string(b, "$firstname$", NULL, 0); + } + + if (http_file_lookup(req, "file", &name, &d, &len)) { + snprintf(buf, sizeof(buf), "%s is %d bytes", name, len); + kore_buf_replace_string(b, + "$upload$", buf, strlen(buf)); + } else { + kore_buf_replace_string(b, "$upload$", NULL, 0); + } } else { kore_buf_replace_string(b, "$upload$", NULL, 0); + kore_buf_replace_string(b, "$firstname$", NULL, 0); } d = kore_buf_release(b, &len); diff --git a/src/http.c b/src/http.c @@ -68,12 +68,14 @@ http_request_new(struct connection *c, struct spdy_stream *s, char *host, req->stream = s; req->post_data = NULL; req->hdlr_extra = NULL; + req->multipart_body = NULL; kore_strlcpy(req->host, host, sizeof(req->host)); kore_strlcpy(req->path, path, sizeof(req->path)); TAILQ_INIT(&(req->resp_headers)); TAILQ_INIT(&(req->req_headers)); TAILQ_INIT(&(req->arguments)); + TAILQ_INIT(&(req->files)); if (!strcasecmp(method, "get")) { req->method = HTTP_METHOD_GET; @@ -175,6 +177,7 @@ http_response_header_add(struct http_request *req, char *header, char *value) void http_request_free(struct http_request *req) { + struct http_file *f, *fnext; struct http_arg *q, *qnext; struct http_header *hdr, *next; @@ -203,13 +206,25 @@ http_request_free(struct http_request *req) TAILQ_REMOVE(&(req->arguments), q, list); kore_mem_free(q->name); + if (q->value != NULL) kore_mem_free(q->value); kore_mem_free(q); } + for (f = TAILQ_FIRST(&(req->files)); f != NULL; f = fnext) { + fnext = TAILQ_NEXT(f, list); + TAILQ_REMOVE(&(req->files), f, list); + + kore_mem_free(f->filename); + kore_mem_free(f->name); + kore_mem_free(f); + } + if (req->method == HTTP_METHOD_POST && req->post_data != NULL) kore_buf_free(req->post_data); + if (req->method == HTTP_METHOD_POST && req->multipart_body != NULL) + kore_mem_free(req->multipart_body); if (req->agent != NULL) kore_mem_free(req->agent); @@ -323,7 +338,7 @@ http_header_recv(struct netbuf *nb) struct http_request *req; struct netbuf *nnb; size_t clen, len; - u_int8_t *end_headers, *end; + u_int8_t *end_headers; int h, i, v, skip, bytes_left; char *request[4], *host[3], *hbuf; char *p, *headers[HTTP_REQ_HEADER_MAX]; @@ -334,19 +349,8 @@ http_header_recv(struct netbuf *nb) if (nb->len < 4) return (KORE_RESULT_OK); - end = nb->buf + nb->offset; - for (end_headers = nb->buf; end_headers < end; end_headers++) { - if (*end_headers != '\r') - continue; - - if ((end - end_headers) < 4) - return (KORE_RESULT_OK); - - if (!memcmp(end_headers, "\r\n\r\n", 4)) - break; - } - - if (end_headers == end) + end_headers = kore_mem_find(nb->buf, nb->offset, "\r\n\r\n", 4); + if (end_headers == NULL) return (KORE_RESULT_OK); *end_headers = '\0'; @@ -355,8 +359,6 @@ http_header_recv(struct netbuf *nb) len = end_headers - nb->buf; hbuf = (char *)nb->buf; - kore_debug("HTTP request:\n'%s'\n", hbuf); - h = kore_split_string(hbuf, "\r\n", headers, HTTP_REQ_HEADER_MAX); if (h < 2) return (KORE_RESULT_ERROR); @@ -458,7 +460,7 @@ http_header_recv(struct netbuf *nb) int http_populate_arguments(struct http_request *req) { - struct http_arg *q; + u_int32_t len; int i, v, c, count; char *query, *args[HTTP_MAX_QUERY_ARGS], *val[3]; @@ -478,13 +480,12 @@ http_populate_arguments(struct http_request *req) continue; } - q = kore_malloc(sizeof(*q)); - q->name = kore_strdup(val[0]); - if (c == 2) - q->value = kore_strdup(val[1]); + if (val[1] == NULL) + len = 0; else - q->value = NULL; - TAILQ_INSERT_TAIL(&(req->arguments), q, list); + len = strlen(val[1]); + + http_argument_add(req, val[0], val[1], len); count++; } @@ -501,6 +502,7 @@ http_argument_lookup(struct http_request *req, const char *name, char **out) if (!strcmp(q->name, name)) { if (q->value == NULL) return (KORE_RESULT_ERROR); + *out = kore_strdup(q->value); return (KORE_RESULT_OK); } @@ -564,8 +566,8 @@ http_argument_multiple_lookup(struct http_request *req, struct http_arg *args) c = 0; for (i = 0; args[i].name != NULL; i++) { - if (!http_argument_lookup(req, - args[i].name, &(args[i].value))) { + if (!http_argument_lookup(req, args[i].name, + &(args[i].value))) { args[i].value = NULL; } else { c++; @@ -586,6 +588,201 @@ http_argument_multiple_free(struct http_arg *args) } } +void +http_argument_add(struct http_request *req, char *name, + char *value, u_int32_t len) +{ + struct http_arg *q; + + q = kore_malloc(sizeof(struct http_arg)); + q->name = kore_strdup(name); + + if (len > 0) { + q->value = kore_malloc(len + 1); + kore_strlcpy(q->value, value, len + 1); + } else { + q->value = NULL; + } + + TAILQ_INSERT_TAIL(&(req->arguments), q, list); +} + +void +http_file_add(struct http_request *req, char *name, char *filename, + u_int8_t *data, u_int32_t len) +{ + struct http_file *f; + + f = kore_malloc(sizeof(struct http_file)); + f->len = len; + f->data = data; + f->name = kore_strdup(name); + f->filename = kore_strdup(filename); + + TAILQ_INSERT_TAIL(&(req->files), f, list); +} + +int +http_file_lookup(struct http_request *req, char *name, char **fname, + u_int8_t **data, u_int32_t *len) +{ + struct http_file *f; + + TAILQ_FOREACH(f, &(req->files), list) { + if (!strcmp(f->name, name)) { + *len = f->len; + *data = f->data; + *fname = f->filename; + return (KORE_RESULT_OK); + } + } + + return (KORE_RESULT_ERROR); +} + +int +http_populate_multipart_form(struct http_request *req, int *v) +{ + int h, i, c; + u_int32_t blen, slen, len; + u_int8_t *s, *end, *e, *end_headers, *data; + char *d, *val, *type, *boundary, *fname; + char *headers[5], *args[5], *opt[5], *name; + + *v = 0; + + if (req->method != HTTP_METHOD_POST) + return (KORE_RESULT_ERROR); + + if (!http_request_header_get(req, "content-type", &type)) + return (KORE_RESULT_ERROR); + + h = kore_split_string(type, ";", args, 3); + if (h != 2) { + kore_mem_free(type); + return (KORE_RESULT_ERROR); + } + + if (strcasecmp(args[0], "multipart/form-data")) { + kore_mem_free(type); + return (KORE_RESULT_ERROR); + } + + if ((val = strchr(args[1], '=')) == NULL) { + kore_mem_free(type); + return (KORE_RESULT_ERROR); + } + + val++; + slen = strlen(val); + boundary = kore_malloc(slen + 3); + snprintf(boundary, slen + 3, "--%s", val); + slen = strlen(boundary); + + kore_mem_free(type); + + req->multipart_body = http_post_data_bytes(req, &blen); + if (slen < 3 || blen < (slen * 2)) { + kore_mem_free(boundary); + return (KORE_RESULT_ERROR); + } + + end = req->multipart_body + blen - 2; + if (end < req->multipart_body || (end - 2) < req->multipart_body) { + kore_mem_free(boundary); + return (KORE_RESULT_ERROR); + } + + if (memcmp((end - slen - 2), boundary, slen) || + memcmp((end - 2), "--", 2)) { + kore_mem_free(boundary); + return (KORE_RESULT_ERROR); + } + + v = 0; + s = req->multipart_body + slen + 2; + while (s < end) { + e = kore_mem_find(s, end - s, boundary, slen); + if (e == NULL) { + kore_mem_free(boundary); + return (KORE_RESULT_ERROR); + } + + *(e - 2) = '\0'; + end_headers = kore_mem_find(s, (e - 2) - s, "\r\n\r\n", 4); + if (end_headers == NULL) { + kore_mem_free(boundary); + return (KORE_RESULT_ERROR); + } + + *end_headers = '\0'; + data = end_headers + 4; + + h = kore_split_string((char *)s, "\r\n", headers, 5); + for (i = 0; i < h; i++) { + c = kore_split_string(headers[i], ":", args, 5); + if (c != 2) + continue; + + /* Ignore other headers for now. */ + if (strcasecmp(args[0], "content-disposition")) + continue; + + for (d = args[1]; isspace(*d); d++) + ; + + c = kore_split_string(d, ";", opt, 5); + if (strcasecmp(opt[0], "form-data")) + continue; + + if ((val = strchr(opt[1], '=')) == NULL) + continue; + if (strlen(val) < 3) + continue; + + val++; + kore_strip_chars(val, '"', &name); + + if (opt[2] == NULL) { + http_argument_add(req, name, + (char *)data, (e - 2) - data); + kore_mem_free(name); + continue; + } + + for (d = opt[2]; isspace(*d); d++) + ; + + len = MIN(strlen("filename="), strlen(d)); + if (!strncasecmp(d, "filename=", len)) { + if ((val = strchr(d, '=')) == NULL) { + kore_mem_free(name); + continue; + } + + val++; + kore_strip_chars(val, '"', &fname); + if (strlen(fname) > 0) { + http_file_add(req, name, fname, + data, (e - 2) - data); + } + + kore_mem_free(fname); + } else { + kore_debug("got unknown: %s", opt[2]); + } + + kore_mem_free(name); + } + + s = e + slen + 2; + } + + kore_mem_free(boundary); + + return (KORE_RESULT_OK); +} + char * http_post_data_text(struct http_request *req) { @@ -604,6 +801,17 @@ http_post_data_text(struct http_request *req) return (text); } +u_int8_t * +http_post_data_bytes(struct http_request *req, u_int32_t *len) +{ + u_int8_t *data; + + data = kore_buf_release(req->post_data, len); + req->post_data = NULL; + + return (data); +} + int http_generic_404(struct http_request *req) { diff --git a/src/utils.c b/src/utils.c @@ -138,6 +138,26 @@ kore_split_string(char *input, char *delim, char **out, size_t ele) return (count); } +void +kore_strip_chars(char *in, char strip, char **out) +{ + u_int32_t len; + char *s, *p; + + len = strlen(in); + *out = kore_malloc(len + 1); + p = *out; + + for (s = in; s < (in + len); s++) { + if (*s == strip) + continue; + + *p++ = *s; + } + + *p = '\0'; +} + time_t kore_date_to_time(char *http_date) { @@ -348,6 +368,26 @@ kore_base64_decode(char *in, u_int8_t **out, u_int32_t *olen) return (KORE_RESULT_OK); } +void * +kore_mem_find(void *src, size_t slen, void *needle, u_int32_t len) +{ + u_int8_t *p, *end; + + end = (u_int8_t *)src + slen; + for (p = src; p < end; p++) { + if (*p != *(u_int8_t *)needle) + continue; + + if ((end - p) < len) + return (NULL); + + if (!memcmp(p, needle, len)) + return (p); + } + + return (NULL); +} + void fatal(const char *fmt, ...) {