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 f4ac8c29552c93d532b2d76ca2d0555440dc1e09
parent 23d5e9b341a5dd8979fb7570da07aae3503a0b17
Author: Stanislav Yudin <stan@endlessinsomnia.com>
Date:   Wed,  8 Feb 2017 07:49:10 +1100

Cookies and arguments parsing improvements (#166)

Add new cookie API for handling of cookies.
Diffstat:
examples/cookies/README.md | 10++++++++++
examples/cookies/conf/build.conf | 18++++++++++++++++++
examples/cookies/conf/cookies.conf | 20++++++++++++++++++++
examples/cookies/src/example.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
includes/http.h | 27+++++++++++++++++++++++++++
src/http.c | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
6 files changed, 279 insertions(+), 3 deletions(-)

diff --git a/examples/cookies/README.md b/examples/cookies/README.md @@ -0,0 +1,10 @@ +This example shows cookies API usage + +* Simple key value cookie +* Complex cookie with RFC 6265 features +* Mix with cookie formatted in the header + +Run: +``` + # kore run +``` diff --git a/examples/cookies/conf/build.conf b/examples/cookies/conf/build.conf @@ -0,0 +1,18 @@ +# generic build config +# You can switch flavors using: kore flavor [newflavor] + +# The cflags below are shared between flavors +cflags=-Wall -Wmissing-declarations -Wshadow +cflags=-Wstrict-prototypes -Wmissing-prototypes +cflags=-Wpointer-arith -Wcast-qual -Wsign-compare + +dev { + # These cflags are added to the shared ones when + # you build the "dev" flavor. + cflags=-g +} + +#prod { +# You can specify additional CFLAGS here which are only +# included if you build with the "prod" flavor. +#} diff --git a/examples/cookies/conf/cookies.conf b/examples/cookies/conf/cookies.conf @@ -0,0 +1,20 @@ +# Placeholder configuration + +bind 127.0.0.1 8888 +load ./cookies.so + +tls_dhparam dh2048.pem + +http_body_max 1024000000 +http_body_disk_offload 1024000 + + +domain 127.0.0.1 { + certfile cert/server.crt + certkey cert/server.key + accesslog kore_access.log + + static / serve_cookies + static /secure serve_cookies + static /vault serve_cookies +} diff --git a/examples/cookies/src/example.c b/examples/cookies/src/example.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 Stanislav Yudin <stan@endlessinsomnia.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <kore/kore.h> +#include <kore/http.h> + +#include <openssl/sha.h> + +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +static char *html = "<html><body><h1>Reload this page</h1></body></html>"; + +int serve_cookies(struct http_request *); + +int +serve_cookies(struct http_request *req) +{ + char *read; + struct http_cookie *complex; + + http_populate_cookies(req); + if (http_request_cookie(req, "Simple", &read)) { + kore_log(LOG_DEBUG, "Got simple: %s", read); + } + if (http_request_cookie(req, "Complex", &read)) { + kore_log(LOG_DEBUG, "Got complex: %s", read); + } + if (http_request_cookie(req, "Formatted", &read)) { + kore_log(LOG_DEBUG, "Got formatted: %s", read); + } + + /* set simple cookie */ + http_response_cookie(req, "Simple", "Hello World!", HTTP_COOKIE_DEFAULT); + + /* set complex cookie */ + complex = http_response_cookie(req, "Complex", "Secure Value!", + HTTP_COOKIE_HTTPONLY | HTTP_COOKIE_SECURE); + complex->path = kore_strdup("/secure"); + complex->expires = time(NULL) + 1 * 60 * 60; + complex->domain = kore_strdup("127.0.0.1"); + + /* set formatted cookie */ + http_response_header(req, "set-cookie", + "Formatted=TheValue; Path=/vault; HttpOnly"); + + http_response(req, 200, html, strlen(html)); + return KORE_RESULT_OK; +}+ \ No newline at end of file diff --git a/includes/http.h b/includes/http.h @@ -39,6 +39,10 @@ extern "C" { #define HTTP_REQ_HEADER_MAX 25 #define HTTP_MAX_QUERY_ARGS 20 #define HTTP_MAX_COOKIES 10 +#define HTTP_MAX_COOKIENAME 255 +#define HTTP_HEADER_BUFSIZE 1024 +#define HTTP_COOKIE_BUFSIZE 1024 +#define HTTP_DATE_MAXSIZE 255 #define HTTP_REQUEST_LIMIT 1000 #define HTTP_BODY_DISK_PATH "tmp_files" #define HTTP_BODY_DISK_OFFLOAD 0 @@ -67,6 +71,22 @@ struct http_header { TAILQ_ENTRY(http_header) list; }; +#define HTTP_COOKIE_DEFAULT 0x0000 +#define HTTP_COOKIE_HTTPONLY 0x0001 +#define HTTP_COOKIE_SECURE 0x0002 + +struct http_cookie { + char *name; + char *value; + char *path; + char *domain; + u_int32_t maxage; + time_t expires; + u_int16_t flags; + + TAILQ_ENTRY(http_cookie) list; +}; + struct http_arg { char *name; char *s_value; @@ -199,6 +219,8 @@ struct http_request { LIST_HEAD(, kore_task) tasks; LIST_HEAD(, kore_pgsql) pgsqls; + TAILQ_HEAD(, http_cookie) req_cookies; + TAILQ_HEAD(, http_cookie) resp_cookies; TAILQ_HEAD(, http_header) req_headers; TAILQ_HEAD(, http_header) resp_headers; TAILQ_HEAD(, http_arg) arguments; @@ -242,8 +264,12 @@ void http_response_stream(struct http_request *, int, void *, size_t, int (*cb)(struct netbuf *), void *); int http_request_header(struct http_request *, const char *, char **); +int http_request_cookie(struct http_request *, + const char *, char **); void http_response_header(struct http_request *, const char *, const char *); +struct http_cookie *http_response_cookie(struct http_request *, + char *, char *, u_int16_t); int http_request_new(struct connection *, const char *, const char *, const char *, const char *, struct http_request **); @@ -255,6 +281,7 @@ int http_header_recv(struct netbuf *); void http_populate_get(struct http_request *); void http_populate_post(struct http_request *); void http_populate_multipart_form(struct http_request *); +void http_populate_cookies(struct http_request *); int http_argument_get(struct http_request *, const char *, void **, void *, int); diff --git a/src/http.c b/src/http.c @@ -54,12 +54,14 @@ static int multipart_parse_headers(struct http_request *, const char *, const int); static struct kore_buf *header_buf; +static struct kore_buf *ckhdr_buf; static char http_version[32]; static u_int16_t http_version_len; static TAILQ_HEAD(, http_request) http_requests; static TAILQ_HEAD(, http_request) http_requests_sleeping; static struct kore_pool http_request_pool; static struct kore_pool http_header_pool; +static struct kore_pool http_cookie_pool; static struct kore_pool http_host_pool; static struct kore_pool http_path_pool; static struct kore_pool http_body_path; @@ -81,7 +83,8 @@ http_init(void) TAILQ_INIT(&http_requests); TAILQ_INIT(&http_requests_sleeping); - header_buf = kore_buf_alloc(1024); + header_buf = kore_buf_alloc(HTTP_HEADER_BUFSIZE); + ckhdr_buf = kore_buf_alloc(HTTP_COOKIE_BUFSIZE); l = snprintf(http_version, sizeof(http_version), "server: kore (%d.%d.%d-%s)\r\n", KORE_VERSION_MAJOR, @@ -96,6 +99,8 @@ http_init(void) sizeof(struct http_request), prealloc); kore_pool_init(&http_header_pool, "http_header_pool", sizeof(struct http_header), prealloc * HTTP_REQ_HEADER_MAX); + kore_pool_init(&http_cookie_pool, "http_cookie_pool", + sizeof(struct http_cookie), prealloc * HTTP_MAX_COOKIES); kore_pool_init(&http_host_pool, "http_host_pool", KORE_DOMAINNAME_LEN, prealloc); @@ -113,6 +118,11 @@ http_cleanup(void) header_buf = NULL; } + if (ckhdr_buf != NULL) { + kore_buf_free(ckhdr_buf); + ckhdr_buf = NULL; + } + kore_pool_cleanup(&http_request_pool); kore_pool_cleanup(&http_header_pool); kore_pool_cleanup(&http_host_pool); @@ -255,6 +265,8 @@ http_request_new(struct connection *c, const char *host, TAILQ_INIT(&(req->resp_headers)); TAILQ_INIT(&(req->req_headers)); + TAILQ_INIT(&(req->resp_cookies)); + TAILQ_INIT(&(req->req_cookies)); TAILQ_INIT(&(req->arguments)); TAILQ_INIT(&(req->files)); @@ -413,6 +425,7 @@ http_request_free(struct http_request *req) struct http_file *f, *fnext; struct http_arg *q, *qnext; struct http_header *hdr, *next; + struct http_cookie *ck, *cknext; #if defined(KORE_USE_TASKS) pending_tasks = 0; @@ -474,6 +487,26 @@ http_request_free(struct http_request *req) kore_pool_put(&http_header_pool, hdr); } + for (ck = TAILQ_FIRST(&(req->resp_cookies)); ck != NULL; ck = cknext) { + cknext = TAILQ_NEXT(ck, list); + + TAILQ_REMOVE(&(req->resp_cookies), ck, list); + kore_free(ck->name); + kore_free(ck->value); + kore_free(ck->path); + kore_free(ck->domain); + kore_pool_put(&http_cookie_pool, ck); + } + + for (ck = TAILQ_FIRST(&(req->req_cookies)); ck != NULL; ck = cknext) { + cknext = TAILQ_NEXT(ck, list); + + TAILQ_REMOVE(&(req->req_cookies), ck, list); + kore_free(ck->name); + kore_free(ck->value); + kore_pool_put(&http_cookie_pool, ck); + } + for (q = TAILQ_FIRST(&(req->arguments)); q != NULL; q = qnext) { qnext = TAILQ_NEXT(q, list); @@ -602,6 +635,21 @@ http_request_header(struct http_request *req, const char *header, char **out) } int +http_request_cookie(struct http_request *req, const char *cookie, char **out) +{ + struct http_cookie *ck; + + TAILQ_FOREACH(ck, &(req->req_cookies), list) { + if (!strcasecmp(ck->name, cookie)) { + *out = ck->value; + return (KORE_RESULT_OK); + } + } + + return (KORE_RESULT_ERROR); +} + +int http_header_recv(struct netbuf *nb) { size_t len; @@ -965,6 +1013,60 @@ http_file_rewind(struct http_file *file) file->offset = 0; } +struct http_cookie* +http_response_cookie(struct http_request *req, char *name, char *val, u_int16_t flags) +{ + if (name == NULL || strlen(name) == 0 || + val == NULL || strlen(val) == 0) { + kore_log(LOG_ERR, "invalid cookie values to set"); + return (NULL); + } + + struct http_cookie *ck; + ck = kore_pool_get(&http_cookie_pool); + + ck->name = kore_strdup(name); + ck->value = kore_strdup(val); + ck->flags = flags; + ck->path = NULL; + ck->domain = NULL; + ck->expires = 0; + ck->maxage = UINT32_MAX; + + TAILQ_INSERT_TAIL(&(req->resp_cookies), ck, list); + return (ck); +} + +void +http_populate_cookies(struct http_request *req) +{ + int i, v, n; + struct http_cookie *ck; + char *c, *header; + char *cookies[HTTP_MAX_COOKIES]; + char *pair[3]; + + if (!http_request_header(req, "cookie", &c)) + return; + + header = kore_strdup(c); + v = kore_split_string(header, ";", cookies, HTTP_MAX_COOKIES); + for (i = 0; i < v; i++) { + for (c = cookies[i]; isspace(*c); c++) + ; + + n = kore_split_string(c, "=", pair, 3); + if (n != 2) { + continue; + } + ck = kore_pool_get(&http_cookie_pool); + ck->name = kore_strdup(pair[0]); + ck->value = kore_strdup(pair[1]); + TAILQ_INSERT_TAIL(&(req->req_cookies), ck, list); + } + kore_free(header); +} + void http_populate_post(struct http_request *req) { @@ -1457,8 +1559,11 @@ http_response_normal(struct http_request *req, struct connection *c, int status, const void *d, size_t len) { struct http_header *hdr; - char *conn; - int connection_close; + struct http_cookie *ck; + struct tm expires_tm; + char *conn; + char expires[HTTP_DATE_MAXSIZE]; + int connection_close; header_buf->offset = 0; @@ -1500,6 +1605,38 @@ http_response_normal(struct http_request *req, struct connection *c, } if (req != NULL) { + TAILQ_FOREACH(ck, &(req->resp_cookies), list) { + kore_buf_reset(ckhdr_buf); + kore_buf_appendf(ckhdr_buf, "%s=%s", ck->name, ck->value); + if (ck->path != NULL) + kore_buf_appendf(ckhdr_buf, "; Path=%s", ck->path); + if (ck->domain != NULL) + kore_buf_appendf(ckhdr_buf, "; Domain=%s", ck->domain); + if (ck->expires > 0) { + if (gmtime_r(&ck->expires, &expires_tm) == NULL) { + kore_log(LOG_ERR, "gmtime_r failed: %s", strerror(errno)); + continue; + } + + if (strftime(expires, sizeof(expires), + "%a, %d %b %y %H:%M:%S GMT", &expires_tm) == 0) { + kore_log(LOG_ERR, "strftime failed: %s", strerror(errno)); + continue; + } + kore_buf_appendf(ckhdr_buf, "; Expires=%s", expires); + } + if (ck->maxage != UINT32_MAX) { + kore_buf_appendf(ckhdr_buf, "; Max-Age=%u", ck->maxage); + } + if (ck->flags & HTTP_COOKIE_HTTPONLY) + kore_buf_appendf(ckhdr_buf, "; HttpOnly"); + if (ck->flags & HTTP_COOKIE_SECURE) + kore_buf_appendf(ckhdr_buf, "; Secure"); + + kore_buf_appendf(header_buf, "set-cookie: %s\r\n", + kore_buf_stringify(ckhdr_buf, NULL)); + } + TAILQ_FOREACH(hdr, &(req->resp_headers), list) { kore_buf_appendf(header_buf, "%s: %s\r\n", hdr->header, hdr->value);