summaryrefslogtreecommitdiff
path: root/tests/test_web.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_web.c')
-rw-r--r--tests/test_web.c175
1 files changed, 175 insertions, 0 deletions
diff --git a/tests/test_web.c b/tests/test_web.c
new file mode 100644
index 0000000..c5a3bea
--- /dev/null
+++ b/tests/test_web.c
@@ -0,0 +1,175 @@
+/* Unit tests for the pure parts of bokfweb (clients/web.c). */
+#include <stdio.h>
+#include <string.h>
+
+#include "util.h"
+#include "web.h"
+
+static int failures = 0;
+static int checks = 0;
+
+#define CHECK(cond) \
+ do { \
+ checks++; \
+ if (!(cond)) { \
+ failures++; \
+ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, \
+ #cond); \
+ } \
+ } while (0)
+
+static int parse(const char *s, struct web_req *r)
+{
+ return web_parse_request(s, strlen(s), r);
+}
+
+static void test_parse(void)
+{
+ struct web_req r;
+ CHECK(parse("GET /web/ HTTP/1.1\r\nHost: x\r\n\r\n", &r) == 0);
+ CHECK(strcmp(r.method, "GET") == 0 && strcmp(r.path, "/web/") == 0);
+ CHECK(r.query[0] == '\0' && r.body_len == 0);
+
+ const char *post = "POST /web/login?next=1 HTTP/1.1\r\n"
+ "content-length: 27\r\n"
+ "Cookie: a=1; bokf_web=tok_EN-9\r\n"
+ "X-Forwarded-For: 203.0.113.7 \r\n\r\n"
+ "username=anna&password=x%21";
+ CHECK(parse(post, &r) == 0);
+ CHECK(strcmp(r.path, "/web/login") == 0);
+ CHECK(strcmp(r.query, "next=1") == 0);
+ CHECK(strcmp(r.forwarded_for, "203.0.113.7") == 0);
+ CHECK(r.body_len == 27 && strncmp(r.body, "username=", 9) == 0);
+
+ /* incomplete: headers or body still coming */
+ CHECK(parse("GET / HTTP/1.1\r\nHost: x\r\n", &r) == 1);
+ CHECK(parse("POST /l HTTP/1.1\r\nContent-Length: 10\r\n\r\nabc", &r) == 1);
+ /* malformed */
+ CHECK(parse("GET\r\n\r\n", &r) == -1);
+ CHECK(parse("GET nopath HTTP/1.1\r\n\r\n", &r) == -1);
+ CHECK(parse("GET / FTP/1.0\r\n\r\n", &r) == -1);
+ CHECK(parse("POST / HTTP/1.1\r\nContent-Length: -1\r\n\r\n", &r) == -1);
+ CHECK(parse("POST / HTTP/1.1\r\nContent-Length: 99999\r\n\r\n", &r) ==
+ -1);
+ CHECK(parse("POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n",
+ &r) == -1);
+ char big[WEB_MAX_REQUEST + 16];
+ memset(big, 'a', sizeof big);
+ memcpy(big, "GET /", 5);
+ CHECK(web_parse_request(big, sizeof big, &r) == -1);
+ /* a path longer than the field is refused, not truncated */
+ char lp[400] = "GET /";
+ memset(lp + 5, 'p', 300);
+ strcpy(lp + 305, " HTTP/1.1\r\n\r\n");
+ CHECK(parse(lp, &r) == -1);
+}
+
+static void test_form_cookie(void)
+{
+ char v[64];
+ const char *f = "username=anna+b&password=p%C3%A5ss%26x&empty=";
+ CHECK(web_form_get(f, strlen(f), "username", v, sizeof v) == 0 &&
+ strcmp(v, "anna b") == 0);
+ CHECK(web_form_get(f, strlen(f), "password", v, sizeof v) == 0 &&
+ strcmp(v, "p\xc3\xa5ss&x") == 0);
+ CHECK(web_form_get(f, strlen(f), "empty", v, sizeof v) == 0 &&
+ v[0] == '\0');
+ CHECK(web_form_get(f, strlen(f), "user", v, sizeof v) == -1);
+ CHECK(web_form_get("a=%2", 4, "a", v, sizeof v) == -1);
+ CHECK(web_form_get("a=%zz", 5, "a", v, sizeof v) == -1);
+ CHECK(web_form_get("a=%00", 5, "a", v, sizeof v) == -1);
+ CHECK(web_form_get("a=12345", 7, "a", v, 4) == -1); /* does not fit */
+ /* the body is not NUL-terminated: len bounds it */
+ CHECK(web_form_get("a=1&b=2XXXX", 7, "b", v, sizeof v) == 0 &&
+ strcmp(v, "2") == 0);
+
+ CHECK(web_cookie_get("a=1; bokf_web=tok; c=3", "bokf_web", v,
+ sizeof v) == 0 &&
+ strcmp(v, "tok") == 0);
+ CHECK(web_cookie_get("xbokf_web=tok", "bokf_web", v, sizeof v) == -1);
+ CHECK(web_cookie_get("", "bokf_web", v, sizeof v) == -1);
+
+ CHECK(web_token_ok("aZ09_-"));
+ CHECK(!web_token_ok(""));
+ CHECK(!web_token_ok("a b"));
+ CHECK(!web_token_ok("a;b"));
+
+ struct buf b;
+ buf_init(&b);
+ web_html_escape(&b, "<a href=\"x\">&'</a>");
+ buf_append(&b, "", 1);
+ CHECK(strcmp((char *)b.p,
+ "&lt;a href=&quot;x&quot;&gt;&amp;&#39;&lt;/a&gt;") == 0);
+ buf_free(&b);
+}
+
+static void test_store(void)
+{
+ static struct web_store st;
+ memset(&st, 0, sizeof st);
+ struct web_session *s = web_store_add(&st, "s_bokfd1", "anna", 1000);
+ CHECK(s && web_token_ok(s->token) && web_token_ok(s->handle));
+ CHECK(strcmp(s->token, s->handle) != 0);
+ char tok[64], hdl[64];
+ snprintf(tok, sizeof tok, "%s", s->token);
+ snprintf(hdl, sizeof hdl, "%s", s->handle);
+ CHECK(web_store_by_token(&st, tok, 1001) == s);
+ CHECK(web_store_by_handle(&st, hdl, 1001) == s);
+ /* token and handle are not interchangeable */
+ CHECK(web_store_by_token(&st, hdl, 1001) == NULL);
+ CHECK(web_store_by_handle(&st, tok, 1001) == NULL);
+ CHECK(web_store_by_token(&st, "nope", 1001) == NULL);
+ CHECK(web_store_by_token(&st, "bad;value", 1001) == NULL);
+ /* absolute expiry */
+ CHECK(web_store_by_token(&st, tok, 1000 + WEB_SESSION_TTL) == NULL);
+ web_store_del(s);
+ CHECK(web_store_by_token(&st, tok, 1001) == NULL);
+
+ /* a full store replaces the oldest session */
+ for (int i = 0; i < WEB_MAX_SESSIONS; i++)
+ web_store_add(&st, "x", "u", 2000 + i);
+ struct web_session *n = web_store_add(&st, "new", "u", 5000);
+ CHECK(n && strcmp(n->bokf, "new") == 0);
+ int oldest_gone = 1;
+ for (int i = 0; i < WEB_MAX_SESSIONS; i++)
+ if (st.s[i].created == 2000)
+ oldest_gone = 0;
+ CHECK(oldest_gone);
+}
+
+static void test_rl(void)
+{
+ static struct web_rl rl;
+ memset(&rl, 0, sizeof rl);
+ for (int i = 0; i < WEB_RL_MAX_FAILS - 1; i++)
+ web_rl_fail(&rl, "203.0.113.7", 100);
+ CHECK(web_rl_blocked(&rl, "203.0.113.7", 100) == 0);
+ web_rl_fail(&rl, "203.0.113.7", 100);
+ CHECK(web_rl_blocked(&rl, "203.0.113.7", 100) == WEB_RL_WINDOW);
+ /* other addresses are not affected */
+ CHECK(web_rl_blocked(&rl, "198.51.100.1", 100) == 0);
+ /* the window ends */
+ CHECK(web_rl_blocked(&rl, "203.0.113.7", 100 + WEB_RL_WINDOW) == 0);
+ web_rl_fail(&rl, "203.0.113.7", 100 + WEB_RL_WINDOW);
+ CHECK(web_rl_blocked(&rl, "203.0.113.7", 100 + WEB_RL_WINDOW) == 0);
+ /* success clears */
+ web_rl_ok(&rl, "203.0.113.7");
+ CHECK(web_rl_blocked(&rl, "203.0.113.7", 101) == 0);
+ /* many addresses: the table never overflows */
+ char a[32];
+ for (int i = 0; i < WEB_RL_SLOTS * 2; i++) {
+ snprintf(a, sizeof a, "10.0.%d.%d", i / 256, i % 256);
+ web_rl_fail(&rl, a, 200);
+ }
+ CHECK(web_rl_blocked(&rl, a, 200) == 0);
+}
+
+int main(void)
+{
+ test_parse();
+ test_form_cookie();
+ test_store();
+ test_rl();
+ printf("test_web: %d checks, %d failures\n", checks, failures);
+ return failures ? 1 : 0;
+}