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:
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);