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:
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, ...)
{