/* * Copyright (c) 2014 Cesanta Software Limited * All rights reserved * This software is dual-licensed: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. For the terms of this * license, see . * * You are free to use this software under the terms of the GNU General * Public License, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * Alternatively, you can license this software under a commercial * license, as set out in . */ #include "mongoose.h" #include "src/mg_internal.h" #include "unit_test.h" #include "common/test_util.h" #include "common/cs_md5.h" #if defined(_MSC_VER) && _MSC_VER >= 1900 #include #endif #if defined(__STDC_VERSION__) && __STDC_VERSION__ < 199901L && !defined(WIN32) #define __func__ "" #endif #define FETCH_BUF_SIZE (1024 * 16) #define HTTP_PORT "45772" #define LOOPBACK_IP "127.0.0.1" #define LISTENING_ADDR LOOPBACK_IP ":" HTTP_PORT static const char *s_argv_0 = NULL; static struct mg_serve_http_opts s_http_server_opts; static int s_listening_port = 23456; #define TEST_MG_MALLOC malloc #define TEST_MG_CALLOC calloc #ifndef intptr_t #define intptr_t long #endif void *(*test_malloc)(size_t) = TEST_MG_MALLOC; void *(*test_calloc)(size_t, size_t) = TEST_MG_CALLOC; void *failing_malloc(size_t size) { (void) size; return NULL; } void *failing_calloc(size_t count, size_t size) { (void) count; (void) size; return NULL; } static char *read_file(const char *path, size_t *size) { FILE *fp; struct stat st; char *data = NULL; if ((fp = mg_fopen(path, "rb")) != NULL && !fstat(fileno(fp), &st)) { *size = st.st_size; data = (char *) malloc(*size); fread(data, 1, *size, fp); fclose(fp); } return data; } void msleep(int millis) { #if defined(_WIN32) Sleep(millis); #else usleep(millis * 1000); #endif } static struct mg_iface *test_iface = NULL; static void init_test_connection(struct mg_connection *nc) { memset(nc, 0, sizeof(*nc)); nc->iface = test_iface; } static struct mg_connection *create_test_connection() { struct mg_connection *nc = (struct mg_connection *) calloc(1, sizeof(*nc)); init_test_connection(nc); return nc; } void mg_destroy_conn(struct mg_connection *conn, int destroy_if); static void destroy_test_connection(struct mg_connection *nc) { mg_destroy_conn(nc, 0 /* destroy_if */); } static const char *test_mbuf(void) { struct mbuf io; const char *data = "TEST"; const char *prefix = "MY"; const char *big_prefix = "Some long prefix: "; size_t old_size; mbuf_init(&io, 0); ASSERT(io.buf == NULL); ASSERT_EQ(io.len, 0); ASSERT_EQ(io.size, 0); mbuf_free(&io); ASSERT(io.buf == NULL); ASSERT_EQ(io.len, 0); ASSERT_EQ(io.size, 0); mbuf_init(&io, 10); ASSERT(io.buf != NULL); ASSERT_EQ(io.len, 0); ASSERT_EQ(io.size, 10); mbuf_free(&io); ASSERT(io.buf == NULL); ASSERT_EQ(io.len, 0); ASSERT_EQ(io.size, 0); mbuf_init(&io, 10); ASSERT_EQ(mbuf_append(&io, NULL, 0), 0); /* test allocation failure */ ASSERT_EQ(mbuf_append(&io, NULL, 1125899906842624), 0); ASSERT_EQ(mbuf_append(&io, data, strlen(data)), strlen(data)); mbuf_resize(&io, 2); ASSERT_EQ(io.size, 10); ASSERT_EQ(io.len, strlen(data)); ASSERT_EQ(mbuf_insert(&io, 0, prefix, strlen(prefix)), strlen(prefix)); ASSERT_EQ(io.size, 10); ASSERT_EQ(io.len, strlen(data) + strlen(prefix)); ASSERT_EQ(mbuf_insert(&io, 0, big_prefix, strlen(big_prefix)), strlen(big_prefix)); ASSERT_EQ(io.size, MBUF_SIZE_MULTIPLIER * (strlen(big_prefix) + strlen(prefix) + strlen(data))); ASSERT_STREQ_NZ(io.buf, "Some long prefix: MYTEST"); old_size = io.size; ASSERT_EQ(mbuf_insert(&io, strlen(big_prefix), data, strlen(data)), strlen(data)); ASSERT_EQ(io.size, old_size); ASSERT_STREQ_NZ(io.buf, "Some long prefix: TESTMYTEST"); /* test allocation failure */ ASSERT_EQ(mbuf_insert(&io, 0, NULL, 1125899906842624), 0); /* test overflow */ ASSERT_EQ(mbuf_insert(&io, 0, NULL, -1), 0); mbuf_free(&io); return NULL; } static void eh1(struct mg_connection *nc, int ev, void *ev_data) { struct mbuf *io = &nc->recv_mbuf; switch (ev) { case MG_EV_CONNECT: { int res = *((int *) ev_data); if (res == 0) { mg_printf(nc, "%d %s there", *(int *) ev_data, "hi"); } else { sprintf((char *) nc->user_data, "connect failed! %d", res); } break; } case MG_EV_RECV: { if (nc->listener != NULL) { mg_printf(nc, "%d", (int) io->len); mbuf_remove(io, io->len); } else if (io->len > 0) { sprintf((char *) nc->user_data, "%sok!", (io->len == 2 && memcmp(io->buf, "10", 2) == 0) ? "" : "NOT "); DBG(("%s", (const char *) nc->user_data)); nc->flags |= MG_F_CLOSE_IMMEDIATELY; } break; } default: break; } } #define S_PEM "server.pem" #define C_PEM "client.pem" #define CA_PEM "ca.pem" static const char *test_mgr_with_ssl(int use_ssl) { char addr[100] = "127.0.0.1:0", ip[sizeof(addr)], buf[100] = "", short_addr[5]; struct mg_mgr mgr; struct mg_connection *nc; struct mg_bind_opts bopts; int port, port2; #if !MG_ENABLE_SSL (void) use_ssl; #endif mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ memset(&bopts, 0, sizeof(bopts)); #if MG_ENABLE_SSL if (use_ssl) { bopts.ssl_cert = S_PEM; bopts.ssl_ca_cert = CA_PEM; } #endif ASSERT((nc = mg_bind_opt(&mgr, addr, eh1, bopts)) != NULL); port2 = htons(nc->sa.sin.sin_port); ASSERT(port2 > 0); mg_sock_to_str(nc->sock, addr, sizeof(addr), 3); ASSERT_EQ(sscanf(addr, "%[^:]:%d", ip, &port), 2); ASSERT_STREQ(ip, "127.0.0.1"); ASSERT_EQ(port, port2); mg_sock_to_str(nc->sock, short_addr, sizeof(short_addr), 3); #ifndef _WIN32 ASSERT_STREQ(short_addr, ""); #else /* We use `inet_ntoa` in Windows, so, can get partial result */ ASSERT_STREQ(short_addr, "127."); #endif ASSERT((nc = mg_connect(&mgr, addr, eh1)) != NULL); #if MG_ENABLE_SSL if (use_ssl) { ASSERT(mg_set_ssl(nc, C_PEM, CA_PEM) == NULL); } #endif nc->user_data = buf; poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "ok!"); buf[0] = '\0'; #if MG_ENABLE_SSL if (use_ssl) { struct mg_connect_opts opts; memset(&opts, 0, sizeof(opts)); opts.user_data = buf; opts.ssl_cert = C_PEM; opts.ssl_ca_cert = CA_PEM; opts.ssl_server_name = "*"; /* TODO(rojer): Test this too. */ ASSERT((nc = mg_connect_opt(&mgr, addr, eh1, opts)) != NULL); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "ok!"); buf[0] = '\0'; } #endif mg_mgr_free(&mgr); return NULL; } static const char *test_mgr(void) { return test_mgr_with_ssl(0); } #if MG_ENABLE_SSL void *run_manager(void *param) { struct mg_mgr *mgr = (struct mg_mgr *) param; while (((intptr_t) mgr->user_data) == 0) { mg_mgr_poll(mgr, 1000); } mg_mgr_free(mgr); free(mgr); return NULL; } static const char *test_ssl(void) { return test_mgr_with_ssl(1); } /* * unfortunately krypton has no BIO so it's hard to test this way * We still set MG_ENABLE_SSL so that vc6 has a binary to build */ #ifdef OPENSSL_VERSION_NUMBER static void eh_hello_server(struct mg_connection *nc, int ev, void *ev_data) { (void) ev_data; if (ev == MG_EV_ACCEPT) mg_printf(nc, "hello"); } static const char *test_modern_crypto(void) { char addr[100] = "127.0.0.1:8000"; struct mg_mgr *mgr = (struct mg_mgr *) malloc(sizeof(*mgr)); struct mg_connection *nc; int port; mg_mgr_init(mgr, NULL); ASSERT((nc = mg_bind(mgr, addr, eh_hello_server)) != NULL); port = htons(nc->sa.sin.sin_port); ASSERT(port > 0); ASSERT(mg_set_ssl(nc, S_PEM, NULL /* no client certs */) == NULL); mg_sock_to_str(nc->sock, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); mg_start_thread(run_manager, mgr); { char buf[100]; /* For older OpenSSL version we have to allow older digests. Then it still tests * DH. */ #if OPENSSL_VERSION_NUMBER < 0x10000000 const char *ciphers = "DH:!ADH:AES:MD5:SHA1"; #else const char *ciphers = "DH:!ADH:AES:!MD5:!SHA1"; #endif SSL_CTX *ctx = NULL; BIO *bio = NULL; SSL *ssl = NULL; ctx = SSL_CTX_new(SSLv23_client_method()); ASSERT(ctx != NULL); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); SSL_CTX_set_verify_depth(ctx, 1); /* * These are pretty restrictive settings and should satisfy e.g. * Chrome's "modern cryptography" requirements. */ ASSERT_EQ(SSL_CTX_set_cipher_list(nc->ssl_ctx, ciphers), 1); ASSERT_EQ(SSL_CTX_load_verify_locations(ctx, CA_PEM, NULL), 1); bio = BIO_new_ssl_connect(ctx); ASSERT(bio != NULL); BIO_get_ssl(bio, &ssl); ASSERT(ssl != NULL); SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); BIO_set_conn_hostname(bio, addr); ASSERT_EQ(BIO_do_connect(bio), 1); ASSERT_EQ(BIO_do_handshake(bio), 1); ASSERT_EQ(SSL_get_verify_result(ssl), X509_V_OK); ASSERT_EQ(BIO_read(bio, buf, 5), 5); ASSERT_STREQ_NZ(buf, "hello"); BIO_free_all(bio); SSL_CTX_free(ctx); } mgr->user_data = (void *) 1; return NULL; } #endif /* OPENSSL_VERSION_NUMBER */ #endif /* MG_ENABLE_SSL */ static const char *test_to64(void) { ASSERT_EQ(to64("0"), 0); ASSERT_EQ(to64(""), 0); ASSERT_EQ(to64("123"), 123); ASSERT_EQ(to64("-34"), -34); ASSERT_EQ(to64("3566626116"), 3566626116U); return NULL; } static const char *test_check_ip_acl(void) { uint32_t ip = 0x01020304; ASSERT_EQ(mg_check_ip_acl(NULL, ip), 1); ASSERT_EQ(mg_check_ip_acl("", ip), 1); ASSERT_EQ(mg_check_ip_acl("invalid", ip), -1); ASSERT_EQ(mg_check_ip_acl("-0.0.0.0/0", ip), 0); ASSERT_EQ(mg_check_ip_acl("-0.0.0.0/0,+1.0.0.0/8", ip), 1); ASSERT_EQ(mg_check_ip_acl("-0.0.0.0/0,+1.2.3.4", ip), 1); ASSERT_EQ(mg_check_ip_acl("-0.0.0.0/0,+1.0.0.0/16", ip), 0); return NULL; } static const char *test_parse_uri(void) { struct mg_str uri_out; struct mg_str scheme, user_info, host, path, query, fragment; unsigned int port; { struct mg_str uri = MG_MK_STR("foo"); ASSERT_EQ(mg_parse_uri(uri, NULL, NULL, NULL, NULL, NULL, NULL, NULL), 0); } { struct mg_str uri = MG_MK_STR("http://user:pw@host:80/foo?bar#baz"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "http"); ASSERT_MG_STREQ(user_info, "user:pw"); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 80); ASSERT_MG_STREQ(path, "/foo"); ASSERT_MG_STREQ(query, "bar"); ASSERT_MG_STREQ(fragment, "baz"); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("http://host:80/foo?bar"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "http"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 80); ASSERT_MG_STREQ(path, "/foo"); ASSERT_MG_STREQ(query, "bar"); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("http://host:80/foo#baz"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "http"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 80); ASSERT_MG_STREQ(path, "/foo"); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, "baz"); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("http://host:80/foo"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "http"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 80); ASSERT_MG_STREQ(path, "/foo"); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("http://host/foo"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "http"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 0); ASSERT_MG_STREQ(path, "/foo"); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("http://host"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "http"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 0); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("http://host:80"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "http"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 80); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("tcp://1.2.3.4:5678"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "tcp"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "1.2.3.4"); ASSERT_EQ(port, 5678); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { const struct mg_str uri = MG_MK_STR("tcp://[::1]:234"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "tcp"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "[::1]"); ASSERT_EQ(port, 234); ASSERT_EQ(path.len, 0); ASSERT_EQ(query.len, 0); ASSERT_EQ(fragment.len, 0); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { const struct mg_str uri = MG_MK_STR("tcp://[::1]"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "tcp"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "[::1]"); ASSERT_EQ(port, 0); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { const struct mg_str uri = MG_MK_STR("tcp://[::1"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), -1); } { struct mg_str uri = MG_MK_STR("host:80"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, ""); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 80); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { const struct mg_str uri = MG_MK_STR("1.2.3.4:56789"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, ""); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "1.2.3.4"); ASSERT_EQ(port, 56789); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { const struct mg_str uri = MG_MK_STR("[::1]:2345"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, ""); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "[::1]"); ASSERT_EQ(port, 2345); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { const struct mg_str uri = MG_MK_STR("[::1]"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, ""); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "[::1]"); ASSERT_EQ(port, 0); ASSERT_MG_STREQ(path, ""); ASSERT_MG_STREQ(query, ""); ASSERT_MG_STREQ(fragment, ""); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("host/foo"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, ""); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "host"); ASSERT_EQ(port, 0); ASSERT_MG_STREQ(path, "/foo"); ASSERT_EQ(query.len, 0); ASSERT_EQ(fragment.len, 0); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } { struct mg_str uri = MG_MK_STR("https://a.com/foo/x@y.z/fw.zip"); ASSERT_EQ(mg_parse_uri(uri, &scheme, &user_info, &host, &port, &path, &query, &fragment), 0); ASSERT_MG_STREQ(scheme, "https"); ASSERT_MG_STREQ(user_info, ""); ASSERT_MG_STREQ(host, "a.com"); ASSERT_EQ(port, 0); ASSERT_MG_STREQ(path, "/foo/x@y.z/fw.zip"); ASSERT_EQ(query.len, 0); ASSERT_EQ(fragment.len, 0); ASSERT_EQ(mg_assemble_uri(&scheme, &user_info, &host, port, &path, &query, &fragment, 0, &uri_out), 0); ASSERT_MG_STREQ(uri_out, uri.p); free((void *) uri_out.p); } return NULL; } static const char *test_assemble_uri(void) { struct mg_str scheme, path, uri; { ASSERT_EQ(mg_assemble_uri(NULL, NULL, NULL, 0, NULL, NULL, NULL, 0, &uri), 0); ASSERT_MG_STREQ(uri, ""); } { scheme = mg_mk_str("file"); path = mg_mk_str("/foo/bar"); ASSERT_EQ( mg_assemble_uri(&scheme, NULL, NULL, 0, &path, NULL, NULL, 0, &uri), 0); ASSERT_MG_STREQ(uri, "file:///foo/bar"); free((void *) uri.p); } { scheme = mg_mk_str("file"); path = mg_mk_str("/foo/.././bar/baz"); ASSERT_EQ( mg_assemble_uri(&scheme, NULL, NULL, 0, &path, NULL, NULL, 0, &uri), 0); ASSERT_MG_STREQ(uri, "file:///foo/.././bar/baz"); free((void *) uri.p); ASSERT_EQ( mg_assemble_uri(&scheme, NULL, NULL, 0, &path, NULL, NULL, 1, &uri), 0); ASSERT_MG_STREQ(uri, "file:///bar/baz"); free((void *) uri.p); } return NULL; } /* TODO(mkm) port these test cases to the new async parse_address */ static const char *test_parse_address(void) { static const char *valid[] = { "1", "1.2.3.4:1", "tcp://123", "udp://0.0.0.0:99", #ifndef _WIN32 /* No /etc/hosts on Windows. */ "tcp://localhost:99", #endif ":8080", #if MG_ENABLE_IPV6 "udp://[::1]:123", "[3ffe:2a00:100:7031::1]:900", #endif NULL }; static const int protos[] = { SOCK_STREAM, SOCK_STREAM, SOCK_STREAM, SOCK_DGRAM, SOCK_STREAM, SOCK_STREAM #if MG_ENABLE_IPV6 , SOCK_DGRAM, SOCK_STREAM #endif }; static const char *need_lookup[] = {"udp://a.com:53", "locl_host:12", NULL}; static const char *invalid[] = { "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p", "blah://12", ":123x", "veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeery.long:12345", "udp://missingport", NULL}; char host[50]; union socket_address sa; int i, proto; for (i = 0; valid[i] != NULL; i++) { ASSERT(mg_parse_address(valid[i], &sa, &proto, host, sizeof(host)) > 0); ASSERT_EQ(proto, protos[i]); } for (i = 0; invalid[i] != NULL; i++) { ASSERT_EQ(mg_parse_address(invalid[i], &sa, &proto, host, sizeof(host)), -1); } for (i = 0; need_lookup[i] != NULL; i++) { ASSERT_EQ(mg_parse_address(need_lookup[i], &sa, &proto, host, sizeof(host)), 0); } return NULL; } #define CHECK_MGNP(in, exp_rv, exp_out) \ do { \ int rv; \ struct mg_str mgs; \ char *in_copy = strdup(in); \ mgs.p = in_copy; \ mgs.len = strlen(in); \ rv = mg_normalize_uri_path(&mgs, &mgs); \ ASSERT_EQ(rv, exp_rv); \ if (rv > 0) { \ ASSERT_MG_STREQ(mgs, exp_out); \ } \ MG_FREE(in_copy); \ } while (0) static const char *test_mg_normalize_uri_path(void) { CHECK_MGNP("", 0, ""); CHECK_MGNP("x", 0, ""); CHECK_MGNP("/", 1, "/"); CHECK_MGNP("//", 1, "//"); CHECK_MGNP("/.", 1, "/"); CHECK_MGNP("//.", 1, "//"); CHECK_MGNP("/..", 1, "/"); CHECK_MGNP("//..", 1, "/"); CHECK_MGNP("///..", 1, "//"); CHECK_MGNP("/./", 1, "/"); CHECK_MGNP("/.//", 1, "//"); CHECK_MGNP("/./.", 1, "/"); CHECK_MGNP("/.//.", 1, "//"); CHECK_MGNP("/foo", 1, "/foo"); CHECK_MGNP("/foo/", 1, "/foo/"); CHECK_MGNP("/foo/.", 1, "/foo/"); CHECK_MGNP("/foo/./", 1, "/foo/"); CHECK_MGNP("/foo/..", 1, "/"); CHECK_MGNP("/foo/../", 1, "/"); CHECK_MGNP("/foo/../bar", 1, "/bar"); CHECK_MGNP("/foo/../bar/", 1, "/bar/"); CHECK_MGNP("/foo/../../bar", 1, "/bar"); CHECK_MGNP("/foo////bar/", 1, "/foo////bar/"); /* No percent-decoding. */ CHECK_MGNP("/fo%2fo/%2e%2e/%2e/bar", 1, "/fo%2fo/%2e%2e/%2e/bar"); return NULL; } #define CHECK_U2LP(u, exp_rv, exp_lp, exp_rem) \ do { \ int rv; \ char *lp = NULL; \ struct mg_str r; \ hm.uri.p = u; \ hm.uri.len = strlen(u); \ rv = mg_uri_to_local_path(&hm, &opts, &lp, &r); \ ASSERT_EQ(rv, exp_rv); \ if (rv == 1) { \ ASSERT_STREQ(lp, exp_lp); \ ASSERT_MG_STREQ(r, exp_rem); \ MG_FREE(lp); \ } \ } while (0) #ifdef _WIN32 #define DS "\\" #else #define DS "/" #endif static const char *test_mg_uri_to_local_path(void) { struct http_message hm; struct mg_serve_http_opts opts; memset(&hm, 0, sizeof(hm)); memset(&opts, 0, sizeof(opts)); opts.document_root = "."; CHECK_U2LP("/", 1, ".", ""); CHECK_U2LP("//", 1, ".", ""); CHECK_U2LP("/.", 1, ".", ""); CHECK_U2LP("/%2e", 1, ".", ""); CHECK_U2LP("//.", 1, ".", ""); CHECK_U2LP("/..", 1, ".", ""); CHECK_U2LP("//..", 1, ".", ""); CHECK_U2LP("/./", 1, ".", ""); CHECK_U2LP("/%2E/", 1, ".", ""); CHECK_U2LP("/.//", 1, ".", ""); CHECK_U2LP("/./.", 1, ".", ""); CHECK_U2LP("/.//.", 1, ".", ""); CHECK_U2LP("/data", 1, "." DS "data", ""); CHECK_U2LP("/data/", 1, "." DS "data", ""); CHECK_U2LP("/data/.", 1, "." DS "data", ""); CHECK_U2LP("/data/./", 1, "." DS "data", ""); CHECK_U2LP("/data/..", 1, ".", ""); CHECK_U2LP("/data/../", 1, ".", ""); CHECK_U2LP("/data/../data", 1, "." DS "data", ""); CHECK_U2LP("/data/../../data", 1, "." DS "data", ""); CHECK_U2LP("/no/such/file", 1, "." DS "no" DS "such" DS "file", ""); CHECK_U2LP("/data/no/such/file", 1, "." DS "data" DS "no" DS "such" DS "file", ""); CHECK_U2LP("/data/dummy.xml/", 1, "." DS "data" DS "dummy.xml", "/"); CHECK_U2LP("/./data/./%2E/dummy.xml", 1, "." DS "data" DS "dummy.xml", ""); /* No DIRSEPs in path. */ CHECK_U2LP("/data%2Fdummy%2Exml/in%2Ffo/", 0, "", ""); /* Ok in PATH_INFO. */ CHECK_U2LP("/data/dummy%2Exml/in%2Ffo/", 1, "." DS "data" DS "dummy.xml", "/in%2Ffo/"); /* Test rewrites */ opts.url_rewrites = "/foo=/bar,/=/xxx"; CHECK_U2LP("/", 1, "/xxx", ""); CHECK_U2LP("/yyy", 1, "/xxx" DS "yyy", ""); CHECK_U2LP("/foo/baz", 1, "/bar" DS "baz", ""); /* /foo does not match - must fall on the boundary, / matches. */ CHECK_U2LP("/foobario/baz", 1, "/xxx" DS "foobario" DS "baz", ""); opts.url_rewrites = "**=/xxx"; CHECK_U2LP("/", 1, "/xxx", ""); CHECK_U2LP("/yyy", 1, "/xxx", ""); hm.header_names[0] = mg_mk_str("Host"); hm.header_values[0] = mg_mk_str("example.org"); opts.url_rewrites = "@EXAMPLE.org=/test"; CHECK_U2LP("/hello", 1, "/test" DS "hello", ""); return NULL; } static const char *test_mg_url_encode(void) { const struct mg_str encode_me = MG_MK_STR("I'm a.little_tea-pot,here's$my;spout~oink(oink)oink/!"); struct mg_str encoded = mg_url_encode(encode_me); ASSERT_MG_STREQ( encoded, "I%27m%20a.little_tea-pot,here%27s$my;spout~oink(oink)oink/%21"); free((void *) encoded.p); return NULL; } static void connect_fail_cb(struct mg_connection *nc, int ev, void *p) { switch (ev) { case MG_EV_CONNECT: /* On connection success, set flag 1, else set 4 */ *(int *) nc->user_data |= *(int *) p == 0 ? 1 : 4; break; case MG_EV_CLOSE: *(int *) nc->user_data |= 2; break; } } static void ev_handler_empty(struct mg_connection *nc, int ev, void *p) { (void) nc; (void) ev; (void) p; } static const char *test_connection_errors(void) { struct mg_mgr mgr; struct mg_bind_opts bopts; struct mg_connect_opts copts; struct mg_connection *nc; const char *error_string; int data = 0; mg_mgr_init(&mgr, NULL); memset(&bopts, 0, sizeof(bopts)); bopts.error_string = &error_string; ASSERT(mg_bind_opt(&mgr, "blah://12", NULL, bopts) == NULL); ASSERT_STREQ(error_string, "handler is required"); ASSERT(mg_bind_opt(&mgr, "blah://12", ev_handler_empty, bopts) == NULL); ASSERT_STREQ(error_string, "cannot parse address"); ASSERT(mg_bind_opt(&mgr, "tcp://8.8.8.8:88", ev_handler_empty, bopts) == NULL); ASSERT_STREQ(error_string, "failed to open listener"); #if MG_ENABLE_SSL bopts.ssl_cert = S_PEM; ASSERT(mg_bind_opt(&mgr, "udp://:0", ev_handler_empty, bopts) == NULL); ASSERT_STREQ(error_string, "SSL for UDP is not supported"); bopts.ssl_cert = "no_such_file"; ASSERT(mg_bind_opt(&mgr, "tcp://:0", ev_handler_empty, bopts) == NULL); ASSERT_STREQ(error_string, "Invalid SSL cert"); bopts.ssl_cert = NULL; bopts.ssl_ca_cert = "no_such_file"; ASSERT(mg_bind_opt(&mgr, "tcp://:0", ev_handler_empty, bopts) == NULL); ASSERT_STREQ(error_string, "Invalid SSL CA cert"); #endif memset(&copts, 0, sizeof(copts)); copts.error_string = &error_string; #if 0 copts.error_string = &error_string; ASSERT(mg_connect_opt(&mgr, "tcp://255.255.255.255:0", NULL, copts) == NULL); ASSERT_STREQ(error_string, "cannot connect to socket"); copts.user_data = &data; ASSERT(mg_connect_opt(&mgr, "tcp://255.255.255.255:0", connect_fail_cb, copts) == NULL); ASSERT_STREQ(error_string, "cannot connect to socket"); /* handler isn't invoked when it fails synchronously */ ASSERT_EQ(data, 0); #endif data = 0; copts.user_data = &data; ASSERT((nc = mg_connect_opt(&mgr, "tcp://does.not.exist:8080", connect_fail_cb, copts)) != NULL); /* handler is invoked when it fails asynchronously */ poll_until(&mgr, 5, c_int_eq, &data, (void *) 6); ASSERT_EQ(data, 6); /* mg_bind() does not use MG_CALLOC, but async resolver does */ test_calloc = failing_calloc; #ifndef _WIN32 ASSERT(mg_connect(&mgr, "some.domain.needs.async.resolv:777", NULL) == NULL); ASSERT(mg_bind(&mgr, ":4321", NULL) == NULL); #endif test_calloc = TEST_MG_CALLOC; #if MG_ENABLE_SSL copts.ssl_cert = "no_such_file"; ASSERT((nc = mg_connect_opt(&mgr, "tcp://google.com:80", connect_fail_cb, copts)) == NULL); ASSERT_STREQ(error_string, "Invalid SSL cert"); copts.ssl_cert = NULL; copts.ssl_ca_cert = "no_such_file"; ASSERT((nc = mg_connect_opt(&mgr, "tcp://google.com:80", connect_fail_cb, copts)) == NULL); ASSERT_STREQ(error_string, "Invalid SSL CA cert"); #endif mg_mgr_free(&mgr); return NULL; } static int avt(char **buf, size_t buf_size, const char *fmt, ...) { int result; va_list ap; va_start(ap, fmt); result = mg_avprintf(buf, buf_size, fmt, ap); va_end(ap); return result; } static const char *test_alloc_vprintf(void) { char buf[5], *p = buf; ASSERT_EQ(avt(&p, sizeof(buf), "%d", 123), 3); ASSERT(p == buf); ASSERT_STREQ(p, "123"); ASSERT_EQ(avt(&p, sizeof(buf), "%d", 123456789), 9); ASSERT(p != buf); ASSERT_STREQ(p, "123456789"); free(p); return NULL; } static const char *test_socketpair(void) { sock_t sp[2]; static const char foo[] = "hi there"; char buf[20]; ASSERT_EQ(mg_socketpair(sp, SOCK_DGRAM), 1); ASSERT(sizeof(foo) < sizeof(buf)); /* Send string in one direction */ ASSERT_EQ(send(sp[0], foo, sizeof(foo), 0), sizeof(foo)); ASSERT_EQ(recv(sp[1], buf, sizeof(buf), 0), sizeof(foo)); ASSERT_STREQ(buf, "hi there"); /* Now in opposite direction */ ASSERT_EQ(send(sp[1], foo, sizeof(foo), 0), sizeof(foo)); ASSERT_EQ(recv(sp[0], buf, sizeof(buf), 0), sizeof(foo)); ASSERT_STREQ(buf, "hi there"); closesocket(sp[0]); closesocket(sp[1]); return NULL; } static void ev_timer_handler(struct mg_connection *c, int ev, void *ev_data) { (void) ev_data; switch (ev) { case MG_EV_TIMER: (*(int *) c->user_data)++; break; case MG_EV_CLOSE: /* Make sure we're alive. Make the ASSERTs fail if we're here. */ (*(int *) c->user_data) += 100; break; } } static const char *test_timer(void) { struct mg_mgr m; struct mg_connection *c; double begin, end; int n = 0, i; mg_mgr_init(&m, NULL); ASSERT((c = mg_add_sock(&m, INVALID_SOCKET, ev_timer_handler)) != NULL); c->user_data = &n; /* MG_EV_TIMER should not fire - we did not set it up */ mg_mgr_poll(&m, 1); ASSERT_EQ(n, 0); /* * Now, set it up. Make sure MG_EV_TIMER event fires. * Also, it brings forward the poll timeout. */ ASSERT_EQ(mg_set_timer(c, mg_time() + 0.1), 0.0); begin = mg_time(); /* * Windows is a bit sloppy about select() timeouts, so it may take * a couple iterations for the timer to actually fire. */ for (i = 0; n != 1 && i < 5; i++) { mg_mgr_poll(&m, 1000); } end = mg_time(); ASSERT_EQ(n, 1); ASSERT_LT(end - begin, 0.9); ASSERT_GT(end - begin, 0.09); /* Make sure that timer is reset - second time it does not fire */ ASSERT_EQ(c->ev_timer_time, 0.0); mg_mgr_poll(&m, 1); ASSERT_EQ(n, 1); c->flags |= MG_F_CLOSE_IMMEDIATELY; mg_mgr_poll(&m, 1); ASSERT_EQ(n, 101); /* Async resolver codepath */ n = 0; ASSERT((c = mg_connect(&m, "awful.sad:1234", ev_timer_handler)) != NULL); c->user_data = &n; mg_set_timer(c, 1); mg_mgr_poll(&m, 1); ASSERT_EQ(n, 101); mg_mgr_free(&m); return NULL; } struct simple_data { int num_accept; int num_connect; int num_recv; int num_send; int num_close; int num_timer; char to_send[100]; char data_rcvd[100]; char fail[200]; struct simple_data *sclient_data; struct mg_connection *sclient_nc; }; #ifndef __APPLE__ static void count_events(struct simple_data *d, int ev) { switch (ev) { case MG_EV_POLL: break; case MG_EV_ACCEPT: d->num_accept++; break; case MG_EV_CONNECT: d->num_connect++; break; case MG_EV_RECV: d->num_recv++; break; case MG_EV_SEND: d->num_send++; break; case MG_EV_CLOSE: d->num_close++; break; case MG_EV_TIMER: d->num_timer++; break; default: { char msg[100]; sprintf(msg, "(unexpected event: %d)", ev); strcat(d->fail, msg); } } } static void do_send(struct simple_data *d, struct mg_connection *nc) { if (d->to_send[0] != '\0') { mg_printf(nc, "%s", d->to_send); } } static void do_recv(struct simple_data *d, struct mg_connection *nc, void *ev_data) { if (*((int *) ev_data) != (int) nc->recv_mbuf.len) { char msg[100]; sprintf(msg, "(num recv wrong: %d vs %d)", *((int *) ev_data), (int) strlen(d->to_send)); strcat(d->fail, msg); return; } strncat(d->data_rcvd, nc->recv_mbuf.buf, nc->recv_mbuf.len); mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len); } static void check_sent(struct simple_data *d, struct mg_connection *nc, void *ev_data) { if (*((int *) ev_data) != (int) strlen(d->to_send)) { char msg[100]; sprintf(msg, "(num sent wrong: %d vs %d)", *((int *) ev_data), (int) strlen(d->to_send)); strcat(d->fail, msg); } else { d->to_send[0] = '\0'; } if (nc->send_mbuf.len != 0) strcat(d->fail, "(send buf not empty)"); } static void cb_client(struct mg_connection *nc, int ev, void *ev_data) { struct simple_data *d = (struct simple_data *) nc->user_data; count_events(d, ev); switch (ev) { case MG_EV_CONNECT: do_send(d, nc); break; case MG_EV_SEND: check_sent(d, nc, ev_data); break; case MG_EV_RECV: do_recv(d, nc, ev_data); break; } } static void cb_sclient(struct mg_connection *nc, int ev, void *ev_data) { struct simple_data *d = (struct simple_data *) nc->user_data; count_events(d, ev); switch (ev) { case MG_EV_RECV: { do_recv(d, nc, ev_data); do_send(d, nc); nc->flags |= MG_F_SEND_AND_CLOSE; break; } case MG_EV_SEND: { check_sent(d, nc, ev_data); break; } } } static void cb_server(struct mg_connection *nc, int ev, void *ev_data) { struct simple_data *d = (struct simple_data *) nc->user_data; (void) ev_data; if (ev == MG_EV_ACCEPT) { d->sclient_nc = nc; nc->user_data = d = d->sclient_data; nc->handler = cb_sclient; } count_events(d, ev); } #endif /* The following test is only reliable on Linux. */ #ifdef __linux__ static const char *test_simple(void) { struct mg_mgr mgr; struct mg_connection *nc_server, *nc_client, *nc_sclient; const char *address = "tcp://127.0.0.1:8910"; struct simple_data client_data, server_data, sclient_data; (void) nc_sclient; mg_mgr_init(&mgr, NULL); ASSERT((nc_server = mg_bind(&mgr, address, cb_server)) != NULL); nc_server->user_data = &server_data; memset(&server_data, 0, sizeof(server_data)); server_data.sclient_data = &sclient_data; memset(&sclient_data, 0, sizeof(sclient_data)); mg_mgr_poll(&mgr, 1); /* 1 - nothing */ ASSERT((nc_client = mg_connect(&mgr, address, cb_client)) != NULL); nc_client->user_data = &client_data; memset(&client_data, 0, sizeof(client_data)); strcpy(client_data.to_send, "hi"); mg_mgr_poll(&mgr, 1); /* 2 - client connects and sends, server accepts */ ASSERT_EQ(server_data.num_accept, 0); ASSERT_EQ(sclient_data.num_accept, 1); ASSERT_EQ(client_data.num_connect, 1); ASSERT_EQ(client_data.num_send, 1); ASSERT_STREQ(client_data.fail, ""); ASSERT_STREQ(server_data.fail, ""); ASSERT(server_data.sclient_nc != NULL); nc_sclient = server_data.sclient_nc; ASSERT_EQ(sclient_data.num_send, 0); ASSERT_EQ(sclient_data.num_recv, 0); strcpy(sclient_data.to_send, "hello"); mg_mgr_poll(&mgr, 1); /* 3 - server receives, buffers response, closes */ ASSERT_STREQ(sclient_data.fail, ""); ASSERT_EQ(sclient_data.num_recv, 1); ASSERT_EQ(sclient_data.num_send, 0); ASSERT_EQ(client_data.num_recv, 0); ASSERT_STREQ(sclient_data.data_rcvd, "hi"); mg_mgr_poll(&mgr, 1); /* 4 - server sends */ ASSERT_STREQ(sclient_data.fail, ""); ASSERT_EQ(sclient_data.num_send, 1); mg_mgr_poll(&mgr, 1); /* 5 - client receives */ ASSERT_STREQ(client_data.fail, ""); ASSERT_EQ(client_data.num_recv, 1); ASSERT_STREQ(client_data.data_rcvd, "hello"); mg_mgr_free(&mgr); ASSERT_STREQ(client_data.fail, ""); ASSERT_STREQ(server_data.fail, ""); ASSERT_STREQ(sclient_data.fail, ""); ASSERT_EQ(server_data.num_accept, 0); ASSERT_EQ(server_data.num_connect, 0); ASSERT_EQ(server_data.num_recv, 0); ASSERT_EQ(server_data.num_send, 0); ASSERT_EQ(server_data.num_close, 1); ASSERT_EQ(client_data.num_accept, 0); ASSERT_EQ(client_data.num_connect, 1); ASSERT_EQ(client_data.num_recv, 1); ASSERT_EQ(client_data.num_send, 1); ASSERT_EQ(client_data.num_close, 1); ASSERT_EQ(sclient_data.num_accept, 1); ASSERT_EQ(sclient_data.num_connect, 0); ASSERT_EQ(sclient_data.num_recv, 1); ASSERT_EQ(sclient_data.num_send, 1); ASSERT_EQ(sclient_data.num_close, 1); return NULL; } #endif #if MG_ENABLE_THREADS static void eh2(struct mg_connection *nc, int ev, void *p) { (void) p; switch (ev) { case MG_EV_RECV: strcpy((char *) nc->user_data, nc->recv_mbuf.buf); break; default: break; } } static void *thread_func(void *param) { sock_t sock = *(sock_t *) param; send(sock, ":-)", 4, 0); return NULL; } static const char *test_thread(void) { struct mg_mgr mgr; struct mg_connection *nc; sock_t sp[2]; char buf[20] = ""; ASSERT_EQ(mg_socketpair(sp, SOCK_STREAM), 1); mg_start_thread(thread_func, &sp[1]); mg_mgr_init(&mgr, NULL); ASSERT((nc = mg_add_sock(&mgr, sp[0], eh2)) != NULL); nc->user_data = buf; poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, ":-)"); mg_mgr_free(&mgr); closesocket(sp[1]); return NULL; } #endif /* MG_ENABLE_THREADS */ struct udp_res { struct mbuf buf_srv; struct mg_connection *srv_recv_conn; struct mbuf buf_clnt; struct mg_connection *clt_recv_conn; }; static void eh3_srv(struct mg_connection *nc, int ev, void *p) { struct udp_res *ur = (struct udp_res *) nc->mgr->user_data; struct mbuf *io = &nc->recv_mbuf; (void) p; if (nc->flags & MG_F_LISTENING) { if (ev != MG_EV_POLL && ev != MG_EV_CLOSE) { mbuf_append(&ur->buf_srv, "BUG", 3); } return; } if (ev == MG_EV_ACCEPT) { mbuf_append(&ur->buf_srv, "ACCEPT", 6); } else if (ev == MG_EV_RECV) { mbuf_append(&ur->buf_srv, (nc->flags & MG_F_SEND_AND_CLOSE ? "+" : "-"), 1); mbuf_append(&ur->buf_srv, " RECV ", 6); mbuf_append(&ur->buf_srv, io->buf, io->len); mg_send(nc, io->buf, io->len); mbuf_remove(io, io->len); } else if (ev == MG_EV_CLOSE) { mbuf_append(&ur->buf_srv, " CLOSE", 6); } } static void eh3_clnt(struct mg_connection *nc, int ev, void *p) { struct udp_res *ur = (struct udp_res *) nc->mgr->user_data; struct mbuf *io = &nc->recv_mbuf; (void) p; if (ev == MG_EV_CONNECT) { mbuf_append(&ur->buf_clnt, "CONNECT", 7); mg_printf(nc, "%s", "boo!"); } else if (ev == MG_EV_RECV) { mbuf_append(&ur->buf_clnt, " RECV ", 6); mbuf_append(&ur->buf_clnt, io->buf, io->len); mbuf_remove(io, io->len); nc->flags |= MG_F_CLOSE_IMMEDIATELY; } else if (ev == MG_EV_CLOSE) { mbuf_append(&ur->buf_clnt, " CLOSE", 6); } } static const char *test_udp(void) { struct mg_mgr mgr; struct mg_connection *nc1, *nc2; const char *address = "udp://127.0.0.1:7878"; struct udp_res res; memset(&res, 0, sizeof(res)); mbuf_init(&res.buf_srv, 100); mbuf_init(&res.buf_clnt, 100); mg_mgr_init(&mgr, &res); ASSERT((nc1 = mg_bind(&mgr, address, eh3_srv)) != NULL); ASSERT((nc2 = mg_connect(&mgr, address, eh3_clnt)) != NULL); poll_until(&mgr, 1, c_int_eq, &res.buf_clnt.len, (void *) 23); ASSERT_STREQ_NZ(res.buf_srv.buf, "ACCEPT+ RECV boo! CLOSE"); ASSERT_STREQ_NZ(res.buf_clnt.buf, "CONNECT RECV boo! CLOSE"); mbuf_free(&res.buf_srv); mbuf_free(&res.buf_clnt); mg_mgr_free(&mgr); return NULL; } static const char *test_parse_http_message(void) { static const char *a = "GET / HTTP/1.0\n\n"; static const char *b = "GET /blah HTTP/1.0\r\nFoo: bar \r\n\r\n"; static const char *c = "get b c\nz: k \nb: t\nvvv\n\n xx"; static const char *d = "a b c\nContent-Length: 21 \nb: t\nvvv\n\n"; static const char *e = "GET /foo?a=b&c=d HTTP/1.0\n\n"; static const char *f = "POST /x HTTP/1.0\n\n"; static const char *g = "WOHOO /x HTTP/1.0\n\n"; static const char *h = "HTTP/1.0 200 OK\n\n"; static const char *i = "HTTP/1.0 999 OMGWTFBBQ\n\n"; static const char *j = "GET / HTTP/1.0\r\nHost: 127.0.0.1:18888\r\nCookie:\r\nX-PlayID: " "45455\r\nRange: 0-1\r\n\r\n"; int idx, len; struct mg_str *v; struct http_message req; len = strlen(b); for (idx = 0; idx < len; idx++) { ASSERT_EQ(mg_parse_http(b, idx, &req, 1), 0); } ASSERT_EQ(mg_parse_http("\b23", 3, &req, 1), -1); ASSERT_EQ(mg_parse_http("\b23", 3, &req, 0), -1); ASSERT_EQ(mg_parse_http("get\n\n", 5, &req, 1), -1); ASSERT_EQ(mg_parse_http("get\n\n", 5, &req, 0), -1); ASSERT_EQ(mg_parse_http(a, strlen(a) - 1, &req, 1), 0); ASSERT_EQ(mg_parse_http(a, strlen(a), &req, 0), -1); ASSERT_EQ(mg_parse_http(a, strlen(a), &req, 1), (int) strlen(a)); ASSERT_EQ(req.message.len, strlen(a)); ASSERT_EQ(req.body.len, 0); ASSERT_EQ(mg_parse_http(b, strlen(b), &req, 0), -1); ASSERT_EQ(mg_parse_http(b, strlen(b), &req, 1), (int) strlen(b)); ASSERT_EQ(req.header_names[0].len, 3); ASSERT_EQ(req.header_values[0].len, 3); ASSERT(req.header_names[1].p == NULL); ASSERT_EQ(req.query_string.len, 0); ASSERT_EQ(req.message.len, strlen(b)); ASSERT_EQ(req.body.len, 0); ASSERT_EQ(mg_parse_http(c, strlen(c), &req, 1), (int) strlen(c) - 3); ASSERT(req.header_names[2].p == NULL); ASSERT(req.header_names[0].p != NULL); ASSERT(req.header_names[1].p != NULL); ASSERT_EQ(memcmp(req.header_values[1].p, "t", 1), 0); ASSERT_EQ(req.header_names[1].len, 1); ASSERT_EQ(req.body.len, 0); ASSERT_EQ(mg_parse_http(d, strlen(d), &req, 1), (int) strlen(d)); ASSERT_EQ(req.body.len, 21); ASSERT_EQ(req.message.len, 21 + strlen(d)); ASSERT(mg_get_http_header(&req, "foo") == NULL); ASSERT((v = mg_get_http_header(&req, "contENT-Length")) != NULL); ASSERT_EQ(v->len, 2); ASSERT_STREQ_NZ(v->p, "21"); ASSERT_EQ(mg_parse_http(e, strlen(e), &req, 1), (int) strlen(e)); ASSERT_EQ(mg_vcmp(&req.uri, "/foo"), 0); ASSERT_EQ(mg_vcmp(&req.query_string, "a=b&c=d"), 0); ASSERT_EQ(mg_parse_http(f, strlen(f), &req, 1), (int) strlen(f)); ASSERT_EQ(req.body.len, (size_t) ~0); ASSERT_EQ(mg_parse_http(g, strlen(g), &req, 1), (int) strlen(g)); ASSERT_EQ(req.body.len, 0); ASSERT_EQ(mg_parse_http(h, strlen(h), &req, 0), (int) strlen(h)); ASSERT_EQ(mg_vcmp(&req.proto, "HTTP/1.0"), 0); ASSERT_EQ(req.resp_code, 200); ASSERT_EQ(mg_vcmp(&req.resp_status_msg, "OK"), 0); ASSERT_EQ(req.body.len, (size_t) ~0); ASSERT_EQ(mg_parse_http(i, strlen(i), &req, 0), -1); ASSERT_EQ(mg_parse_http(j, strlen(j), &req, 1), (int) strlen(j)); ASSERT(mg_get_http_header(&req, "Host") != NULL); ASSERT(mg_get_http_header(&req, "Cookie") == NULL); ASSERT(mg_get_http_header(&req, "Range") != NULL); return NULL; } static const char *test_get_http_var(void) { char buf[256]; struct mg_str body; body.p = "key1=value1&key2=value2&key3=value%203&key4=value+4"; body.len = strlen(body.p); ASSERT(mg_get_http_var(&body, "key1", buf, sizeof(buf)) > 0); ASSERT_STREQ(buf, "value1"); ASSERT(mg_get_http_var(&body, "KEY1", buf, sizeof(buf)) > 0); ASSERT_STREQ(buf, "value1"); ASSERT(mg_get_http_var(&body, "key2", buf, sizeof(buf)) > 0); ASSERT_STREQ(buf, "value2"); ASSERT(mg_get_http_var(&body, "key3", buf, sizeof(buf)) > 0); ASSERT_STREQ(buf, "value 3"); ASSERT(mg_get_http_var(&body, "key4", buf, sizeof(buf)) > 0); ASSERT_STREQ(buf, "value 4"); ASSERT_EQ(mg_get_http_var(&body, "key1", NULL, sizeof(buf)), -2); ASSERT_EQ(mg_get_http_var(&body, "key1", buf, 0), -2); ASSERT_EQ(mg_get_http_var(&body, NULL, buf, sizeof(buf)), -1); ASSERT_EQ(mg_get_http_var(&body, "key1", buf, 1), -3); body.p = "key=broken%2"; body.len = strlen(body.p); ASSERT(mg_get_http_var(&body, "key", buf, sizeof(buf)) < 0); body.p = "key=broken%2x"; body.len = strlen(body.p); ASSERT(mg_get_http_var(&body, "key", buf, sizeof(buf)) < 0); ASSERT_EQ(mg_get_http_var(&body, "inexistent", buf, sizeof(buf)), -4); return NULL; } static void cb1(struct mg_connection *nc, int ev, void *ev_data) { if (ev == MG_EV_HTTP_REQUEST) { struct http_message *hm = (struct http_message *) ev_data; if (mg_vcmp(&hm->uri, "/foo") == 0) { mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[%.*s %d]", (int) hm->uri.len, hm->uri.p, (int) hm->body.len); nc->flags |= MG_F_SEND_AND_CLOSE; } else { s_http_server_opts.document_root = "."; s_http_server_opts.per_directory_auth_file = "passwords.txt"; s_http_server_opts.auth_domain = "foo.com"; s_http_server_opts.dav_document_root = "./data/dav"; s_http_server_opts.hidden_file_pattern = "hidden_file.*$"; s_http_server_opts.custom_mime_types = ".txt=text/plain; charset=windows-1251," ".c=text/plain; charset=utf-8"; mg_serve_http(nc, hm, s_http_server_opts); } } else if (ev == MG_EV_SSI_CALL) { mg_printf_html_escape(nc, "[ssi call: %s]", (char *) ev_data); } else if (ev == MG_EV_SSI_CALL_CTX) { struct mg_ssi_call_ctx *cctx = (struct mg_ssi_call_ctx *) ev_data; mg_printf_html_escape(nc, "[ssi call w/ ctx: %.*s %.*s %.*s]", (int) cctx->req->uri.len, cctx->req->uri.p, (int) cctx->file.len, cctx->file.p, (int) cctx->arg.len, cctx->arg.p); } } static void secret_endpoint_cb(struct mg_connection *nc, int ev, void *ev_data) { if (ev != MG_EV_HTTP_REQUEST) return; mg_send_response_line(nc, 200, "Content-Type: text/plain\r\n"); mg_printf(nc, "%s", "Secret!"); nc->flags |= MG_F_SEND_AND_CLOSE; (void) ev_data; } static void cb2(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; if (ev == MG_EV_HTTP_REPLY) { sprintf((char *) nc->user_data, "%.*s %lu", (int) hm->body.len, hm->body.p, (unsigned long) hm->message.len); nc->flags |= MG_F_CLOSE_IMMEDIATELY; } } static void cb7(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; size_t size; char *data, *user_data = (char *) nc->user_data; if (ev == MG_EV_HTTP_REPLY) { /* Make sure that we've downloaded this executable, byte-to-byte */ data = read_file(s_argv_0, &size); DBG(("file %s, size %d; got %d", s_argv_0, (int) size, (int) hm->body.len)); if (data != NULL && size == hm->body.len && memcmp(hm->body.p, data, size) == 0) { strcpy(user_data, "success"); } else { strcpy(user_data, "fail"); } free(data); nc->flags |= MG_F_CLOSE_IMMEDIATELY; } else if (ev == MG_EV_RECV) { #if 0 LOG(LL_INFO, ("%d", (int) nc->recv_mbuf.len)); #endif } } static void cb8(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; DBG(("%p ev=%d", (void *) nc, ev)); if (ev == MG_EV_HTTP_REPLY) { snprintf((char *) nc->user_data, FETCH_BUF_SIZE, "%.*s", (int) hm->message.len, hm->message.p); nc->flags |= MG_F_CLOSE_IMMEDIATELY; } } static void cb10(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; struct mg_str *s; if (ev == MG_EV_HTTP_REPLY && (s = mg_get_http_header(hm, "Content-Type")) != NULL) { sprintf((char *) nc->user_data, "%.*s", (int) s->len, s->p); } } static void endpoint_handler(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; (void) ev_data; if (ev == MG_EV_HTTP_REQUEST) { if (mg_vcmp(&hm->uri, "/") == 0 || mg_vcmp(&hm->uri, "/foo") == 0) { mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[%.*s%s%.*s %d]", (int) hm->uri.len, hm->uri.p, hm->query_string.len > 0 ? "?" : "", (int) hm->query_string.len, hm->query_string.p, (int) hm->body.len); nc->flags |= MG_F_SEND_AND_CLOSE; } } else if (ev == MG_EV_CLOSE) { if (nc->listener != NULL) { (*(int *) nc->listener->user_data) += 100; } } } static void handle_hello1(struct mg_connection *nc, int ev, void *ev_data) { (void) ev_data; switch (ev) { case MG_EV_HTTP_REQUEST: mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[I am Hello1]"); nc->flags |= MG_F_SEND_AND_CLOSE; break; case MG_EV_CLOSE: (*(int *) nc->listener->user_data)++; break; } } static void handle_hello2(struct mg_connection *nc, int ev, void *ev_data) { (void) ev_data; switch (ev) { case MG_EV_HTTP_REQUEST: mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[I am Hello two]"); nc->flags |= MG_F_SEND_AND_CLOSE; break; case MG_EV_CLOSE: (*(int *) nc->listener->user_data)++; break; } } static void handle_hello5(struct mg_connection *nc, int ev, void *ev_data) { (void) ev_data; switch (ev) { case MG_EV_HTTP_REQUEST: mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[I am Hello again]"); nc->flags |= MG_F_SEND_AND_CLOSE; break; case MG_EV_CLOSE: (*(int *) nc->listener->user_data)++; break; } } static void vfetch_http(char *buf, const char *auth_file, const char *request_fmt, va_list ap) { struct mg_mgr mgr; struct mg_connection *nc; struct mg_http_endpoint_opts ep_opts; char local_addr[50]; /* Setup server. Use different local port for the next invocation. */ mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ snprintf(local_addr, sizeof(local_addr), "127.0.0.1:%d", s_listening_port++); nc = mg_bind(&mgr, local_addr, cb1); mg_set_protocol_http_websocket(nc); memset(&ep_opts, 0, sizeof(ep_opts)); ep_opts.auth_file = auth_file; ep_opts.auth_domain = "foo.com"; mg_register_http_endpoint_opt(nc, "/secret", secret_endpoint_cb, ep_opts); /* Setup client */ nc = mg_connect(&mgr, local_addr, cb8); mg_set_protocol_http_websocket(nc); nc->user_data = buf; mg_vprintf(nc, request_fmt, ap); /* Run event loop, destroy server */ buf[0] = '\0'; poll_until(&mgr, 5, c_str_ne, buf, (void *) ""); mg_mgr_free(&mgr); } static void fetch_http(char *buf, const char *request_fmt, ...) { va_list ap; va_start(ap, request_fmt); vfetch_http(buf, "data/auth/passwords.txt", request_fmt, ap); va_end(ap); } static void fetch_http2(char *buf, const char *auth_file, const char *request_fmt, ...) { va_list ap; va_start(ap, request_fmt); vfetch_http(buf, auth_file, request_fmt, ap); va_end(ap); } static const char *test_http_endpoints(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *local_addr = "127.0.0.1:7778"; char buf[50]; int close_count = 0; mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ ASSERT((nc = mg_bind(&mgr, local_addr, endpoint_handler)) != NULL); mg_register_http_endpoint(nc, "/hello1", handle_hello1 MG_UD_ARG(NULL)); mg_register_http_endpoint(nc, "/hello1/hello2", handle_hello2 MG_UD_ARG(NULL)); mg_register_http_endpoint(nc, "/hello5/*", handle_hello5 MG_UD_ARG(NULL)); nc->user_data = &close_count; mg_set_protocol_http_websocket(nc); /* Valid HTTP request. Pass test buffer to the callback. */ ASSERT((nc = mg_connect_http(&mgr, cb2, "127.0.0.1:7778", NULL, "0123456789")) != NULL); memset(buf, 0, sizeof(buf)); nc->user_data = buf; DBG(("== WAIT ==")); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[/ 10] 25"); ASSERT((nc = mg_connect_http(&mgr, cb2, "http://127.0.0.1:7778/", NULL, "0123456789")) != NULL); memset(buf, 0, sizeof(buf)); nc->user_data = buf; DBG(("== WAIT ==")); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[/ 10] 25"); ASSERT((nc = mg_connect_http(&mgr, cb2, "http://127.0.0.1:7778/foo?a=b", NULL, "foo")) != NULL); memset(buf, 0, sizeof(buf)); nc->user_data = buf; DBG(("== WAIT ==")); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[/foo?a=b 3] 31"); ASSERT((nc = mg_connect_http(&mgr, cb2, "http://127.0.0.1:7778/foo", NULL, "0123456789")) != NULL); memset(buf, 0, sizeof(buf)); nc->user_data = buf; DBG(("== WAIT ==")); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[/foo 10] 28"); ASSERT((nc = mg_connect_http(&mgr, cb2, "http://127.0.0.1:7778/hello1", NULL, "0123456789")) != NULL); memset(buf, 0, sizeof(buf)); nc->user_data = buf; DBG(("== WAIT ==")); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[I am Hello1] 32"); ASSERT((nc = mg_connect_http(&mgr, cb2, "http://127.0.0.1:7778/hello1/hello2", NULL, "0123456789")) != NULL); memset(buf, 0, sizeof(buf)); nc->user_data = buf; DBG(("== WAIT ==")); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[I am Hello two] 35"); ASSERT((nc = mg_connect_http(&mgr, cb2, "http://127.0.0.1:7778/hello5/dummy", NULL, "0123456789")) != NULL); memset(buf, 0, sizeof(buf)); nc->user_data = buf; poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[I am Hello again] 37"); ASSERT_EQ(close_count, 700); mg_mgr_free(&mgr); return NULL; } static const char *serve_file_verify(struct http_message *hm) { size_t size; char *data = read_file("unit_test.c", &size); if (data == NULL || size != hm->body.len || memcmp(hm->body.p, data, size) != 0) { return "wrong data"; } free(data); { char buf[20]; struct mg_str *h = mg_get_http_header(hm, "Content-Length"); ASSERT(h != NULL); snprintf(buf, sizeof(buf), "%d", (int) hm->body.len); ASSERT_MG_STREQ(*h, buf); } { struct mg_str *h = mg_get_http_header(hm, "Connection"); ASSERT(h != NULL); ASSERT_MG_STREQ(*h, "keep-alive"); } return "success"; } static void serve_file_cb(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; char *user_data = (char *) nc->user_data; if (ev == MG_EV_HTTP_REQUEST) { mg_http_serve_file(nc, hm, "unit_test.c", mg_mk_str("text/plain"), mg_mk_str("")); } else if (ev == MG_EV_HTTP_REPLY) { strcpy(user_data, serve_file_verify(hm)); nc->flags |= MG_F_CLOSE_IMMEDIATELY; } } static const char *test_http_serve_file(void) { struct mg_mgr mgr; struct mg_connection *lc, *nc; const char *local_addr = "127.0.0.1:7777"; char status[100]; mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ ASSERT((lc = mg_bind(&mgr, local_addr, serve_file_cb)) != NULL); mg_set_protocol_http_websocket(lc); ASSERT((nc = mg_connect(&mgr, local_addr, serve_file_cb)) != NULL); mg_set_protocol_http_websocket(nc); status[0] = '\0'; nc->user_data = status; mg_printf(nc, "GET / HTTP/1.1\r\n\r\n"); poll_until(&mgr, 5, c_str_ne, status, (void *) ""); ASSERT_STREQ(status, "success"); mg_mgr_free(&mgr); return NULL; } static void srv1(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_REQUEST) { mg_http_serve_file(c, (struct http_message *) ev_data, "unit_test.c", mg_mk_str("text/plain"), mg_mk_str("")); } } static void srv2(struct mg_connection *c, int ev, void *ev_data) { static cs_md5_ctx c1, c2; struct http_message *hm = (struct http_message *) ev_data; switch (ev) { case MG_EV_CONNECT: cs_md5_init(&c1); cs_md5_init(&c2); break; case MG_EV_HTTP_CHUNK: cs_md5_update(&c1, (const unsigned char *) hm->body.p, hm->body.len); c->flags |= MG_F_DELETE_CHUNK; break; case MG_EV_HTTP_REPLY: { unsigned char sig1[16], sig2[sizeof(sig1)]; size_t size; char *data = read_file("unit_test.c", &size); if (data != NULL) cs_md5_update(&c2, (const unsigned char *) data, size); free(data); cs_md5_final(sig1, &c1); cs_md5_final(sig2, &c2); *(int *) c->user_data = (memcmp(sig1, sig2, sizeof(sig1)) == 0) ? 1 : 2; break; } } } static const char *test_http_serve_file_streaming(void) { struct mg_mgr mgr; struct mg_connection *lc, *nc; const char *local_addr = "127.0.0.1:7732"; int status = 0; mg_mgr_init(&mgr, NULL); ASSERT((lc = mg_bind(&mgr, local_addr, srv1)) != NULL); mg_set_protocol_http_websocket(lc); ASSERT((nc = mg_connect(&mgr, local_addr, srv2)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = &status; mg_printf(nc, "GET / HTTP/1.1\r\n\r\n"); poll_until(&mgr, 30, c_int_ne, &status, (void *) 0); ASSERT_EQ(status, 1); mg_mgr_free(&mgr); return NULL; } static const char *test_http(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *this_binary, *local_addr = "127.0.0.1:7777"; char buf[50] = "", status[100] = "", mime1[20] = "", mime2[100] = ""; char opt_buf[1024] = ""; const char *opt_answer = "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS, MKCOL, " "PUT, DELETE, PROPFIND, MOVE"; char url[1000]; mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ ASSERT((nc = mg_bind(&mgr, local_addr, cb1)) != NULL); mg_set_protocol_http_websocket(nc); /* Valid HTTP request. Pass test buffer to the callback. */ ASSERT((nc = mg_connect_http(&mgr, cb2, "http://127.0.0.1:7777/foo", NULL, "0123456789")) != NULL); nc->user_data = buf; DBG(("== WAIT ==")); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "[/foo 10] 28"); /* OPTIONS request */ ASSERT((nc = mg_connect(&mgr, local_addr, cb8)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = opt_buf; mg_printf(nc, "%s", "OPTIONS / HTTP 1.0\n\n"); poll_until(&mgr, 1, c_str_ne, opt_buf, (void *) ""); ASSERT_STREQ_NZ(opt_buf, opt_answer); /* Invalid HTTP request */ ASSERT((nc = mg_connect(&mgr, local_addr, cb2)) != NULL); mg_set_protocol_http_websocket(nc); mg_printf(nc, "%s", "bl\x03\n\n"); /* Test static file download by downloading this executable, argv[0] */ ASSERT((nc = mg_connect(&mgr, local_addr, cb7)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = status; /* Wine and GDB set argv0 to full path: strip the dir component */ if ((this_binary = strrchr(s_argv_0, '\\')) != NULL) { this_binary++; } else if ((this_binary = strrchr(s_argv_0, '/')) != NULL) { this_binary++; } else { this_binary = s_argv_0; } mg_printf(nc, "GET /%s HTTP/1.0\n\n", this_binary); /* Test mime type for static file */ snprintf(url, sizeof(url), "http://%s/data/dummy.xml", local_addr); ASSERT((nc = mg_connect_http(&mgr, cb10, url, NULL, NULL)) != NULL); nc->user_data = mime1; /* Test custom mime type for static file */ snprintf(url, sizeof(url), "http://%s/data/range.txt", local_addr); ASSERT((nc = mg_connect_http(&mgr, cb10, url, NULL, NULL)) != NULL); nc->user_data = mime2; /* Run event loop. Use more cycles to let file download complete. */ poll_until(&mgr, 5, c_str_ne, status, (void *) ""); mg_mgr_free(&mgr); /* Check that test buffer has been filled by the callback properly. */ ASSERT_STREQ(status, "success"); ASSERT_STREQ(mime1, "text/xml"); ASSERT_STREQ(mime2, "text/plain; charset=windows-1251"); return NULL; } static const char *test_http_send_redirect(void) { struct mg_connection nc; init_test_connection(&nc); mg_http_send_redirect(&nc, 301, mg_mk_str("http://foo"), mg_mk_str(NULL)); mg_send(&nc, "", 1); /* NUL */ ASSERT_STREQ_NZ(nc.send_mbuf.buf, "HTTP/1.1 301 Moved\r\n" "Server: Mongoose/" MG_VERSION "\r\n" "Location: http://foo\r\n" "Content-Type: text/html\r\n" "Content-Length: 41\r\n" "Cache-Control: no-cache\r\n" "\r\n" "

Moved here.\r\n"); ASSERT_EQ(nc.send_mbuf.len, 179); mbuf_remove(&nc.send_mbuf, nc.send_mbuf.len); mg_http_send_redirect(&nc, 302, mg_mk_str("/bar"), mg_mk_str("O-M-G: WTF?!")); mg_send(&nc, "", 1); /* NUL */ ASSERT_STREQ_NZ(nc.send_mbuf.buf, "HTTP/1.1 302 Found\r\n" "Server: Mongoose/" MG_VERSION "\r\n" "Location: /bar\r\n" "Content-Type: text/html\r\n" "Content-Length: 35\r\n" "Cache-Control: no-cache\r\n" "O-M-G: WTF?!\r\n" "\r\n" "

Moved here.\r\n"); ASSERT_EQ(nc.send_mbuf.len, 181); mbuf_remove(&nc.send_mbuf, nc.send_mbuf.len); mbuf_free(&nc.send_mbuf); return NULL; } static const char *test_http_digest_auth(void) { char buf[FETCH_BUF_SIZE], auth_hdr[200]; char nonce[40]; struct mg_str bufstr; /* Test digest authorization popup - per-directory auth file */ fetch_http(buf, "%s", "GET /data/auth/a.txt?a=b HTTP/1.0\r\n\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 401"); bufstr = mg_mk_str(buf); ASSERT_GT(mg_http_parse_header(&bufstr, "nonce", nonce, sizeof(nonce)), 0); buf[0] = '\0'; /* Per-endpoint auth config */ fetch_http(buf, "%s", "GET /secret HTTP/1.0\r\n\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 401"); #if MG_ENABLE_HTTP_STREAMING_MULTIPART fetch_http(buf, "%s", "POST /secret/upload HTTP/1.0\r\n" "Content-Type: multipart/form-data;boundary=omgwtf\r\n" "\r\n--omgwtf\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 401"); #endif /* Test digest authorization success */ mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/data/auth/a.txt?a=b", "foo.com", "joe", "doe", nonce); fetch_http(buf, "GET /data/auth/a.txt?a=b HTTP/1.0\r\n%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200"); ASSERT_STREQ(buf + strlen(buf) - 7, "\r\n\r\nhi\n"); mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/secret", "foo.com", "joe", "doe", nonce); fetch_http(buf, "GET /secret HTTP/1.0\r\n%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200"); ASSERT_STREQ(buf + strlen(buf) - 7, "Secret!"); /* Test digest authorization failure with non-existing passwords file */ mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/data/auth/a.txt?a=b", "foo.com", "joe", "doe", nonce); fetch_http2(buf, "data/auth/non-existing-passwords.txt", "GET /data/auth/a.txt?a=b HTTP/1.0\r\n%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200"); ASSERT_STREQ(buf + strlen(buf) - 7, "\r\n\r\nhi\n"); mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/secret", "foo.com", "joe", "doe", nonce); fetch_http2(buf, "data/auth/non-existing-passwords.txt", "GET /secret HTTP/1.0\r\n%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 401"); /* Test digest authorization failure with old nonce */ { /* * Add 10 seconds to the nonce value, so that it'll be in the future * and mg_check_nonce() should fail. */ unsigned long nonce_val = strtoul(nonce, NULL, 16); sprintf(nonce, "%lx", nonce_val + 10); mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/data/auth/a.txt?a=b", "foo.com", "joe", "doe", nonce); fetch_http(buf, "GET /data/auth/a.txt?a=b HTTP/1.0\r\n%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 401"); mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/secret", "foo.com", "joe", "doe", nonce); fetch_http(buf, "GET /secret HTTP/1.0\r\n%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 401"); /* Make nonce 1-hour-old, so mg_check_nonce() should fail again. */ sprintf(nonce, "%lx", nonce_val - 60 * 60); mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/data/auth/a.txt?a=b", "foo.com", "joe", "doe", nonce); fetch_http(buf, "GET /data/auth/a.txt?a=b HTTP/1.0\r\n%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 401"); /* Renew nonce to represent the actual nonce from the server */ bufstr = mg_mk_str(buf); ASSERT_GT(mg_http_parse_header(&bufstr, "nonce", nonce, sizeof(nonce)), 0); } /* Test that passwords file is hidden from view */ mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/data/auth/p%61sswords%2etxt", "foo.com", "joe", "doe", nonce); fetch_http(buf, "GET /data/auth/p%%61sswords%%2etxt HTTP/1.0\r\n" "%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 404"); /* On case-insensitive filesystems too. */ mg_http_create_digest_auth_header(auth_hdr, sizeof(auth_hdr), "GET", "/data/auth/Passwords.txt", "foo.com", "joe", "doe", nonce); fetch_http(buf, "GET /data/auth/Passwords.txt HTTP/1.0\r\n" "%s\r\n\r\n", auth_hdr); ASSERT_STREQ_NZ(buf, "HTTP/1.1 404"); return NULL; } static const char *test_http_errors(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *local_addr = "127.0.0.1:7777"; char status[1000] = ""; mg_mgr_init(&mgr, NULL); s_http_server_opts.enable_directory_listing = NULL; ASSERT((nc = mg_bind(&mgr, local_addr, cb1)) != NULL); mg_set_protocol_http_websocket(nc); #if !defined(TEST_UNDER_VIRTUALBOX) && !defined(_WIN32) /* Test file which exists but cannot be opened */ ASSERT((nc = mg_connect(&mgr, local_addr, cb8)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = status; system("touch test_unreadable; chmod 000 test_unreadable"); mg_printf(nc, "GET /%s HTTP/1.0\n\n", "../test_unreadable"); /* Run event loop. Use more cycles to let file download complete. */ poll_until(&mgr, 1, c_str_ne, status, (void *) ""); system("rm -f test_unreadable"); /* Check that it failed */ ASSERT_STREQ_NZ(status, "HTTP/1.1 403"); #endif /* Test non existing file */ ASSERT((nc = mg_connect(&mgr, local_addr, cb8)) != NULL); mg_set_protocol_http_websocket(nc); status[0] = '\0'; nc->user_data = status; mg_printf(nc, "GET /%s HTTP/1.0\n\n", "/please_dont_create_this_file_srsly"); poll_until(&mgr, 1, c_str_ne, status, (void *) ""); /* Check that it failed */ ASSERT_STREQ_NZ(status, "HTTP/1.1 404"); /* Cleanup */ mg_mgr_free(&mgr); return NULL; } static const char *test_http_index(void) { char buf[FETCH_BUF_SIZE]; fetch_http(buf, "%s", "GET /data/dir_with_index/ HTTP/1.0\r\n\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200"); ASSERT(strstr(buf, "Content-Length: 3\r\n") != 0); ASSERT_STREQ(buf + strlen(buf) - 5, "\r\nfoo"); /* Directory listing: allowed by default. */ s_http_server_opts.enable_directory_listing = NULL; fetch_http(buf, "%s", "GET /data/dir_no_index/ HTTP/1.0\r\n\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200 OK\r\n" "Server: Mongoose/" MG_VERSION "\r\n" "Transfer-Encoding: chunked\r\n"); ASSERT(strstr(buf, "") != NULL); /* Allowed explicitly. */ s_http_server_opts.enable_directory_listing = "yes"; fetch_http(buf, "%s", "GET /data/dir_no_index/ HTTP/1.0\r\n\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200"); /* Disallowed. */ s_http_server_opts.enable_directory_listing = "no"; fetch_http(buf, "%s", "GET /data/dir_no_index/ HTTP/1.0\r\n\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 403"); return NULL; } static const char *test_ssi(void) { char buf[FETCH_BUF_SIZE]; fetch_http(buf, "%s", "GET /data/ssi/ HTTP/1.0\n\n"); ASSERT_STREQ(buf, "HTTP/1.1 200 OK\r\n" "Server: Mongoose/" MG_VERSION "\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n\r\na\n\n" "<b>[ssi call: foo(bar)]" "[ssi call w/ ctx: /data/ssi/ ." DS "data" DS "ssi" DS "nested.shtml foo(bar)]" "</b>\nb\n\n\n"); return NULL; } #ifndef NO_CGI_TEST static const char *test_cgi(void) { char buf[FETCH_BUF_SIZE]; char expected_server_port[100]; const char *post_data = "aa=1234&bb=hi there"; fetch_http(buf, "POST /data/cgi/ HTTP/1.0\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\n\n%s", (int) strlen(post_data), post_data); /* fprintf(stderr, "%s", buf); */ snprintf(expected_server_port, sizeof(expected_server_port), "\nE: SERVER_PORT=%d\r\n", s_listening_port - 1); ASSERT(strstr(buf, expected_server_port) != NULL); ASSERT_STREQ_NZ(buf, "HTTP/1.1 201 Created\r\n"); ASSERT(strstr(buf, "\nE: CONTENT_LENGTH=19\r\n") != NULL); ASSERT( strstr(buf, "\nE: CONTENT_TYPE=application/x-www-form-urlencoded\r\n") != NULL); ASSERT(strstr(buf, "\nE: DOCUMENT_ROOT=.\r\n") != NULL); ASSERT(strstr(buf, "\nE: HTTPS=off\r\n") != NULL); ASSERT(strstr(buf, "\nE: HTTP_CONTENT_LENGTH=19\r\n") != NULL); ASSERT( strstr(buf, "\nE: HTTP_CONTENT_TYPE=application/x-www-form-urlencoded\r\n") != NULL); ASSERT(strstr(buf, "\nE: REMOTE_ADDR=127.0.0.1\r\n") != NULL); ASSERT(strstr(buf, "\nE: REQUEST_METHOD=POST\r\n") != NULL); ASSERT(strstr(buf, "\nE: REQUEST_URI=/data/cgi/\r\n") != NULL); ASSERT(strstr(buf, "\nE: SCRIPT_NAME=/data/cgi/index.cgi\r\n") != NULL); ASSERT(strstr(buf, "\nE: SCRIPT_FILENAME=." DS "data" DS "cgi" DS "index.cgi\r\n") != NULL); ASSERT(strstr(buf, "\nE: PATH_INFO=") == NULL); ASSERT(strstr(buf, "\nE: PATH_TRANSLATED=") == NULL); ASSERT(strstr(buf, "\nE: SERVER_SOFTWARE=Mongoose/" MG_VERSION "\r\n") != NULL); ASSERT(strstr(buf, "\nP: aa=1234\r\n") != NULL); ASSERT(strstr(buf, "\nP: bb=hi there\r\n") != NULL); fetch_http(buf, "GET /data/cgi/index%%2ecgi/foo%%2fbar/baz/?x=y HTTP/1.0\r\n\r\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 201 Created\r\n"); ASSERT(strstr(buf, "\nE: CONTENT_LENGTH=") == NULL); ASSERT(strstr(buf, "\nE: CONTENT_TYPE=") == NULL); ASSERT(strstr(buf, "\nE: DOCUMENT_ROOT=.\r\n") != NULL); ASSERT(strstr(buf, "\nE: HTTPS=off\r\n") != NULL); ASSERT(strstr(buf, "\nE: HTTP_CONTENT_LENGTH=") == NULL); ASSERT(strstr(buf, "\nE: HTTP_CONTENT_TYPE=") == NULL); ASSERT(strstr(buf, "\nE: REQUEST_METHOD=GET\r\n") != NULL); ASSERT( strstr(buf, "\nE: REQUEST_URI=/data/cgi/index%2ecgi/foo%2fbar/baz/?x=y\r\n") != NULL); ASSERT(strstr(buf, "\nE: SCRIPT_NAME=/data/cgi/index%2ecgi\r\n") != NULL); ASSERT(strstr(buf, "\nE: SCRIPT_FILENAME=." DS "data" DS "cgi" DS "index.cgi\r\n") != NULL); ASSERT(strstr(buf, "\nE: PATH_INFO=/foo%2fbar/baz/") != NULL); ASSERT(strstr(buf, "\nE: PATH_TRANSLATED=/foo%2fbar/baz/") != NULL); ASSERT(strstr(buf, "\nE: SERVER_SOFTWARE=Mongoose/" MG_VERSION "\r\n") != NULL); ASSERT(strstr(buf, "\nQ: x=y\r\n") != NULL); return NULL; } #endif static const char *test_http_rewrites(void) { char buf[FETCH_BUF_SIZE]; s_http_server_opts.url_rewrites = "/~joe=./data/rewrites," "@foo.com=./data/rewrites/foo.com"; /* Test rewrite */ fetch_http(buf, "%s", "GET /~joe/msg.txt HTTP/1.0\nHost: foo.co\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200 OK"); ASSERT(strstr(buf, "Content-Length: 6\r\n") != 0); ASSERT_STREQ(buf + strlen(buf) - 8, "\r\nworks\n"); /* Test rewrite that points to directory, expect redirect */ fetch_http(buf, "%s", "GET /~joe HTTP/1.0\n\n"); ASSERT_STREQ(buf, "HTTP/1.1 301 Moved\r\nLocation: /~joe/\r\n" "Content-Length: 0\r\n\r\n"); /* Test domain-based rewrite */ fetch_http(buf, "%s", "GET / HTTP/1.0\nHost: foo.com\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200 OK"); ASSERT(strstr(buf, "Content-Length: 9\r\n") != 0); ASSERT_STREQ(buf + strlen(buf) - 11, "\r\nfoo_root\n"); /* Test port-based rewrites */ s_listening_port = 25555; s_http_server_opts.url_rewrites = "%25555=https://foo"; fetch_http(buf, "%s", "GET / HTTP/1.0\n\n"); ASSERT_STREQ(buf, "HTTP/1.1 301 Moved\r\n" "Server: Mongoose/" MG_VERSION "\r\n" "Content-Length: 0\r\n" "Location: https://foo/\r\n\r\n"); return NULL; } static const char *test_http_dav(void) { char buf[FETCH_BUF_SIZE]; cs_stat_t st; remove("./data/dav/b.txt"); rmdir("./data/dav/d"); /* Test PROPFIND */ s_http_server_opts.enable_directory_listing = "yes"; fetch_http(buf, "%s", "PROPFIND / HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 207"); ASSERT(strstr(buf, "a.txt") != NULL); ASSERT(strstr(buf, "hidden_file.txt") == NULL); /* Test MKCOL */ fetch_http(buf, "%s", "MKCOL /d HTTP/1.0\nContent-Length:5\n\n12345"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 415"); fetch_http(buf, "%s", "MKCOL /d HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 201"); fetch_http(buf, "%s", "MKCOL /d HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 405"); fetch_http(buf, "%s", "MKCOL /x/d HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 409"); /* Test PUT */ fetch_http(buf, "%s", "PUT /b.txt HTTP/1.0\nContent-Length: 5\n\n12345"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 201"); fetch_http(buf, "%s", "GET /data/dav/b.txt HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200"); ASSERT(strstr(buf, "Content-Length: 5\r\n") != 0); ASSERT_STREQ(buf + strlen(buf) - 7, "\r\n12345"); /* Test DELETE */ fetch_http(buf, "%s", "DELETE /b.txt HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 204"); ASSERT(mg_stat("./data/dav/b.txt", &st) != 0); fetch_http(buf, "%s", "DELETE /d HTTP/1.0\n\n"); ASSERT(mg_stat("./data/dav/d", &st) != 0); /* Non-existing file */ fetch_http(buf, "%s", "PROPFIND /__blah.txt HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 404"); return NULL; } static const char *test_http_range(void) { char buf[FETCH_BUF_SIZE]; fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 200 OK"); ASSERT(strstr(buf, "Content-Length: 312\r\n") != 0); /* Fetch a piece from the middle of the file */ fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\nRange: bytes=5-10\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 206 Partial Content"); ASSERT(strstr(buf, "Content-Length: 6\r\n") != 0); ASSERT(strstr(buf, "Content-Range: bytes 5-10/312\r\n") != 0); ASSERT_STREQ(buf + strlen(buf) - 8, "\r\n of co"); /* Fetch till EOF */ fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\nRange: bytes=300-\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 206 Partial Content"); ASSERT(strstr(buf, "Content-Length: 12\r\n") != 0); ASSERT(strstr(buf, "Content-Range: bytes 300-311/312\r\n") != 0); ASSERT_STREQ(buf + strlen(buf) - 14, "\r\nis disease.\n"); /* Fetch past EOF, must trigger 416 response */ fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\nRange: bytes=1000-\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 416"); ASSERT(strstr(buf, "Content-Length: 0\r\n") != 0); ASSERT(strstr(buf, "Content-Range: bytes */312\r\n") != 0); /* Request range past EOF, must trigger 416 response */ fetch_http(buf, "%s", "GET /data/range.txt HTTP/1.0\nRange: bytes=0-312\n\n"); ASSERT_STREQ_NZ(buf, "HTTP/1.1 416"); return NULL; } static void cb3(struct mg_connection *nc, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; if (ev == MG_EV_WEBSOCKET_FRAME) { const char *reply = wm->size == 2 && !memcmp(wm->data, "hi", 2) ? "A" : "B"; mg_printf_websocket_frame(nc, WEBSOCKET_OP_TEXT, "%s", reply); } } static void cb4(struct mg_connection *nc, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; if (ev == MG_EV_WEBSOCKET_FRAME) { memcpy(nc->user_data, wm->data, wm->size); mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, NULL, 0); } else if (ev == MG_EV_WEBSOCKET_HANDSHAKE_DONE) { /* Send "hi" to server. server must reply "A". */ struct mg_str h[2]; h[0].p = "h"; h[0].len = 1; h[1].p = "i"; h[1].len = 1; mg_send_websocket_framev(nc, WEBSOCKET_OP_TEXT, h, 2); } } static void cb_ws_server(struct mg_connection *nc, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; if (ev == MG_EV_WEBSOCKET_FRAME || ev == MG_EV_WEBSOCKET_CONTROL_FRAME) { mg_printf_websocket_frame( nc, WEBSOCKET_OP_TEXT, "%s%.2x:{%.*s}", (ev == MG_EV_WEBSOCKET_CONTROL_FRAME ? "CONTROL:" : ""), wm->flags, (int) wm->size, wm->data); } } static int s_ws_client1_connected = false; static void cb_ws_client1(struct mg_connection *nc, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; if (ev == MG_EV_WEBSOCKET_FRAME || ev == MG_EV_WEBSOCKET_CONTROL_FRAME) { char *tmp = NULL; int tmplen; tmplen = mg_asprintf(&tmp, 0, "%s%.2x:[%.*s]", (ev == MG_EV_WEBSOCKET_CONTROL_FRAME ? "CONTROL:" : ""), wm->flags, (int) wm->size, wm->data); mbuf_append((struct mbuf *) nc->user_data, tmp, tmplen); free(tmp); } else if (ev == MG_EV_CLOSE) { s_ws_client1_connected = 0; } else if (ev == MG_EV_WEBSOCKET_HANDSHAKE_DONE) { s_ws_client1_connected = 1; } } static const char *test_websocket(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *local_addr = "127.0.0.1:7778"; struct mbuf mb; mbuf_init(&mb, 100); mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ ASSERT((nc = mg_bind(&mgr, local_addr, cb_ws_server)) != NULL); mg_set_protocol_http_websocket(nc); /* * Websocket request "hi" via mg_send_websocket_framev() */ mb.len = 0; s_ws_client1_connected = 0; ASSERT((nc = mg_connect(&mgr, local_addr, cb_ws_client1)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = &mb; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 1); { /* Send "hi" to server. server must reply "A". */ struct mg_str h[2]; h[0].p = "h"; h[0].len = 1; h[1].p = "i"; h[1].len = 1; mg_send_websocket_framev(nc, WEBSOCKET_OP_TEXT, h, 2); } poll_until(&mgr, 1, c_int_ne, &mb.len, (void *) 0); mbuf_append(&mb, "\0", 1); /* Check that test buffer has been filled by the callback properly. */ ASSERT_STREQ(mb.buf, "81:[81:{hi}]"); /* * Websocket request "hallloo", composed from two fragments */ mb.len = 0; s_ws_client1_connected = 0; ASSERT((nc = mg_connect(&mgr, local_addr, cb_ws_client1)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = &mb; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 1); ASSERT_EQ(s_ws_client1_connected, 1); mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT | WEBSOCKET_DONT_FIN, "hall", 4); mg_send_websocket_frame(nc, WEBSOCKET_OP_CONTINUE | WEBSOCKET_DONT_FIN, "--", 2); /* Poll 0.5 seconds, we don't expect c_str_ne predicate to return true */ poll_until(&mgr, 0.5, c_int_ne, &mb.len, (void *) 0); ASSERT_EQ(mb.len, 0); mg_send_websocket_frame(nc, WEBSOCKET_OP_CONTINUE, "loo", 3); poll_until(&mgr, 1, c_int_ne, &mb.len, (void *) 0); mbuf_append(&mb, "\0", 1); /* Check that test buffer has been filled by the callback properly. */ ASSERT_STREQ(mb.buf, "81:[81:{hall--loo}]"); /* * Ping request */ mb.len = 0; s_ws_client1_connected = 0; ASSERT((nc = mg_connect(&mgr, local_addr, cb_ws_client1)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = &mb; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 1); ASSERT_EQ(s_ws_client1_connected, 1); mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, "myping", 6); { /* * As a response to Ping, we should first receive Pong (0x8a), * and then text message (0x81) with our Ping (0x89) echo. */ const char *expected = "CONTROL:8a:[myping]81:[CONTROL:89:{myping}]"; poll_until(&mgr, 1, c_int_eq, &mb.len, (void *) strlen(expected)); mbuf_append(&mb, "\0", 1); ASSERT_STREQ(mb.buf, expected); } /* * Ping request injected in between of a fragmented message */ mb.len = 0; s_ws_client1_connected = 0; ASSERT((nc = mg_connect(&mgr, local_addr, cb_ws_client1)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = &mb; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 1); ASSERT_EQ(s_ws_client1_connected, 1); mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT | WEBSOCKET_DONT_FIN, "abc", 3); mg_send_websocket_frame(nc, WEBSOCKET_OP_CONTINUE | WEBSOCKET_DONT_FIN, "def", 3); mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, "0123", 4); mg_send_websocket_frame(nc, WEBSOCKET_OP_CONTINUE, "ghi", 3); { /* * As a response to Ping, we should first receive Pong (0x8a), * and then text message (0x81) with our Ping (0x89) echo. * And then, our fragmented text message: abcdefghi. */ const char *expected = "CONTROL:8a:[0123]81:[CONTROL:89:{0123}]81:[81:{abcdefghi}]"; poll_until(&mgr, 1, c_int_eq, &mb.len, (void *) strlen(expected)); mbuf_append(&mb, "\0", 1); ASSERT_STREQ(mb.buf, expected); } /* * Test illegal text frame in the middle of a fragmented message */ mb.len = 0; s_ws_client1_connected = 0; ASSERT((nc = mg_connect(&mgr, local_addr, cb_ws_client1)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = &mb; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 1); ASSERT_EQ(s_ws_client1_connected, 1); /* Send a few parts of a fragmented message */ mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT | WEBSOCKET_DONT_FIN, "abc", 3); mg_send_websocket_frame(nc, WEBSOCKET_OP_CONTINUE | WEBSOCKET_DONT_FIN, "def", 3); /* Send (illegal) text frame in the middle of a fragmented message */ mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, "ghi", 3); /* Wait until connection is closed by the server */ poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 0); ASSERT_EQ(s_ws_client1_connected, 0); /* Verify the error message */ mbuf_append(&mb, "\0", 1); ASSERT_STREQ( mb.buf, "CONTROL:88:[non-continuation in the middle of a fragmented message]"); /* * Test closing by the client */ mb.len = 0; s_ws_client1_connected = 0; ASSERT((nc = mg_connect(&mgr, local_addr, cb_ws_client1)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = &mb; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 1); ASSERT_EQ(s_ws_client1_connected, 1); /* Send a few parts of a fragmented message */ mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, "bye", 3); /* Wait until connection is closed by the server */ poll_until(&mgr, 1, c_int_ne, &s_ws_client1_connected, (void *) 0); ASSERT_EQ(s_ws_client1_connected, 0); /* * TODO(dfrank): mongoose closes the connection automatically when * mg_send_websocket_frame() is called with a WEBSOCKET_OP_CLOSE op, so we * can't hear anything from the server. Server is actually obliged to send * the control frame in response, but we can't test it here. */ mg_mgr_free(&mgr); mbuf_free(&mb); return NULL; } static void cbwep(struct mg_connection *c, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; char *buf = (char *) c->user_data; switch (ev) { case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: strcat(buf, "R"); break; case MG_EV_WEBSOCKET_HANDSHAKE_DONE: strcat(buf, "D"); break; case MG_EV_WEBSOCKET_FRAME: strcat(buf, "F"); mg_printf_websocket_frame(c, WEBSOCKET_OP_TEXT, "%s|%.*s", buf, (int) wm->size, wm->data); break; } } static const char *test_websocket_endpoint(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *local_addr = "127.0.0.1:7798"; char buf[20] = "", buf2[20] = ""; mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ ASSERT((nc = mg_bind(&mgr, local_addr, cb3)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = buf2; mg_register_http_endpoint(nc, "/boo", cbwep MG_UD_ARG(NULL)); /* Websocket request */ ASSERT((nc = mg_connect(&mgr, local_addr, cb4)) != NULL); mg_set_protocol_http_websocket(nc); nc->user_data = buf; mg_send_websocket_handshake(nc, "/boo", NULL); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); mg_mgr_free(&mgr); /* Check that test buffer has been filled by the callback properly. */ ASSERT_STREQ(buf, "RDF|hi"); return NULL; } static const char *test_connect_ws(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *urls[] = {"ws://127.0.0.1:7778/ws", "http://127.0.0.1:7778/ws", "127.0.0.1:7778/ws", NULL}; const char **url; mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ ASSERT((nc = mg_bind(&mgr, "127.0.0.1:7778", cb3)) != NULL); mg_set_protocol_http_websocket(nc); for (url = urls; *url != NULL; url++) { char buf[20] = ""; ASSERT((nc = mg_connect_ws(&mgr, cb4, "ws://127.0.0.1:7778/ws", NULL, NULL)) != NULL); nc->user_data = buf; poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); ASSERT_STREQ(buf, "A"); } mg_mgr_free(&mgr); return NULL; } struct big_payload_params { size_t size; char *buf; }; static void cb3_big(struct mg_connection *nc, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; if (ev == MG_EV_WEBSOCKET_FRAME) { int success = 1; size_t i; for (i = 0; i < wm->size; i++) { if (wm->data[i] != 'x') { success = 0; break; } } mg_printf_websocket_frame(nc, WEBSOCKET_OP_TEXT, "%s", success ? "success" : "fail"); } } static void cb4_big(struct mg_connection *nc, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; struct big_payload_params *params = (struct big_payload_params *) nc->user_data; if (ev == MG_EV_WEBSOCKET_FRAME) { memcpy(params->buf, wm->data, wm->size); mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, NULL, 0); } else if (ev == MG_EV_WEBSOCKET_HANDSHAKE_DONE) { /* Send large payload to server. server must reply "success". */ char *payload = (char *) malloc(params->size); memset(payload, 'x', params->size); mg_printf_websocket_frame(nc, WEBSOCKET_OP_TEXT, "%.*s", params->size, payload); free(payload); } } /* Big payloads follow a different code path because it will use the extended * length field and possibly mg_avprintf will need to reallocate the buffer. */ static const char *test_websocket_big(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *local_addr = "127.0.0.1:7778"; char buf[20] = ""; struct big_payload_params params; params.buf = buf; mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ ASSERT((nc = mg_bind(&mgr, local_addr, cb3_big)) != NULL); mg_set_protocol_http_websocket(nc); /* Websocket request */ ASSERT((nc = mg_connect(&mgr, local_addr, cb4_big)) != NULL); mg_set_protocol_http_websocket(nc); params.size = 8192; nc->user_data = ¶ms; params.buf[0] = '\0'; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_str_ne, params.buf, (void *) ""); /* Check that test buffer has been filled by the callback properly. */ ASSERT_STREQ(buf, "success"); /* Websocket request */ ASSERT((nc = mg_connect(&mgr, local_addr, cb4_big)) != NULL); mg_set_protocol_http_websocket(nc); params.size = 65535; nc->user_data = ¶ms; params.buf[0] = '\0'; mg_send_websocket_handshake(nc, "/ws", NULL); poll_until(&mgr, 1, c_str_ne, params.buf, (void *) ""); mg_mgr_free(&mgr); /* Check that test buffer has been filled by the callback properly. */ ASSERT_STREQ(buf, "success"); return NULL; } static const char *test_mqtt_handshake(void) { struct mg_send_mqtt_handshake_opts opts; struct mg_connection *nc = create_test_connection(); const char *client_id = "testclient"; const char *user_name = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; const char *password = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; const char *will_topic = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"; const char *will_message = "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"; double before = mg_time(); const char *got; msleep(2); mg_set_protocol_mqtt(nc); mg_send_mqtt_handshake(nc, client_id); got = nc->send_mbuf.buf; /* handshake header + keepalive + client id len + client id */ ASSERT_EQ(nc->send_mbuf.len, 1 + 1 + 1 + 5 + 1 + 1 + 2 + 2 + strlen(client_id)); ASSERT_EQ(got[0], 0x10); ASSERT_EQ(got[1], (int) nc->send_mbuf.len - 2); ASSERT_EQ(got[2], 0); ASSERT_EQ(got[3], 4); ASSERT_STREQ_NZ(&got[4], "MQTT"); ASSERT_EQ(got[8], 4); ASSERT_EQ(got[9], 0); /* connect flags */ ASSERT_EQ(got[10], 0); ASSERT_EQ(got[11], 60); ASSERT_EQ(got[12], 0); ASSERT_EQ(got[13], (char) strlen(client_id)); ASSERT_EQ(strncmp(&got[14], client_id, strlen(client_id)), 0); ASSERT_EQ(((struct mg_mqtt_proto_data *) nc->proto_data)->keep_alive, 60); ASSERT_GT(((struct mg_mqtt_proto_data *) nc->proto_data)->last_control_time, before); mbuf_remove(&nc->send_mbuf, nc->send_mbuf.len); memset(&opts, 0, sizeof(opts)); before = mg_time(); msleep(2); opts.will_topic = will_topic; opts.will_message = will_message; opts.user_name = user_name; opts.password = password; opts.keep_alive = 0x1234; mg_send_mqtt_handshake_opt(nc, client_id, opts); got = nc->send_mbuf.buf; /* handshake header + keepalive + client id len + client id */ ASSERT_EQ(nc->send_mbuf.len, 1 + 2 + 1 + 5 + 1 + 1 + 2 + 2 + strlen(client_id) + 2 + strlen(will_topic) + 2 + strlen(will_message) + 2 + strlen(user_name) + 2 + strlen(password)); ASSERT_EQ(got[0], 0x10); ASSERT_EQ(((unsigned char *) got)[1], 223); ASSERT_EQ(got[2], 1); ASSERT_EQ(got[3], 0); ASSERT_EQ(got[4], 4); ASSERT_STREQ_NZ(&got[5], "MQTT"); ASSERT_EQ(got[9], 4); ASSERT_EQ(((unsigned char *) got)[10], 0xc4); /* connect flags */ ASSERT_EQ(got[11], 0x12); ASSERT_EQ(got[12], 0x34); ASSERT_EQ(got[13], 0); ASSERT_EQ(got[14], (char) strlen(client_id)); ASSERT_EQ(strncmp(&got[15], client_id, strlen(client_id)), 0); ASSERT_EQ(((struct mg_mqtt_proto_data *) nc->proto_data)->keep_alive, 0x1234); ASSERT_GT(((struct mg_mqtt_proto_data *) nc->proto_data)->last_control_time, before); mbuf_remove(&nc->send_mbuf, nc->send_mbuf.len); destroy_test_connection(nc); return NULL; } static const char *test_mqtt_publish(void) { struct mg_connection *nc = create_test_connection(); const double before = mg_time(); char data[] = "dummy"; const char *got; mg_set_protocol_mqtt(nc); msleep(2); mg_mqtt_publish(nc, "/test", 42, MG_MQTT_QOS(1) | MG_MQTT_RETAIN, data, sizeof(data)); got = nc->send_mbuf.buf; ASSERT_EQ(nc->send_mbuf.len, 17); ASSERT(got[0] & MG_MQTT_RETAIN); ASSERT_EQ((got[0] & 0xf0), (MG_MQTT_CMD_PUBLISH << 4)); ASSERT_EQ(MG_MQTT_GET_QOS(got[0]), 1); ASSERT_EQ((size_t) got[1], (nc->send_mbuf.len - 2)); ASSERT_EQ(got[2], 0); ASSERT_EQ(got[3], 5); ASSERT_STREQ_NZ(&got[4], "/test"); ASSERT_EQ(got[9], 0); ASSERT_EQ(got[10], 42); ASSERT_EQ(strncmp(&got[11], data, sizeof(data)), 0); ASSERT_GT(((struct mg_mqtt_proto_data *) nc->proto_data)->last_control_time, before); destroy_test_connection(nc); return NULL; } static const char *test_mqtt_subscribe(void) { struct mg_connection *nc = create_test_connection(); const double before = mg_time(); const int qos = 1; const char *got; struct mg_mqtt_topic_expression topic_expressions[] = {{"/stuff", qos}}; mg_set_protocol_mqtt(nc); msleep(2); mg_mqtt_subscribe(nc, topic_expressions, 1, 42); got = nc->send_mbuf.buf; ASSERT_EQ(nc->send_mbuf.len, 13); ASSERT_EQ((got[0] & 0xf0), (MG_MQTT_CMD_SUBSCRIBE << 4)); ASSERT_EQ((size_t) got[1], (nc->send_mbuf.len - 2)); ASSERT_EQ(got[2], 0); ASSERT_EQ(got[3], 42); ASSERT_EQ(got[4], 0); ASSERT_EQ(got[5], 6); ASSERT_STREQ_NZ(&got[6], "/stuff"); ASSERT_EQ(got[12], qos); ASSERT_GT(((struct mg_mqtt_proto_data *) nc->proto_data)->last_control_time, before); destroy_test_connection(nc); return NULL; } static const char *test_mqtt_unsubscribe(void) { struct mg_connection *nc = create_test_connection(); char *topics[] = {(char *) "/stuff"}; const double before = mg_time(); const char *got; mg_set_protocol_mqtt(nc); msleep(2); mg_mqtt_unsubscribe(nc, topics, 1, 42); got = nc->send_mbuf.buf; ASSERT_EQ(nc->send_mbuf.len, 12); ASSERT_EQ((got[0] & 0xf0), (MG_MQTT_CMD_UNSUBSCRIBE << 4)); ASSERT_EQ((size_t) got[1], (nc->send_mbuf.len - 2)); ASSERT_EQ(got[2], 0); ASSERT_EQ(got[3], 42); ASSERT_EQ(got[4], 0); ASSERT_EQ(got[5], 6); ASSERT_STREQ_NZ(&got[6], "/stuff"); ASSERT_GT(((struct mg_mqtt_proto_data *) nc->proto_data)->last_control_time, before); destroy_test_connection(nc); return NULL; } static const char *test_mqtt_connack(void) { struct mg_connection *nc = create_test_connection(); const char *got; mg_set_protocol_mqtt(nc); mg_mqtt_connack(nc, 42); got = nc->send_mbuf.buf; ASSERT(nc->send_mbuf.len > 0); ASSERT_EQ((got[0] & 0xf0), (MG_MQTT_CMD_CONNACK << 4)); ASSERT_EQ((size_t) got[1], (nc->send_mbuf.len - 2)); ASSERT_EQ(got[3], 42); destroy_test_connection(nc); return NULL; } static const char *test_mqtt_suback(void) { struct mg_connection *nc = create_test_connection(); uint8_t qoss[] = {1}; const char *got; mg_set_protocol_mqtt(nc); mg_mqtt_suback(nc, qoss, 1, 42); got = nc->send_mbuf.buf; ASSERT_EQ(nc->send_mbuf.len, 5); ASSERT_EQ((got[0] & 0xf0), (MG_MQTT_CMD_SUBACK << 4)); ASSERT_EQ(MG_MQTT_GET_QOS(got[0]), 1); ASSERT_EQ((size_t) got[1], (nc->send_mbuf.len - 2)); ASSERT_EQ(got[2], 0); ASSERT_EQ(got[3], 42); ASSERT_EQ(got[4], 1); destroy_test_connection(nc); return NULL; } static const char *test_mqtt_simple_acks(void) { unsigned long i; struct { uint8_t cmd; void (*f)(struct mg_connection *, uint16_t); } cases[] = { {MG_MQTT_CMD_PUBACK, mg_mqtt_puback}, {MG_MQTT_CMD_PUBREC, mg_mqtt_pubrec}, {MG_MQTT_CMD_PUBREL, mg_mqtt_pubrel}, {MG_MQTT_CMD_PUBCOMP, mg_mqtt_pubcomp}, {MG_MQTT_CMD_UNSUBACK, mg_mqtt_unsuback}, }; for (i = 0; i < ARRAY_SIZE(cases); i++) { struct mg_connection *nc = create_test_connection(); const double before = mg_time(); const char *got; mg_set_protocol_mqtt(nc); msleep(2); cases[i].f(nc, 42); got = nc->send_mbuf.buf; ASSERT_EQ(nc->send_mbuf.len, 4); ASSERT_EQ((got[0] & 0xf0), (cases[i].cmd << 4)); if (cases[i].cmd == MG_MQTT_CMD_PUBREL) { ASSERT_EQ(got[0] & 0xf, 2); } else { ASSERT_EQ(got[0] & 0xf, 0); } ASSERT_EQ((size_t) got[1], (nc->send_mbuf.len - 2)); ASSERT_EQ(got[2], 0); ASSERT_EQ(got[3], 42); ASSERT_GT(((struct mg_mqtt_proto_data *) nc->proto_data)->last_control_time, before); destroy_test_connection(nc); } return NULL; } static const char *test_mqtt_nullary(void) { unsigned long i; struct { uint8_t cmd; void (*f)(struct mg_connection *); } cases[] = { {MG_MQTT_CMD_PINGREQ, mg_mqtt_ping}, {MG_MQTT_CMD_PINGRESP, mg_mqtt_pong}, {MG_MQTT_CMD_DISCONNECT, mg_mqtt_disconnect}, }; for (i = 0; i < ARRAY_SIZE(cases); i++) { struct mg_connection *nc = create_test_connection(); const double before = mg_time(); struct mg_mqtt_message msg; const char *got; mg_set_protocol_mqtt(nc); msleep(2); cases[i].f(nc); got = nc->send_mbuf.buf; ASSERT_EQ(nc->send_mbuf.len, 2); ASSERT_EQ((got[0] & 0xf0), (cases[i].cmd << 4)); ASSERT_EQ((size_t) got[1], (nc->send_mbuf.len - 2)); ASSERT_EQ(parse_mqtt(&nc->send_mbuf, &msg), (int) nc->send_mbuf.len); ASSERT_GT(((struct mg_mqtt_proto_data *) nc->proto_data)->last_control_time, before); destroy_test_connection(nc); } return NULL; } static const size_t mqtt_long_payload_len = 200; static const size_t mqtt_very_long_payload_len = 20000; static void mqtt_eh(struct mg_connection *nc, int ev, void *ev_data) { int *check = (int *) nc->user_data; struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; size_t i; (void) nc; (void) ev_data; switch (ev) { case MG_EV_MQTT_SUBACK: *check = 1; break; case MG_EV_MQTT_PUBLISH: *check = 0; if (mg_vcmp(&mm->topic, "/topic")) { *check = -1; break; } for (i = 0; i < mm->payload.len; i++) { if (nc->recv_mbuf.buf[10 + i] != 'A') { *check = -1; break; } } if (mm->payload.len == mqtt_long_payload_len) { *check = 2; } else if (mm->payload.len == mqtt_very_long_payload_len) { *check = 3; } break; case MG_EV_MQTT_CONNACK: *check = 4; break; } } static const char *test_mqtt_parse_mqtt(void) { struct mg_connection *nc = create_test_connection(); char msg[] = {(char) (MG_MQTT_CMD_SUBACK << 4), 2, 1, 1}; char *long_msg; int check = 0; int num_bytes = sizeof(msg), rest_len, i; nc->user_data = ✓ nc->handler = mqtt_eh; mg_set_protocol_mqtt(nc); mbuf_append(&nc->recv_mbuf, msg, num_bytes); nc->proto_handler(nc, MG_EV_RECV, &num_bytes); ASSERT_EQ(check, 1); mbuf_free(&nc->recv_mbuf); /* test a payload whose length encodes as two bytes */ rest_len = 2 + 6 + mqtt_long_payload_len; long_msg = (char *) malloc(512); long_msg[0] = (char) (MG_MQTT_CMD_PUBLISH << 4); long_msg[1] = (rest_len & 0x7f) | 0x80; long_msg[2] = rest_len >> 7; memcpy(&long_msg[3], "\0\006/topic", 8); memset(&long_msg[11], 'A', mqtt_long_payload_len); num_bytes = 3 + rest_len; mbuf_append(&nc->recv_mbuf, long_msg, num_bytes); /* Short read: handler is not run and data is not consumed */ for (i = 0; i < num_bytes; i++) { check = 123; nc->recv_mbuf.len = i; nc->proto_handler(nc, MG_EV_RECV, &i); ASSERT_EQ((int) nc->recv_mbuf.len, i); ASSERT_EQ(check, 123); } nc->recv_mbuf.len = num_bytes; nc->proto_handler(nc, MG_EV_RECV, &num_bytes); ASSERT_EQ(check, 2); mbuf_free(&nc->recv_mbuf); free(long_msg); /* test a payload whose length encodes as two bytes */ rest_len = 8 + mqtt_very_long_payload_len; long_msg = (char *) malloc(20100); long_msg[0] = (char) (MG_MQTT_CMD_PUBLISH << 4); long_msg[1] = (rest_len & 127) | 0x80; long_msg[2] = ((rest_len >> 7) & 127) | 0x80; long_msg[3] = (rest_len >> 14); memcpy(&long_msg[4], "\0\006/topic", 8); memset(&long_msg[12], 'A', mqtt_very_long_payload_len); num_bytes = 4 + rest_len; mbuf_append(&nc->recv_mbuf, long_msg, num_bytes); nc->proto_handler(nc, MG_EV_RECV, &num_bytes); ASSERT_EQ(check, 3); mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len); /* Message length encodings > 4 bytes are not allowed by the standard, * connections should be closed immediately. */ long_msg[0] = (char) (MG_MQTT_CMD_PUBLISH << 4); long_msg[1] = 0xff; long_msg[2] = 0xff; long_msg[3] = 0xff; long_msg[4] = 0xff; long_msg[5] = 0xff; mbuf_append(&nc->recv_mbuf, long_msg, 10); nc->proto_handler(nc, MG_EV_RECV, &num_bytes); ASSERT(nc->flags & MG_F_CLOSE_IMMEDIATELY); mbuf_free(&nc->recv_mbuf); free(long_msg); /* test encoding a large payload */ long_msg = (char *) malloc(mqtt_very_long_payload_len); memset(long_msg, 'A', mqtt_very_long_payload_len); mg_mqtt_publish(nc, "/topic", 0, 0, long_msg, mqtt_very_long_payload_len); nc->recv_mbuf = nc->send_mbuf; mbuf_init(&nc->send_mbuf, 0); num_bytes = nc->recv_mbuf.len; nc->proto_handler(nc, MG_EV_RECV, &num_bytes); ASSERT_EQ(check, 3); mbuf_free(&nc->recv_mbuf); free(long_msg); /* test connack parsing */ mg_mqtt_connack(nc, 0); nc->recv_mbuf = nc->send_mbuf; mbuf_init(&nc->send_mbuf, 0); num_bytes = 4; nc->proto_handler(nc, MG_EV_RECV, &num_bytes); ASSERT_EQ(check, 4); mbuf_free(&nc->recv_mbuf); destroy_test_connection(nc); return NULL; } static const char *test_mqtt_parse_mqtt_qos1(void) { /* clang-format off */ struct mg_mqtt_message mm; char msg_qos1[] = { ((MG_MQTT_CMD_PUBLISH << 4) | (1 << 1)), 2 + 6 + 2 + 7, 0, 6, '/', 't', 'o', 'p', 'i', 'c', 0x12, 0x34, 'p', 'a', 'y', 'l', 'o', 'a', 'd', }; /* clang-format on */ struct mbuf mb; memset(&mm, 0, sizeof(mm)); mbuf_init(&mb, 0); mbuf_append(&mb, msg_qos1, sizeof(msg_qos1)); ASSERT_EQ(parse_mqtt(&mb, &mm), (int) sizeof(msg_qos1)); ASSERT_EQ(mm.cmd, MG_MQTT_CMD_PUBLISH); ASSERT_EQ(mm.qos, 1); ASSERT_EQ(mm.message_id, 0x1234); ASSERT_EQ(mm.topic.len, 6); ASSERT_STREQ_NZ(mm.topic.p, "/topic"); ASSERT_EQ(mm.payload.len, 7); ASSERT_STREQ_NZ(mm.payload.p, "payload"); mbuf_free(&mb); return NULL; } static const char *test_mqtt_match_topic_expression(void) { ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo")), 1); ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/")), 0); ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/bar")), 0); ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo")), 0); ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/")), 0); ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar")), 1); ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar/baz")), 1); return NULL; } #if MG_ENABLE_MQTT_BROKER struct mg_mqtt_topic_expression brk_test_te[] = {{"/dummy", 0}, {"/unit/#", 0}}; static void brk_cln_cb1(struct mg_connection *nc, int ev, void *p) { struct mg_mqtt_message *msg = (struct mg_mqtt_message *) p; switch (ev) { case MG_EV_CONNECT: mg_set_protocol_mqtt(nc); mg_send_mqtt_handshake(nc, "dummy"); break; case MG_EV_MQTT_CONNACK: mg_mqtt_subscribe(nc, brk_test_te, ARRAY_SIZE(brk_test_te), 42); break; case MG_EV_MQTT_SUBACK: mg_mqtt_publish(nc, "/unit/test", 0, MG_MQTT_QOS(0), "payload", 7); break; case MG_EV_MQTT_PUBLISH: if (mg_vcmp(&msg->topic, "/unit/test") == 0 && msg->payload.len == 7 && mg_vcmp(&msg->payload, "payload") == 0) { *(int *) nc->user_data = 1; } break; } } static void mqtt_dummy_eh(struct mg_connection *nc, int ev, void *ev_data) { (void) nc; (void) ev; (void) ev_data; } static const char *test_mqtt_client_keep_alive(void) { struct mg_send_mqtt_handshake_opts opts; struct mg_connection *nc = create_test_connection(); struct mg_mqtt_proto_data *pd; double before = mg_time(); const char *got; mg_set_protocol_mqtt(nc); pd = (struct mg_mqtt_proto_data *) nc->proto_data; nc->handler = mqtt_dummy_eh; memset(&opts, 0, sizeof(opts)); before = mg_time(); msleep(2); opts.keep_alive = 2; mg_send_mqtt_handshake_opt(nc, "testclient", opts); ASSERT_EQ(pd->keep_alive, 2); ASSERT_GT(pd->last_control_time, before); mbuf_remove(&nc->send_mbuf, nc->send_mbuf.len); /* Remove the CONNECT. */ before = pd->last_control_time; msleep(2); nc->proto_handler(nc, MG_EV_POLL, NULL); ASSERT_EQ(nc->send_mbuf.len, 0); ASSERT_EQ(pd->last_control_time, before); sleep(1); nc->proto_handler(nc, MG_EV_POLL, NULL); ASSERT_EQ(nc->send_mbuf.len, 0); /* Not yet */ ASSERT_EQ(pd->last_control_time, before); sleep(1); msleep(2); nc->proto_handler(nc, MG_EV_POLL, NULL); ASSERT_GT(nc->send_mbuf.len, 0); /* PINGREQ sent */ got = nc->send_mbuf.buf; ASSERT_EQ(nc->send_mbuf.len, 2); ASSERT_EQ((got[0] & 0xf0), (MG_MQTT_CMD_PINGREQ << 4)); ASSERT_GT(pd->last_control_time, before); /* Resets itself */ destroy_test_connection(nc); return NULL; } static const char *test_mqtt_broker(void) { struct mg_mgr mgr; struct mg_mqtt_broker brk; struct mg_connection *brk_nc; struct mg_connection *cln_nc; const char *brk_local_addr = "127.0.0.1:7777"; int brk_data = 0, cln_data = 0; mg_mgr_init(&mgr, NULL); mg_mqtt_broker_init(&brk, &brk_data); ASSERT((brk_nc = mg_bind(&mgr, brk_local_addr, mg_mqtt_broker)) != NULL); brk_nc->priv_2 = &brk; ASSERT((cln_nc = mg_connect(&mgr, brk_local_addr, brk_cln_cb1)) != NULL); cln_nc->user_data = &cln_data; /* Run event loop. Use more cycles to let client and broker communicate. */ poll_until(&mgr, 1, c_int_eq, &cln_data, (void *) 1); ASSERT_EQ(cln_data, 1); mg_mgr_free(&mgr); return NULL; } #endif /* MG_ENABLE_MQTT_BROKER */ static void cb5(struct mg_connection *nc, int ev, void *ev_data) { switch (ev) { case MG_EV_CONNECT: sprintf((char *) nc->user_data, "%d", *(int *) ev_data); break; default: break; } } static const char *test_connect_fail(void) { struct mg_mgr mgr; struct mg_connection *nc; char buf[100] = "0"; mg_mgr_init(&mgr, NULL); ASSERT((nc = mg_connect(&mgr, "127.0.0.1:33211", cb5)) != NULL); nc->user_data = buf; poll_until(&mgr, 1, c_str_ne, buf, (void *) "0"); mg_mgr_free(&mgr); /* printf("failed connect status: [%s]\n", buf); */ /* TODO(lsm): fix this for Win32 */ #ifndef _WIN32 ASSERT(strcmp(buf, "0") != 0); #endif return NULL; } static void cb6(struct mg_connection *nc, int ev, void *ev_data) { (void) ev; (void) ev_data; nc->flags |= MG_F_USER_4; nc->flags |= MG_F_WANT_READ; /* Should not be allowed. */ } static const char *test_connect_opts(void) { struct mg_mgr mgr; struct mg_connection *nc; struct mg_connect_opts opts; memset(&opts, 0, sizeof(opts)); opts.user_data = (void *) 0xdeadbeef; opts.flags = MG_F_USER_6; opts.flags |= MG_F_WANT_READ; /* Should not be allowed. */ mg_mgr_init(&mgr, NULL); ASSERT((nc = mg_connect_opt(&mgr, "127.0.0.1:33211", cb6, opts)) != NULL); ASSERT(nc->user_data == (void *) 0xdeadbeef); ASSERT(nc->flags & MG_F_USER_6); ASSERT(!(nc->flags & MG_F_WANT_READ)); /* TODO(rojer): find a way to test this w/o touching nc (already freed). poll_mgr(&mgr, 25); ASSERT(nc->flags & MG_F_USER_4); ASSERT(nc->flags & MG_F_USER_6); ASSERT(!(nc->flags & MG_F_WANT_READ)); */ mg_mgr_free(&mgr); return NULL; } static const char *test_connect_opts_error_string(void) { struct mg_mgr mgr; struct mg_connection *nc; struct mg_connect_opts opts; const char *error_string = NULL; memset(&opts, 0, sizeof(opts)); opts.error_string = &error_string; mg_mgr_init(&mgr, NULL); ASSERT((nc = mg_connect_opt(&mgr, "127.0.0.1:65537", cb6, opts)) == NULL); ASSERT(error_string != NULL); ASSERT_STREQ(error_string, "cannot parse address"); mg_mgr_free(&mgr); return NULL; } #ifndef NO_DNS_TEST static const char *test_resolve(void) { char buf[20]; ASSERT(mg_resolve("localhost", buf, sizeof(buf)) > 0); ASSERT_STREQ(buf, "127.0.0.1"); ASSERT_EQ(mg_resolve("please_dont_name_a_host_like_ths", buf, sizeof(buf)), 0); return NULL; } #endif static const char *test_base64(void) { const char *cases[] = {"test", "longer string"}; unsigned long i; char enc[8192]; char dec[8192]; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { mg_base64_encode((unsigned char *) cases[i], strlen(cases[i]), enc); mg_base64_decode((unsigned char *) enc, strlen(enc), dec); ASSERT_EQ(strcmp(cases[i], dec), 0); } ASSERT_EQ(mg_base64_decode((unsigned char *) "кю", 4, dec), 0); ASSERT_EQ(mg_base64_decode((unsigned char *) "AAAA----", 8, dec), 4); ASSERT_EQ(mg_base64_decode((unsigned char *) "Q2VzYW50YQ==", 12, dec), 12); ASSERT_STREQ(dec, "Cesanta"); return NULL; } static const char *test_sock_addr_to_str(void) { char buf[60]; buf[0] = '\0'; { union socket_address a4; memset(&a4, 0, sizeof(a4)); a4.sa.sa_family = AF_INET; a4.sin.sin_addr.s_addr = inet_addr("127.0.0.1"); a4.sin.sin_port = htons(12345); mg_sock_addr_to_str(&a4, buf, sizeof(buf), 0); ASSERT_STREQ(buf, ""); mg_sock_addr_to_str(&a4, buf, sizeof(buf), MG_SOCK_STRINGIFY_IP); ASSERT_STREQ(buf, "127.0.0.1"); mg_sock_addr_to_str(&a4, buf, sizeof(buf), MG_SOCK_STRINGIFY_PORT); ASSERT_STREQ(buf, "12345"); mg_sock_addr_to_str(&a4, buf, sizeof(buf), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); ASSERT_STREQ(buf, "127.0.0.1:12345"); } #if MG_ENABLE_IPV6 && !defined(_WIN32) { union socket_address a6; memset(&a6, 0, sizeof(a6)); a6.sa.sa_family = AF_INET6; ASSERT_EQ(inet_pton(AF_INET6, "2001::123", &a6.sin6.sin6_addr), 1); a6.sin6.sin6_port = htons(12345); mg_sock_addr_to_str(&a6, buf, sizeof(buf), 0); ASSERT_STREQ(buf, ""); mg_sock_addr_to_str(&a6, buf, sizeof(buf), MG_SOCK_STRINGIFY_IP); ASSERT_STREQ(buf, "2001::123"); mg_sock_addr_to_str(&a6, buf, sizeof(buf), MG_SOCK_STRINGIFY_PORT); ASSERT_STREQ(buf, "12345"); mg_sock_addr_to_str(&a6, buf, sizeof(buf), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); ASSERT_STREQ(buf, "[2001::123]:12345"); } #endif return NULL; } static const char *test_hexdump(void) { const char *src = "\1\2\3\4abcd"; char got[256]; const char *want = "0000 01 02 03 04 61 62 63 64" " ....abcd\n"; ASSERT_EQ(mg_hexdump(src, strlen(src), got, sizeof(got)), (int) strlen(want)); ASSERT_STREQ(got, want); return NULL; } static const char *test_hexdump_file(void) { const char *path = "test_hexdump"; char *data, *got; size_t size; FILE *fp; struct mg_connection *nc = create_test_connection(); /* "In the GNU system, non-null pointers are printed as unsigned integers, * as if a `%#x' conversion were used. Null pointers print as `(nil)'. * (Pointers might print differently in other systems.)" * indeed it prints 0x0 on apple. */ nc->user_data = (void *) 0xbeef; /* truncate file. open+O_TRUNC works on wine but not on real windows */ fp = fopen(path, "w"); ASSERT(fp != NULL); fclose(fp); mbuf_append(&nc->send_mbuf, "foo", 3); mbuf_append(&nc->recv_mbuf, "bar", 3); mg_hexdump_connection(nc, path, nc->send_mbuf.buf, 3, MG_EV_SEND); mbuf_free(&nc->send_mbuf); mbuf_free(&nc->recv_mbuf); free(nc); ASSERT((data = read_file(path, &size)) != NULL); unlink(path); got = data; while (got - data < (int) size && *got++ != ' ') ; size -= got - data; ASSERT(strstr(got, "0000 66 6f 6f " " foo") != NULL); free(data); return NULL; } static const char *test_basic_auth_helpers() { struct mbuf buf; mbuf_init(&buf, 0); mg_basic_auth_header(mg_mk_str("foo"), mg_mk_str("bar"), &buf); ASSERT_EQ(buf.len, 35); ASSERT_EQ(memcmp(buf.buf, "Authorization: Basic Zm9vOmJhcg==\r\n", 35), 0); buf.len = 0; mg_basic_auth_header(mg_mk_str("foo:bar"), mg_mk_str_n(NULL, 0), &buf); ASSERT_EQ(buf.len, 35); ASSERT_EQ(memcmp(buf.buf, "Authorization: Basic Zm9vOmJhcg==\r\n", 35), 0); mbuf_free(&buf); { char user[256]; char pass[256]; size_t i; struct { const char *hdr; const char *user; const char *pass; int res; } cases[] = { {"Basic Zm9vOmJhcg==", "foo", "bar", 0}, /* foo:bar */ {"Basic Zm9v", "foo", "", 0}, /* foo */ {"Basic Zm9vOmJhcjpiYXo=", "foo", "bar:baz", 0}, /* foo:bar:baz */ {"Basic Zm9vOg==", "foo", "", 0}, /* foo: */ {"Basic OmJhcg==", "", "", -1}, /* :bar */ }; for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { struct mg_str hdr = mg_mk_str(cases[i].hdr); int res; memset(user, 0, sizeof(user)); memset(pass, 0, sizeof(pass)); res = mg_parse_http_basic_auth(&hdr, user, sizeof(user), pass, sizeof(pass)); ASSERT_EQ(res, cases[i].res); ASSERT_EQ(strcmp(user, cases[i].user), 0); ASSERT_EQ(strcmp(pass, cases[i].pass), 0); } } return NULL; } static void test_auth_server_handler(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; struct mg_str *hdr; char user[256] = ""; char pass[256] = ""; int res; switch (ev) { case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: case MG_EV_HTTP_REQUEST: { hdr = mg_get_http_header(hm, "Sec-WebSocket-Protocol"); if (hdr != NULL) { if (mg_vcmp(hdr, "myproto") == 0) { *(int *) nc->user_data = 2; break; } } res = mg_get_http_basic_auth(hm, user, sizeof(user), pass, sizeof(pass)); if (res == -1) { *(int *) nc->user_data = -1; break; } if (strcmp(user, "foo") == 0 && strcmp(pass, "bar") == 0) { *(int *) nc->user_data = 1; } else { *(int *) nc->user_data = -2; } break; } } } static void test_auth_client_handler(struct mg_connection *nc, int ev, void *ev_data) { (void) nc; (void) ev; (void) ev_data; } static const char *test_http_auth() { int connected; struct mg_connection *nc; struct mg_mgr mgr; mg_mgr_init(&mgr, NULL); nc = mg_bind(&mgr, "127.0.0.1:1234", test_auth_server_handler); mg_set_protocol_http_websocket(nc); nc->user_data = &connected; connected = 0; mg_connect_http(&mgr, test_auth_client_handler, "http://foo:bar@127.0.0.1:1234", NULL, NULL); poll_until(&mgr, 4, c_int_eq, &connected, (void *) 1); ASSERT_EQ(connected, 1); connected = 0; mg_connect_ws(&mgr, test_auth_client_handler, "ws://foo:bar@127.0.0.1:1234", NULL, NULL); poll_until(&mgr, 4, c_int_eq, &connected, (void *) 1); ASSERT_EQ(connected, 1); /* check that we didn't break proto or other headers */ connected = 0; mg_connect_ws(&mgr, test_auth_client_handler, "ws://127.0.0.1:1234", "myproto", NULL); poll_until(&mgr, 4, c_int_eq, &connected, (void *) 2); ASSERT_EQ(connected, 2); mg_mgr_free(&mgr); return NULL; } void tunnel_server_test_handler(struct mg_connection *nc, int ev, void *ev_data) { (void) ev_data; switch (ev) { case MG_EV_HTTP_REQUEST: if (nc->user_data) break; nc->user_data = (void *) malloc(sizeof(int)); (*(int *) nc->user_data) = 0; mg_set_timer(nc, mg_time() + 0.01); mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); break; case MG_EV_TIMER: mg_printf_http_chunk(nc, "%d", (*(int *) nc->user_data)++); if (*(int *) nc->user_data == 10) { mg_send_http_chunk(nc, "", 0); nc->flags |= MG_F_SEND_AND_CLOSE; } else { mg_set_timer(nc, mg_time() + 0.01); } break; case MG_EV_CLOSE: free(nc->user_data); default: break; } } void tunnel_client_test_handler(struct mg_connection *nc, int ev, void *ev_data) { struct http_message *hm = (struct http_message *) ev_data; (void) nc; (void) ev_data; switch (ev) { case MG_EV_HTTP_REPLY: if (hm->body.len == 10 && strcmp(hm->body.p, "0123456789") == 0) { *(int *) nc->user_data = 1; } else { fprintf(stderr, "want: 10, got:%d \"%.*s\"\n", (int) hm->body.len, (int) hm->body.len, hm->body.p); } break; default: break; } } static const char *test_http_chunk(void) { struct mg_connection nc; init_test_connection(&nc); mg_printf_http_chunk(&nc, "%d %s", 123, ":-)"); ASSERT_EQ(nc.send_mbuf.len, 12); ASSERT_EQ(memcmp(nc.send_mbuf.buf, "7\r\n123 :-)\r\n", 12), 0); mbuf_free(&nc.send_mbuf); mg_send_http_chunk(&nc, "", 0); ASSERT_EQ(nc.send_mbuf.len, 5); ASSERT_EQ(memcmp(nc.send_mbuf.buf, "0\r\n\r\n", 3), 0); mbuf_free(&nc.send_mbuf); return NULL; } static int s_handle_chunk_event = 0; static char s_events[100]; static void eh_chunk2(struct mg_connection *nc, int ev, void *ev_data) { if (ev == MG_EV_HTTP_CHUNK) { if (s_handle_chunk_event) { nc->flags |= MG_F_DELETE_CHUNK; } } snprintf(s_events + strlen(s_events), sizeof(s_events) - strlen(s_events), "_%d", ev); (void) ev_data; } static const char *test_http_extra_headers(void) { char buf[FETCH_BUF_SIZE]; const char *expect = "MyHdr: my_val\r\n"; s_http_server_opts.extra_headers = "MyHdr: my_val"; fetch_http(buf, "%s", "GET / HTTP/1.0\r\n\r\n"); ASSERT(strstr(buf, expect) != NULL); s_http_server_opts.extra_headers = NULL; return NULL; } static const char *test_http_not_modified(void) { struct http_message hm; cs_stat_t st; const char *req1 = "GET / HTTP/1.0\nIf-None-Match: \"0.123\"\n" "If-Modified-Since: Tue 17 Nov 2015 21:01:56 GMT\n\n"; const char *req4 = "GET / HTTP/1.0\nIf-None-Match: \"0.7\"\n" "If-Modified-Since: Tue 17 Nov 2015 21:01:56 GMT\n\n"; const char *req2 = "GET / HTTP/1.0\n" "If-Modified-Since: Tue 17 Nov 2015 21:01:56 GMT\n\n"; const char *req3 = "GET / HTTP/1.0\n\n"; st.st_size = 7; st.st_mtime = (time_t) 0; /* Not modified according to If-Modified-Since, but modified by Etag. */ ASSERT(mg_parse_http(req1, strlen(req1), &hm, 1) > 0); ASSERT(mg_is_not_modified(&hm, &st) == 0); /* Not modified according to If-Modified-Since, and no Etag. */ ASSERT(mg_parse_http(req2, strlen(req2), &hm, 1) > 0); ASSERT(mg_is_not_modified(&hm, &st) != 0); ASSERT(mg_parse_http(req3, strlen(req3), &hm, 1) > 0); ASSERT(mg_is_not_modified(&hm, &st) == 0); ASSERT(mg_parse_http(req4, strlen(req4), &hm, 1) > 0); ASSERT(mg_is_not_modified(&hm, &st) != 0); return NULL; } static const char *test_http_chunk2(void) { struct mg_connection nc; struct http_message hm; char buf[100] = "5\r\nhe"; struct mg_mgr mgr; mg_mgr_init(&mgr, NULL); memset(&nc, 0, sizeof(nc)); memset(&hm, 0, sizeof(hm)); nc.mgr = &mgr; nc.sock = INVALID_SOCKET; nc.handler = eh_chunk2; hm.message.len = hm.body.len = ~0; s_handle_chunk_event = 0; ASSERT_EQ(mg_handle_chunked(&nc, &hm, buf, strlen(buf)), 0); /* Simulate arrival of chunks. MG_EV_HTTP_CHUNK events are not handled. */ strcat(buf, "llo\r"); ASSERT_EQ(mg_handle_chunked(&nc, &hm, buf, strlen(buf)), 0); ASSERT_STREQ(buf, "5\r\nhello\r"); strcat(buf, "\n"); ASSERT_EQ(mg_handle_chunked(&nc, &hm, buf, strlen(buf)), 5); ASSERT_STREQ(buf, "hello"); s_handle_chunk_event = 1; strcat(buf, "3\r\n:-)\r\n"); ASSERT_EQ(mg_handle_chunked(&nc, &hm, buf, strlen(buf)), 8); ASSERT_STREQ(buf, ""); s_handle_chunk_event = 0; strcat(buf, "3\r\n...\r\na\r\n0123456789\r\n0\r"); ASSERT_EQ(mg_handle_chunked(&nc, &hm, buf, strlen(buf)), 13); ASSERT_STREQ(buf, "...01234567890\r"); ASSERT_EQ(hm.message.len, (size_t) ~0); strcat(buf, "\n\r\n"); ASSERT_EQ(mg_handle_chunked(&nc, &hm, buf, strlen(buf)), 13); ASSERT_STREQ(buf, "...0123456789"); ASSERT_EQ(hm.message.len, 13); ASSERT_STREQ(s_events, "_102_102_102_102"); ASSERT(nc.proto_data != NULL); nc.proto_data_destructor(nc.proto_data); mg_mgr_free(&mgr); return NULL; } static const char *test_parse_date_string(void) { ASSERT_EQ(mg_parse_date_string("Sat, 31 Oct 2019 10:51:12 GMT"), 1572519072); ASSERT_EQ(mg_parse_date_string("31/Oct/2019 10:51:12 GMT"), 1572519072); ASSERT_EQ(mg_parse_date_string("31 Oct 2019 10:51:12 GMT"), 1572519072); ASSERT_EQ(mg_parse_date_string("31-Oct-2019 10:51:12 GMT"), 1572519072); return NULL; } #if MG_ENABLE_HTTP_STREAMING_MULTIPART struct cb_mp_srv_data { struct mbuf res; int request_end; }; static void cb_mp_srv(struct mg_connection *nc, int ev, void *p) { struct cb_mp_srv_data *mpd = (struct cb_mp_srv_data *) nc->user_data; struct mbuf *data = &mpd->res; if (nc->listener == NULL) return; switch (ev) { case MG_EV_HTTP_MULTIPART_REQUEST: { struct http_message *hm = (struct http_message *) p; mbuf_append(data, "<MPRQ", 5); mbuf_append(data, hm->uri.p, hm->uri.len); break; } case MG_EV_HTTP_PART_BEGIN: { struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p; if (mp->status < 0) { mbuf_append(data, "ERROR", 5); } else { DBG(("BEGIN %s %s", mp->var_name, mp->file_name)); mbuf_append(data, mp->var_name, strlen(mp->var_name)); mbuf_append(data, mp->file_name, strlen(mp->file_name)); } break; } case MG_EV_HTTP_PART_DATA: { struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p; if (mp->status < 0) { mbuf_append(data, "ERROR", 5); } else { DBG(("DATA %d", (int) mp->data.len)); mbuf_append(data, mp->data.p, mp->data.len); } break; } case MG_EV_HTTP_PART_END: { struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p; struct mbuf *data = (struct mbuf *) nc->listener->user_data; mbuf_append(data, mp->var_name, strlen(mp->var_name)); mbuf_append(data, mp->file_name, strlen(mp->file_name)); if (mp->status < 0) { DBG(("PARTERR %s %s", mp->var_name, mp->file_name)); mbuf_append(data, "ERROR", 5); } else { DBG(("END %s %s", mp->var_name, mp->file_name)); mbuf_append(data, "FIN", 3); } break; } case MG_EV_HTTP_MULTIPART_REQUEST_END: { struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) p; DBG(("RQEND %p %p", (void *) mp->var_name, (void *) mp->file_name)); mbuf_append(data, (mp->status < 0 ? "-" : "+"), 1); mbuf_append(data, (mp->var_name == NULL ? "1" : "0"), 1); mbuf_append(data, (mp->file_name == NULL ? "1" : "0"), 1); mbuf_append(data, "MPRQ>", 5); mpd->request_end = 1; break; } } } static void cb_mp_send_one_byte(struct mg_connection *nc, int ev, void *p) { static int i; (void) p; if (ev == MG_EV_POLL) { char ch = ((char *) nc->user_data)[i++]; int l = strlen((char *) nc->user_data); if (ch != '\0') { mg_send(nc, &ch, 1); DBG(("%p sent %d of %d", (void *) nc, i, l)); } } } static void cb_mp_empty(struct mg_connection *nc, int ev, void *p) { (void) nc; (void) ev; (void) p; } static const char b1[] = "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111\r\n" "111111111111111111111111111111111111111111111111111111111111111"; static const char b2[] = "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n" "222222222222222222222222222222222222222222222222222222222222222\r\n"; static const char b4[] = "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444\r\n" "444444444444444444444444444444444444444444444444444444444444444"; static const char *test_http_multipart_check_res(struct mbuf *res) { const char *ptr = res->buf; ASSERT_STREQ_NZ(ptr, "<MPRQ/test"); ptr += 10; ASSERT_STREQ_NZ(ptr, "afoo"); ptr += 4; ASSERT_STREQ_NZ(ptr, b1); ptr += sizeof(b1) - 1; ASSERT_STREQ_NZ(ptr, "afooFIN"); ptr += 7; /* No file_name for second part */ ASSERT_STREQ_NZ(ptr, "b"); ptr++; ASSERT_STREQ_NZ(ptr, b2); ptr += sizeof(b2) - 1; ASSERT_STREQ_NZ(ptr, "bFIN"); ptr += 4; ASSERT_STREQ_NZ(ptr, "cbar"); ptr += 4; ASSERT_STREQ_NZ(ptr, b4); ptr += sizeof(b4) - 1; ASSERT_STREQ_NZ(ptr, "cbarFIN"); ptr += 7; ASSERT_STREQ_NZ(ptr, "+11MPRQ>"); ptr += 8; ASSERT_EQ((size_t)(ptr - res->buf), res->len); return NULL; } static const char *test_http_multipart2(void) { struct mg_mgr mgr; struct mg_connection *nc_listen; const char multi_part_req_fmt[] = "%s" "Content-Disposition: form-data; name=\"a\"; filename=\"foo\"\r\n" "\r\n" "%s" "\r\n--Asrf456BGe4h\r\n" "Content-Disposition: form-data; name=\"b\"\r\n" "\r\n" "%s" "\r\n--Asrf456BGe4h\r\n" "Content-Disposition: form-data; name=\"c\"; filename=\"bar\"\r\n" "\r\n" "%s" "\r\n--Asrf456BGe4h--\r\n" "\r\n"; char multi_part_req[1024 * 5]; int i; struct mg_connection *c; const char *r; struct cb_mp_srv_data mpd; memset(&mpd, 0, sizeof(mpd)); mbuf_init(&mpd.res, 0); mg_mgr_init(&mgr, NULL); nc_listen = mg_bind(&mgr, "8765", cb_mp_srv); nc_listen->user_data = &mpd; mg_set_protocol_http_websocket(nc_listen); snprintf(multi_part_req, sizeof(multi_part_req), multi_part_req_fmt, "", b1, b2, b4); ASSERT((c = mg_connect_http(&mgr, cb_mp_send_one_byte, "http://127.0.0.1:8765/test", "Content-Type: " "multipart/form-data;boundary=Asrf456BGe4h", "\r\n--Asrf456BGe4h\r\n")) != NULL); c->user_data = multi_part_req; poll_until(&mgr, 10, c_int_eq, &mpd.request_end, (void *) 1); if ((r = test_http_multipart_check_res(&mpd.res)) != NULL) return r; c->flags |= MG_F_CLOSE_IMMEDIATELY; mbuf_free(&mpd.res); memset(&mpd, 0, sizeof(mpd)); mbuf_init(&mpd.res, 0); snprintf(multi_part_req, sizeof(multi_part_req), multi_part_req_fmt, "\r\n--Asrf456BGe4h\r\n", b1, b2, b4); /* Testing delivering to endpoint handler*/ nc_listen = mg_bind(&mgr, "8766", cb_mp_empty); nc_listen->user_data = &mpd; mg_set_protocol_http_websocket(nc_listen); mg_register_http_endpoint(nc_listen, "/test", cb_mp_srv MG_UD_ARG(NULL)); ASSERT((c = mg_connect_http(&mgr, cb_mp_empty, "http://127.0.0.1:8766/test", "Connection: keep-alive\r\nContent-Type: " "multipart/form-data; boundary=Asrf456BGe4h", multi_part_req)) != NULL); poll_until(&mgr, 3, c_int_eq, &mpd.request_end, (void *) 1); mg_mgr_poll(&mgr, 1); /* Consume epilogue. */ if ((r = test_http_multipart_check_res(&mpd.res)) != NULL) return r; /* Keep the connection alive */ mbuf_free(&mpd.res); memset(&mpd, 0, sizeof(mpd)); mbuf_init(&mpd.res, 0); mg_printf(c, "POST /test HTTP/1.1\r\nHost: 127.0.0.1:8766\r\nContent-Length: " "%" SIZE_T_FMT "\r\nConnection: keep-alive\r\nContent-Type: multipart/form-data; " "boundary=Asrf456BGe4h\r\n%s", strlen(multi_part_req), multi_part_req); poll_until(&mgr, 3, c_int_eq, &mpd.request_end, (void *) 1); if ((r = test_http_multipart_check_res(&mpd.res)) != NULL) return r; c->flags |= MG_F_CLOSE_IMMEDIATELY; mbuf_free(&mpd.res); memset(&mpd, 0, sizeof(mpd)); mbuf_init(&mpd.res, 0); mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len); /* Test interrupted request */ multi_part_req[1800] = '\0'; c = mg_connect_http( &mgr, cb_mp_empty, "http://127.0.0.1:8765", "Content-Type: multipart/form-data; boundary=Asrf456BGe4h", multi_part_req); ASSERT(c != NULL); for (i = 0; i < 20; i++) { mg_mgr_poll(&mgr, 1); } c->flags |= MG_F_CLOSE_IMMEDIATELY; poll_until(&mgr, 3, c_int_eq, &mpd.request_end, (void *) 1); ASSERT_STREQ_NZ(mpd.res.buf + mpd.res.len - 17, "cbarERROR-11MPRQ>"); mbuf_free(&mpd.res); memset(&mpd, 0, sizeof(mpd)); mbuf_init(&mpd.res, 0); ASSERT( mg_connect_http( &mgr, cb_mp_empty, "http://127.0.0.1:8766/test", "Content-Type: multipart/form-data; boundary=Asrf456BGe4h", "\r\n--Asrf456BGe4h\r\n" "Content-Disposition: form-data; name=\"d\"; filename=\"small\"\r\n" "\r\n" "boooo" "\r\n--Asrf456BGe4h--\r\n" "\r\n") != NULL); poll_until(&mgr, 3, c_int_eq, &mpd.request_end, (void *) 1); ASSERT(mpd.res.len != 0); ASSERT_STREQ_NZ(mpd.res.buf, "<MPRQ/testdsmallboooodsmallFIN+11MPRQ>"); mbuf_free(&mpd.res); /* * Test malformed packet * See https://github.com/cesanta/dev/issues/6974 * This request should not lead to crash */ c = mg_connect(&mgr, "127.0.0.1:8766", cb_mp_empty); mg_printf(c, "POST /test HTTP/1.1\r\n" "Connection: keep-alive\r\n" "Content-Type: multipart/form-data; Content-Length: 1\r\n" "1\r\n\r\n"); for (i = 0; i < 20; i++) { mg_mgr_poll(&mgr, 1); } mg_mgr_free(&mgr); return NULL; } #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ static const char *test_http_multipart(void) { struct http_message hm; char buf[FETCH_BUF_SIZE] = "", var_name[100], file_name[100]; const char *chunk; size_t chunk_len, ofs; fetch_http(buf, "%s", "GET /data/multipart.txt HTTP/1.0\r\n\r\n"); mg_parse_http(buf, strlen(buf), &hm, 1); ofs = mg_parse_multipart(hm.body.p, hm.body.len, var_name, sizeof(var_name), file_name, sizeof(file_name), &chunk, &chunk_len); ASSERT(ofs < hm.body.len); ASSERT(ofs > 0); ASSERT_EQ(chunk_len, 10); ASSERT_EQ(memcmp(chunk, "file1 data", chunk_len), 0); ofs = mg_parse_multipart(hm.body.p + ofs, hm.body.len - ofs, var_name, sizeof(var_name), file_name, sizeof(file_name), &chunk, &chunk_len); ASSERT(ofs < hm.body.len); ASSERT(ofs > 0); ASSERT_EQ(chunk_len, 10); ASSERT_EQ(memcmp(chunk, "file2 data", chunk_len), 0); ofs = mg_parse_multipart(hm.body.p + ofs, hm.body.len - ofs, var_name, sizeof(var_name), file_name, sizeof(file_name), &chunk, &chunk_len); ASSERT(ofs == 0); return NULL; } static const char *test_dns_encode(void) { struct mg_connection nc; const char *got; int query_types[] = {MG_DNS_A_RECORD, MG_DNS_AAAA_RECORD}; size_t i; init_test_connection(&nc); /* * Testing TCP encoding since when the connection * is in UDP mode the data is not stored in the send buffer. */ for (i = 0; i < ARRAY_SIZE(query_types); i++) { mg_send_dns_query(&nc, "www.cesanta.com", query_types[i]); got = nc.send_mbuf.buf; ASSERT_EQ(nc.send_mbuf.len, 12 + 4 + 13 + 4 + 2); ASSERT_EQ(got[14], 3); ASSERT_STREQ_NZ(&got[15], "www"); ASSERT_EQ(got[18], 7); ASSERT_STREQ_NZ(&got[19], "cesanta"); ASSERT_EQ(got[26], 3); ASSERT_STREQ_NZ(&got[27], "com"); ASSERT_EQ(got[30], 0); ASSERT_EQ(got[31], 0); ASSERT_EQ(got[32], query_types[i]); ASSERT_EQ(got[33], 0); ASSERT_EQ(got[34], 1); mbuf_free(&nc.send_mbuf); } return NULL; } static const char *test_dns_uncompress(void) { /* * Order or string constants is important. Names being uncompressed * must be before the end of the DNS packet end. */ const char *s = "\03www\07cesanta\03com\0\03www\xc0\06\05dummy\07cesanta\3com\0"; struct mg_dns_message msg; struct mg_str name = mg_mk_str(s); struct mg_str comp_name = mg_mk_str_n(s + 17, 6); struct mg_str n; char dst[256]; int len; size_t i; const char *cases[] = {"www.cesanta.com", "www", "ww", "www.", "www.c"}; memset(&msg, 0, sizeof(msg)); msg.pkt.p = s + 23; msg.pkt.len = strlen(msg.pkt.p) + 1; memset(dst, 'X', sizeof(dst)); n = mg_mk_str_n(msg.pkt.p, msg.pkt.len); len = mg_dns_uncompress_name(&msg, &n, dst, sizeof(dst)); ASSERT_EQ(len, 17); ASSERT_EQ(len, (int) strlen(dst)); ASSERT_STREQ_NZ(dst, "dummy.cesanta.com"); for (i = 0; i < ARRAY_SIZE(cases); i++) { size_t l = strlen(cases[i]); memset(dst, 'X', sizeof(dst)); len = mg_dns_uncompress_name(&msg, &name, dst, l); ASSERT_EQ(len, (int) l); ASSERT_EQ(strncmp(dst, cases[i], l), 0); ASSERT_EQ(dst[l], 'X'); } /* if dst has enough space, check the trailing '\0' */ memset(dst, 'X', sizeof(dst)); len = mg_dns_uncompress_name(&msg, &name, dst, sizeof(dst)); ASSERT_EQ(len, 15); ASSERT_EQ(len, (int) strlen(dst)); ASSERT_STREQ_NZ(dst, "www.cesanta.com"); ASSERT_EQ(dst[15], 0); /* check compressed name */ memset(dst, 'X', sizeof(dst)); len = mg_dns_uncompress_name(&msg, &comp_name, dst, sizeof(dst)); printf("%d [%s]\n", len, dst); ASSERT_EQ(len, 15); ASSERT_EQ(len, (int) strlen(dst)); ASSERT_STREQ_NZ(dst, "www.cesanta.com"); ASSERT_EQ(dst[15], 0); /* check parsing loop avoidance */ msg.pkt.p = "\05dummy\xc0\x06\0"; msg.pkt.len = strlen(msg.pkt.p) + 1; memset(dst, 'X', sizeof(dst)); len = mg_dns_uncompress_name(&msg, &comp_name, dst, sizeof(dst)); ASSERT_EQ(len, 0); return NULL; } static const char *test_dns_decode(void) { struct mg_dns_message msg; char name[256]; const char *hostname = "go.cesanta.com"; const char *cname = "ghs.googlehosted.com"; struct mg_dns_resource_record *r; uint16_t tiny; struct in_addr ina; int n; /* * Response for a record A query host for `go.cesanta.com`. * The response contains two answers: * * CNAME go.cesanta.com -> ghs.googlehosted.com * A ghs.googlehosted.com -> 74.125.136.121 * * Captured from a reply generated by Google DNS server (8.8.8.8) */ const unsigned char pkt[] = { 0xa1, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x67, 0x6f, 0x07, 0x63, 0x65, 0x73, 0x61, 0x6e, 0x74, 0x61, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x09, 0x52, 0x00, 0x13, 0x03, 0x67, 0x68, 0x73, 0x0c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0xc0, 0x17, 0xc0, 0x2c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, 0x00, 0x04, 0x4a, 0x7d, 0x88, 0x79}; ASSERT_EQ(mg_parse_dns((const char *) pkt, sizeof(pkt), &msg), 0); ASSERT_EQ(msg.num_questions, 1); ASSERT_EQ(msg.num_answers, 2); r = &msg.questions[0]; ASSERT_EQ(mg_dns_uncompress_name(&msg, &r->name, name, sizeof(name)), strlen(hostname)); ASSERT_EQ(strncmp(name, hostname, strlen(hostname)), 0); r = &msg.answers[0]; ASSERT_EQ(mg_dns_uncompress_name(&msg, &r->name, name, sizeof(name)), strlen(hostname)); ASSERT_EQ(strncmp(name, hostname, strlen(hostname)), 0); ASSERT_EQ(mg_dns_uncompress_name(&msg, &r->rdata, name, sizeof(name)), strlen(cname)); ASSERT_EQ(strncmp(name, cname, strlen(cname)), 0); r = &msg.answers[1]; ASSERT_EQ(mg_dns_uncompress_name(&msg, &r->name, name, sizeof(name)), strlen(cname)); ASSERT_EQ(strncmp(name, cname, strlen(cname)), 0); ASSERT_EQ(mg_dns_parse_record_data(&msg, r, &tiny, sizeof(tiny)), -1); ASSERT_EQ(mg_dns_parse_record_data(&msg, r, &ina, sizeof(ina)), 0); ASSERT_EQ(ina.s_addr, inet_addr("74.125.136.121")); /* Test iteration */ n = 0; r = NULL; while ((r = mg_dns_next_record(&msg, MG_DNS_A_RECORD, r))) { n++; } ASSERT_EQ(n, 1); n = 0; r = NULL; while ((r = mg_dns_next_record(&msg, MG_DNS_CNAME_RECORD, r))) { n++; } ASSERT_EQ(n, 1); /* Test unknown record type */ r = mg_dns_next_record(&msg, MG_DNS_A_RECORD, r); r->rtype = 0xff; ASSERT_EQ(mg_dns_parse_record_data(&msg, r, &ina, sizeof(ina)), -1); return NULL; } static const char *test_dns_decode_truncated(void) { struct mg_dns_message msg; char name[256]; const char *hostname = "go.cesanta.com"; const char *cname = "ghs.googlehosted.com"; struct mg_dns_resource_record *r; uint16_t tiny; struct in_addr ina; int n; int i; const unsigned char src[] = { 0xa1, 0x00, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x67, 0x6f, 0x07, 0x63, 0x65, 0x73, 0x61, 0x6e, 0x74, 0x61, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x09, 0x52, 0x00, 0x13, 0x03, 0x67, 0x68, 0x73, 0x0c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x68, 0x6f, 0x73, 0x74, 0x65, 0x64, 0xc0, 0x17, 0xc0, 0x2c, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, 0x00, 0x04, 0x4a, 0x7d, 0x88, 0x79}; char *pkt = NULL; #define WONDER(expr) \ if (!(expr)) continue for (i = sizeof(src) - 1; i > 0; i--) { free(pkt); pkt = (char *) malloc(i); memcpy(pkt, src, i); WONDER(mg_parse_dns((const char *) pkt, i, &msg) == 0); WONDER(msg.num_questions == 1); WONDER(msg.num_answers == 2); r = &msg.questions[0]; WONDER(mg_dns_uncompress_name(&msg, &r->name, name, sizeof(name)) == strlen(hostname)); WONDER(strncmp(name, hostname, strlen(hostname)) == 0); r = &msg.answers[0]; WONDER(mg_dns_uncompress_name(&msg, &r->name, name, sizeof(name)) == strlen(hostname)); WONDER(strncmp(name, hostname, strlen(hostname)) == 0); WONDER(mg_dns_uncompress_name(&msg, &r->rdata, name, sizeof(name)) == strlen(cname)); WONDER(strncmp(name, cname, strlen(cname)) == 0); r = &msg.answers[1]; WONDER(mg_dns_uncompress_name(&msg, &r->name, name, sizeof(name)) == strlen(cname)); WONDER(strncmp(name, cname, strlen(cname)) == 0); WONDER(mg_dns_parse_record_data(&msg, r, &tiny, sizeof(tiny)) == -1); WONDER(mg_dns_parse_record_data(&msg, r, &ina, sizeof(ina)) == 0); WONDER(ina.s_addr == inet_addr("74.125.136.121")); /* Test iteration */ n = 0; r = NULL; while ((r = mg_dns_next_record(&msg, MG_DNS_A_RECORD, r))) { n++; } WONDER(n == 1); n = 0; r = NULL; while ((r = mg_dns_next_record(&msg, MG_DNS_CNAME_RECORD, r))) { n++; } WONDER(n == 1); /* Test unknown record type */ r = mg_dns_next_record(&msg, MG_DNS_A_RECORD, r); WONDER(r != NULL); printf("GOT %p\n", (void *) r); r->rtype = 0xff; WONDER(mg_dns_parse_record_data(&msg, r, &ina, sizeof(ina)) == -1); ASSERT("Should have failed" == NULL); } free(pkt); return NULL; } static const char *check_www_cesanta_com_reply(const char *pkt, size_t len) { char name[256]; in_addr_t addr = inet_addr("54.194.65.250"); struct in_addr ina; struct mg_dns_message msg; memset(&msg, 0, sizeof(msg)); ASSERT(mg_parse_dns(pkt, len, &msg) != -1); memset(name, 0, sizeof(name)); ASSERT(mg_dns_uncompress_name(&msg, &msg.questions[0].name, name, sizeof(name)) > 0); ASSERT_STREQ_NZ(name, "www.cesanta.com"); memset(name, 0, sizeof(name)); ASSERT(mg_dns_uncompress_name(&msg, &msg.answers[0].name, name, sizeof(name)) > 0); ASSERT_STREQ_NZ(name, "www.cesanta.com"); ASSERT_EQ(msg.answers[0].rtype, MG_DNS_CNAME_RECORD); memset(name, 0, sizeof(name)); ASSERT(mg_dns_parse_record_data(&msg, &msg.answers[0], name, sizeof(name)) != -1); ASSERT_STREQ_NZ(name, "cesanta.com"); memset(name, 0, sizeof(name)); ASSERT(mg_dns_uncompress_name(&msg, &msg.answers[1].name, name, sizeof(name)) > 0); ASSERT_STREQ_NZ(name, "cesanta.com"); ASSERT_EQ(msg.answers[1].rtype, MG_DNS_A_RECORD); ASSERT(mg_dns_parse_record_data(&msg, &msg.answers[1], &ina, sizeof(ina)) != -1); ASSERT_EQ(ina.s_addr, addr); return NULL; } static const char *test_dns_reply_encode(void) { const char *err; struct mg_dns_message msg; struct mg_dns_resource_record *rr; char name[256]; in_addr_t addr = inet_addr("54.194.65.250"); struct mbuf pkt; struct mg_connection nc; mbuf_init(&pkt, 0); init_test_connection(&nc); /* create a fake query */ mg_send_dns_query(&nc, "www.cesanta.com", MG_DNS_A_RECORD); /* remove message length from tcp buffer */ mbuf_remove(&nc.send_mbuf, 2); ASSERT_EQ(mg_parse_dns(nc.send_mbuf.buf, nc.send_mbuf.len, &msg), 0); /* build an answer */ msg.num_answers = 2; mg_dns_insert_header(&pkt, 0, &msg); mg_dns_copy_questions(&pkt, &msg); mg_dns_uncompress_name(&msg, &msg.questions[0].name, name, sizeof(name)); rr = &msg.answers[0]; *rr = msg.questions[0]; rr->rtype = MG_DNS_CNAME_RECORD; rr->ttl = 3600; rr->kind = MG_DNS_ANSWER; ASSERT(mg_dns_encode_record(&pkt, rr, "www.cesanta.com", 15, (void *) "cesanta.com", 11) != -1); rr = &msg.answers[1]; *rr = msg.questions[0]; rr->ttl = 3600; rr->kind = MG_DNS_ANSWER; ASSERT(mg_dns_encode_record(&pkt, rr, "cesanta.com", 11, &addr, 4) != -1); if ((err = check_www_cesanta_com_reply(pkt.buf, pkt.len)) != NULL) { return err; } mbuf_free(&pkt); mbuf_free(&nc.send_mbuf); return NULL; } #if MG_ENABLE_DNS_SERVER static void dns_server_eh(struct mg_connection *nc, int ev, void *ev_data) { struct mg_dns_message *msg; struct mg_dns_resource_record *rr; struct mg_dns_reply reply; char name[512]; int i; name[511] = 0; switch (ev) { case MG_DNS_MESSAGE: msg = (struct mg_dns_message *) ev_data; reply = mg_dns_create_reply(&nc->send_mbuf, msg); for (i = 0; i < msg->num_questions; i++) { rr = &msg->questions[i]; if (rr->rtype == MG_DNS_A_RECORD) { mg_dns_uncompress_name(msg, &rr->name, name, sizeof(name) - 1); if (strcmp(name, "cesanta.com") == 0) { mg_dns_reply_record(&reply, rr, NULL, rr->rtype, 3600, nc->user_data, 4); } else if (strcmp(name, "www.cesanta.com") == 0) { mg_dns_reply_record(&reply, rr, NULL, MG_DNS_CNAME_RECORD, 3600, "cesanta.com", strlen("cesanta.com")); mg_dns_reply_record(&reply, rr, "cesanta.com", rr->rtype, 3600, nc->user_data, 4); } } } /* * We don't set the error flag even if there were no answers * maching the MG_DNS_A_RECORD query type. * This indicates that we have (syntetic) answers for MG_DNS_A_RECORD. * See http://goo.gl/QWvufr for a distinction between NXDOMAIN and NODATA. */ mg_dns_send_reply(nc, &reply); nc->flags |= MG_F_SEND_AND_CLOSE; break; } } static int check_record_name(struct mg_dns_message *msg, struct mg_str *n, const char *want) { char name[512]; if (mg_dns_uncompress_name(msg, n, name, sizeof(name)) == 0) { return 0; } return strncmp(name, want, sizeof(name)) == 0; } static const char *test_dns_server(void) { const char *err; struct mg_connection nc; struct mg_dns_message msg; in_addr_t addr = inet_addr("54.194.65.250"); int ilen; init_test_connection(&nc); nc.handler = dns_server_eh; nc.user_data = &addr; mg_set_protocol_dns(&nc); mg_send_dns_query(&nc, "www.cesanta.com", MG_DNS_A_RECORD); nc.recv_mbuf = nc.send_mbuf; mbuf_init(&nc.send_mbuf, 0); ilen = nc.recv_mbuf.len; nc.proto_handler(&nc, MG_EV_RECV, &ilen); mbuf_free(&nc.recv_mbuf); /* remove message length from tcp buffer before manually checking */ mbuf_remove(&nc.send_mbuf, 2); if ((err = check_www_cesanta_com_reply(nc.send_mbuf.buf, nc.send_mbuf.len)) != NULL) { mbuf_free(&nc.send_mbuf); return err; } mbuf_free(&nc.send_mbuf); /* test mg_dns_reply_record */ mg_send_dns_query(&nc, "cesanta.com", MG_DNS_A_RECORD); nc.recv_mbuf = nc.send_mbuf; mbuf_init(&nc.send_mbuf, 0); ilen = nc.recv_mbuf.len; nc.proto_handler(&nc, MG_EV_RECV, &ilen); /* remove message length from tcp buffer before manually checking */ mbuf_remove(&nc.send_mbuf, 2); ASSERT(mg_parse_dns(nc.send_mbuf.buf, nc.send_mbuf.len, &msg) != -1); ASSERT_EQ(msg.num_answers, 1); ASSERT_EQ(msg.answers[0].rtype, MG_DNS_A_RECORD); ASSERT(check_record_name(&msg, &msg.answers[0].name, "cesanta.com")); mbuf_free(&nc.send_mbuf); mbuf_free(&nc.recv_mbuf); /* check malformed request error */ memset(&msg, 0, sizeof(msg)); ilen = 0; nc.proto_handler(&nc, MG_EV_RECV, &ilen); /* remove message length from tcp buffer before manually checking */ mbuf_remove(&nc.send_mbuf, 2); ASSERT(mg_parse_dns(nc.send_mbuf.buf, nc.send_mbuf.len, &msg) != -1); ASSERT(msg.flags & 1); ASSERT_EQ(msg.num_questions, 0); ASSERT_EQ(msg.num_answers, 0); mbuf_free(&nc.recv_mbuf); mbuf_free(&nc.send_mbuf); return NULL; } #endif /* MG_ENABLE_DNS_SERVER */ static void dns_resolve_cb(struct mg_dns_message *msg, void *data, enum mg_resolve_err e) { struct mg_dns_resource_record *rr; char cname[256]; struct in_addr got_addr; in_addr_t want_addr = inet_addr("131.107.255.255"); (void) e; rr = mg_dns_next_record(msg, MG_DNS_A_RECORD, NULL); if (rr != NULL) { mg_dns_parse_record_data(msg, rr, &got_addr, sizeof(got_addr)); } rr = mg_dns_next_record(msg, MG_DNS_CNAME_RECORD, NULL); if (rr != NULL) { mg_dns_parse_record_data(msg, rr, cname, sizeof(cname)); } /* * We saw cases when A query returns only A record, or A and CNAME records. * Expect A answer, and optionally CNAME answer. */ if (want_addr == got_addr.s_addr || strcmp(cname, "dns.msftncsi.com") == 0) { *(int *) data = 1; /* Success */ } else { *(int *) data = 2; /* Error */ } } static const char *test_dns_resolve(void) { struct mg_mgr mgr; struct mg_mgr_init_opts opts; int data = 0; mg_mgr_init(&mgr, NULL); /* Microsoft promises dns.msftncsi.com is always 131.107.255.255 */ mg_resolve_async(&mgr, "dns.msftncsi.com", MG_DNS_A_RECORD, dns_resolve_cb, &data); poll_until(&mgr, 1, c_int_ne, &data, (void *) 0); ASSERT_EQ(data, 1); mg_mgr_free(&mgr); data = 0; memset(&opts, 0, sizeof(opts)); opts.nameserver = "8.8.4.4"; mg_mgr_init_opt(&mgr, NULL, opts); /* Microsoft promises dns.msftncsi.com is always 131.107.255.255 */ mg_resolve_async(&mgr, "dns.msftncsi.com", MG_DNS_A_RECORD, dns_resolve_cb, &data); poll_until(&mgr, 1, c_int_ne, &data, (void *) 0); ASSERT_EQ(data, 1); mg_mgr_free(&mgr); data = 0; mg_mgr_init(&mgr, NULL); mg_set_nameserver(&mgr, "8.8.4.4"); /* Microsoft promises dns.msftncsi.com is always 131.107.255.255 */ mg_resolve_async(&mgr, "dns.msftncsi.com", MG_DNS_A_RECORD, dns_resolve_cb, &data); poll_until(&mgr, 1, c_int_ne, &data, (void *) 0); ASSERT_EQ(data, 1); mg_mgr_free(&mgr); return NULL; } static void dns_resolve_timeout_cb(struct mg_dns_message *msg, void *data, enum mg_resolve_err e) { (void) e; if (msg == NULL) { *(int *) data = 1; } } extern char mg_dns_server[256]; static const char *test_dns_resolve_timeout(void) { struct mg_mgr mgr; struct mg_resolve_async_opts opts; int data = 0; mg_mgr_init(&mgr, NULL); memset(&opts, 0, sizeof(opts)); opts.nameserver = "7.7.7.7"; opts.timeout = -1; /* 0 would be the default timeout */ opts.max_retries = 1; mg_resolve_async_opt(&mgr, "www.cesanta.com", MG_DNS_A_RECORD, dns_resolve_timeout_cb, &data, opts); poll_until(&mgr, 5, c_int_eq, &data, (void *) 1); ASSERT_EQ(data, 1); mg_mgr_free(&mgr); return NULL; } #ifndef NO_RESOLVE_HOSTS_TEST static const char *test_dns_resolve_hosts(void) { union socket_address sa; in_addr_t want_addr = inet_addr("127.0.0.1"); memset(&sa, 0, sizeof(sa)); ASSERT_EQ(mg_resolve_from_hosts_file("localhost", &sa), 0); ASSERT_EQ(sa.sin.sin_addr.s_addr, want_addr); ASSERT_EQ(mg_resolve_from_hosts_file("does_not,exist!in_host*file", &sa), -1); return NULL; } #endif static void ehb_srv(struct mg_connection *nc, int ev, void *p) { struct mbuf *io = &nc->recv_mbuf; (void) io; (void) p; if (ev == MG_EV_RECV) { if (*(int *) p == 1) (*(int *) nc->mgr->user_data)++; mbuf_remove(io, *(int *) p); } } static void ehb_srv2(struct mg_connection *c, int ev, void *p) { if (ev == MG_EV_CLOSE) { if (c->recv_mbuf.size == 1) (*(int *) c->mgr->user_data) = 1; (void) p; } } static const char *test_buffer_limit(void) { struct mg_mgr mgr; struct mg_connection *clnt, *srv; const char *address = "tcp://127.0.0.1:7878"; int res = 0; mg_mgr_init(&mgr, &res); ASSERT((srv = mg_bind(&mgr, address, ehb_srv)) != NULL); srv->recv_mbuf_limit = 1; ASSERT((clnt = mg_connect(&mgr, address, NULL)) != NULL); mg_printf(clnt, "abcd"); poll_until(&mgr, 1, c_int_eq, &res, (void *) 4); /* expect four single byte read events */ ASSERT_EQ(res, 4); /* * Make sure we're not closing full non-drained connections without any * protocol handler */ res = 0; srv->handler = ehb_srv2; ASSERT((clnt = mg_connect(&mgr, address, NULL)) != NULL); mg_printf(clnt, "abcd"); poll_until(&mgr, 1, c_int_eq, &res, (void *) 1); ASSERT_EQ(res, 0); /* * Make sure we're closing full non-drained connections with mqtt protocol * handler */ res = 0; srv->handler = ehb_srv2; mg_set_protocol_mqtt(srv); ASSERT((clnt = mg_connect(&mgr, address, NULL)) != NULL); mg_printf(clnt, "abcd"); poll_until(&mgr, 1, c_int_eq, &res, (void *) 1); ASSERT_EQ(res, 1); mg_mgr_free(&mgr); return NULL; } static const char *test_http_parse_header(void) { static struct mg_str h = MG_MK_STR( "xx=1 kl yy, ert=234 kl=123, " "uri=\"/?naii=x,y\";ii=\"12\\\"34\" zz='aa bb',tt=2,gf=\"xx d=1234"); char buf[20]; ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, sizeof(buf)), 3); ASSERT_STREQ(buf, "234"); ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 2), 0); ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 3), 0); ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 4), 3); ASSERT_EQ(mg_http_parse_header(&h, "gf", buf, sizeof(buf)), 0); ASSERT_EQ(mg_http_parse_header(&h, "zz", buf, sizeof(buf)), 5); ASSERT_STREQ(buf, "aa bb"); ASSERT_EQ(mg_http_parse_header(&h, "d", buf, sizeof(buf)), 4); ASSERT_STREQ(buf, "1234"); buf[0] = 'x'; ASSERT_EQ(mg_http_parse_header(&h, "MMM", buf, sizeof(buf)), 0); ASSERT_EQ(buf[0], '\0'); ASSERT_EQ(mg_http_parse_header(&h, "kl", buf, sizeof(buf)), 3); ASSERT_STREQ(buf, "123"); ASSERT_EQ(mg_http_parse_header(&h, "xx", buf, sizeof(buf)), 1); ASSERT_STREQ(buf, "1"); ASSERT_EQ(mg_http_parse_header(&h, "ii", buf, sizeof(buf)), 5); ASSERT_STREQ(buf, "12\"34"); ASSERT_EQ(mg_http_parse_header(&h, "tt", buf, sizeof(buf)), 1); ASSERT_STREQ(buf, "2"); ASSERT(mg_http_parse_header(&h, "uri", buf, sizeof(buf)) > 0); return NULL; } #if MG_ENABLE_COAP struct results { int server; int client; }; static void coap_handler_1(struct mg_connection *nc, int ev, void *p) { switch (ev) { case MG_EV_CONNECT: { struct mg_coap_message cm; memset(&cm, 0, sizeof(cm)); cm.msg_id = 1; cm.msg_type = MG_COAP_MSG_CON; mg_coap_send_message(nc, &cm); break; } case MG_EV_COAP_ACK: { struct mg_coap_message *cm = (struct mg_coap_message *) p; ((struct results *) (nc->user_data))->client = cm->msg_id + cm->msg_type; break; } case MG_EV_COAP_CON: { struct mg_coap_message *cm = (struct mg_coap_message *) p; ((struct results *) (nc->user_data))->server = cm->msg_id + cm->msg_type; mg_coap_send_ack(nc, cm->msg_id); break; } } } static const char *test_coap(void) { struct mbuf packet_in, packet_out; struct mg_coap_message cm; uint32_t res; unsigned char coap_packet_1[] = {0x42, 0x01, 0xe9, 0x1b, 0x07, 0x90, 0xb8, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x10, 0xd1, 0x23, 0x11}; unsigned char coap_packet_2[] = {0x60, 0x00, 0xe9, 0x1b}; unsigned char coap_packet_3[] = { 0x42, 0x45, 0x57, 0x0f, 0x07, 0x90, 0xff, 0x54, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x62, 0x79, 0x20, 0x61, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x0a, 0x59, 0x6f, 0x75, 0x72, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x63, 0x6b, 0x6e, 0x6f, 0x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x20, 0x69, 0x74, 0x2c, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x2e}; unsigned char coap_packet_4[] = {0x60, 0x00, 0x57, 0x0f}; unsigned char coap_packet_5[] = { 0x40, 0x03, 0x95, 0x22, 0xb7, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x0a, 0x6d, 0x79, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0xff, 0x6d, 0x79, 0x64, 0x61, 0x74, 0x61}; unsigned char coap_packet_6[] = {0xFF, 0x00, 0xFF, 0x00}; unsigned char coap_packet_7[] = { 0x40, 0x03, 0x95, 0x22, 0xb7, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x0a, 0x6d, 0x79, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0xf1, 0x6d, 0x79, 0x64, 0x61, 0x74, 0x61}; mbuf_init(&packet_in, 0); /* empty buf */ res = mg_coap_parse(&packet_in, &cm); ASSERT((res & MG_COAP_NOT_ENOUGH_DATA) != 0); mg_coap_free_options(&cm); mbuf_free(&packet_in); mbuf_init(&packet_out, 0); /* ACK, MID: 59675, Empty Message */ packet_in.buf = (char *) coap_packet_2; packet_in.len = sizeof(coap_packet_2); res = mg_coap_parse(&packet_in, &cm); ASSERT_EQ((res & MG_COAP_ERROR), 0); ASSERT_EQ(cm.code_class, 0); ASSERT_EQ(cm.code_detail, 0); ASSERT_EQ(cm.msg_id, 59675); ASSERT_EQ(cm.msg_type, MG_COAP_MSG_ACK); ASSERT(cm.options == NULL); ASSERT_EQ(cm.payload.len, 0); ASSERT(cm.payload.p == NULL); ASSERT_EQ(cm.token.len, 0); ASSERT(cm.token.p == NULL); res = mg_coap_compose(&cm, &packet_out); ASSERT_EQ(res, 0); ASSERT_EQ(packet_out.len, sizeof(coap_packet_2)); ASSERT_EQ(memcmp(packet_out.buf, coap_packet_2, packet_out.len), 0); mg_coap_free_options(&cm); mbuf_free(&packet_out); /* ACK, MID: 22287, Empty Message */ packet_in.buf = (char *) coap_packet_4; packet_in.len = sizeof(coap_packet_4); res = mg_coap_parse(&packet_in, &cm); ASSERT_EQ((res & MG_COAP_ERROR), 0); ASSERT_EQ(cm.code_class, 0); ASSERT_EQ(cm.code_detail, 0); ASSERT_EQ(cm.msg_id, 22287); ASSERT_EQ(cm.msg_type, MG_COAP_MSG_ACK); ASSERT(cm.options == NULL); ASSERT_EQ(cm.payload.len, 0); ASSERT(cm.payload.p == NULL); ASSERT_EQ(cm.token.len, 0); ASSERT(cm.token.p == NULL); res = mg_coap_compose(&cm, &packet_out); ASSERT_EQ(res, 0); ASSERT_EQ(packet_out.len, sizeof(coap_packet_4)); ASSERT_EQ(memcmp(packet_out.buf, coap_packet_4, packet_out.len), 0); mg_coap_free_options(&cm); mbuf_free(&packet_out); /* CON, MID: 59675 ... */ packet_in.buf = (char *) coap_packet_1; packet_in.len = sizeof(coap_packet_1); res = mg_coap_parse(&packet_in, &cm); ASSERT_EQ((res & MG_COAP_ERROR), 0); ASSERT_EQ(cm.code_class, 0); ASSERT_EQ(cm.code_detail, 1); ASSERT_EQ(cm.msg_id, 59675); ASSERT_EQ(cm.msg_type, MG_COAP_MSG_CON); ASSERT(cm.options != 0); ASSERT_EQ(cm.options->number, 11); ASSERT_EQ(cm.options->value.len, 8); ASSERT_STREQ_NZ(cm.options->value.p, "separate"); ASSERT(cm.options->next != 0); ASSERT_EQ(cm.options->next->number, 12); ASSERT_EQ(cm.options->next->value.len, 0); ASSERT(cm.options->next->next != 0); ASSERT_EQ(cm.options->next->next->number, 60); ASSERT_EQ(cm.options->next->next->value.len, 1); ASSERT_EQ(*cm.options->next->next->value.p, 0x11); ASSERT(cm.options->next->next->next == NULL); ASSERT_EQ(cm.payload.len, 0); ASSERT(cm.payload.p == NULL); ASSERT_EQ(cm.token.len, 2); ASSERT_EQ(*cm.token.p, 0x07); ASSERT_EQ((unsigned char) *(cm.token.p + 1), 0x90); res = mg_coap_compose(&cm, &packet_out); ASSERT_EQ(res, 0); ASSERT_EQ(packet_out.len, sizeof(coap_packet_1)); ASSERT_EQ(memcmp(packet_out.buf, coap_packet_1, packet_out.len), 0); mg_coap_free_options(&cm); mbuf_free(&packet_out); /* CON, MID: 22287 ... */ packet_in.buf = (char *) coap_packet_3; packet_in.len = sizeof(coap_packet_3); res = mg_coap_parse(&packet_in, &cm); ASSERT_EQ((res & MG_COAP_ERROR), 0); ASSERT_EQ(cm.code_class, 2); ASSERT_EQ(cm.code_detail, 5); ASSERT_EQ(cm.msg_id, 22287); ASSERT_EQ(cm.msg_type, MG_COAP_MSG_CON); ASSERT(cm.options == NULL); ASSERT_EQ(cm.token.len, 2); ASSERT_EQ(*cm.token.p, 0x07); ASSERT_EQ((unsigned char) *(cm.token.p + 1), 0x90); ASSERT_EQ(cm.payload.len, 122); ASSERT(strncmp(cm.payload.p, "This message was sent by a separate response.\n" "Your client will need to acknowledge it," " otherwise it will be retransmitted.", 122) == 0); res = mg_coap_compose(&cm, &packet_out); ASSERT_EQ(res, 0); ASSERT_EQ(packet_out.len, sizeof(coap_packet_3)); ASSERT_EQ(memcmp(packet_out.buf, coap_packet_3, packet_out.len), 0); mg_coap_free_options(&cm); mbuf_free(&packet_out); packet_in.buf = (char *) coap_packet_5; packet_in.len = sizeof(coap_packet_5); res = mg_coap_parse(&packet_in, &cm); ASSERT_EQ((res & MG_COAP_ERROR), 0); ASSERT_EQ(cm.code_class, 0); ASSERT_EQ(cm.code_detail, 3); ASSERT_EQ(cm.msg_id, 38178); ASSERT_EQ(cm.msg_type, MG_COAP_MSG_CON); ASSERT(cm.options != 0); ASSERT_EQ(cm.options->number, 11); ASSERT_EQ(cm.options->value.len, 7); ASSERT_STREQ_NZ(cm.options->value.p, "storage"); ASSERT(cm.options->next != 0); ASSERT_EQ(cm.options->next->number, 11); ASSERT_EQ(cm.options->next->value.len, 10); ASSERT_STREQ_NZ(cm.options->next->value.p, "myresource"); ASSERT(cm.options->next->next == NULL); ASSERT_EQ(cm.token.len, 0); ASSERT_EQ(cm.payload.len, 6); ASSERT_STREQ_NZ(cm.payload.p, "mydata"); res = mg_coap_compose(&cm, &packet_out); ASSERT_EQ(res, 0); ASSERT_EQ(packet_out.len, sizeof(coap_packet_5)); ASSERT_EQ(memcmp(packet_out.buf, coap_packet_5, packet_out.len), 0); mg_coap_free_options(&cm); mbuf_free(&packet_out); packet_in.buf = (char *) coap_packet_6; packet_in.len = sizeof(coap_packet_6); res = mg_coap_parse(&packet_in, &cm); ASSERT((res & MG_COAP_ERROR) != 0); mg_coap_free_options(&cm); packet_in.buf = (char *) coap_packet_7; packet_in.len = sizeof(coap_packet_7); res = mg_coap_parse(&packet_in, &cm); ASSERT((res & MG_COAP_ERROR) != 0); mg_coap_free_options(&cm); { unsigned char coap_packet_2_broken[] = {0x6F, 0x00, 0xe9, 0x1b}; packet_in.buf = (char *) coap_packet_2_broken; packet_in.len = sizeof(coap_packet_2_broken); res = mg_coap_parse(&packet_in, &cm); ASSERT((res & MG_COAP_FORMAT_ERROR) != 0); } { unsigned char coap_packet_2_broken[] = {0x65, 0x00, 0xe9, 0x1b}; packet_in.buf = (char *) coap_packet_2_broken; packet_in.len = sizeof(coap_packet_2_broken); res = mg_coap_parse(&packet_in, &cm); ASSERT((res & MG_COAP_NOT_ENOUGH_DATA) != 0); } memset(&cm, 0, sizeof(cm)); mg_coap_add_option(&cm, 10, 0, 0); ASSERT_EQ(cm.options->number, 10); ASSERT(cm.options->next == NULL); mg_coap_add_option(&cm, 5, 0, 0); ASSERT_EQ(cm.options->number, 5); ASSERT_EQ(cm.options->next->number, 10); ASSERT(cm.options->next->next == NULL); mg_coap_add_option(&cm, 7, 0, 0); ASSERT_EQ(cm.options->number, 5); ASSERT_EQ(cm.options->next->number, 7); ASSERT_EQ(cm.options->next->next->number, 10); ASSERT(cm.options->next->next->next == NULL); mg_coap_add_option(&cm, 1, 0, 0); ASSERT_EQ(cm.options->number, 1); ASSERT_EQ(cm.options->next->number, 5); ASSERT_EQ(cm.options->next->next->number, 7); ASSERT_EQ(cm.options->next->next->next->number, 10); ASSERT(cm.options->next->next->next->next == NULL); mg_coap_free_options(&cm); { unsigned char value16[] = {0xCC, 0xDD}; packet_in.buf = (char *) coap_packet_4; packet_in.len = sizeof(coap_packet_4); res = mg_coap_parse(&packet_in, &cm); ASSERT_EQ((res & MG_COAP_ERROR), 0); mg_coap_add_option(&cm, 0xAABB, (char *) value16, sizeof(value16)); res = mg_coap_compose(&cm, &packet_out); mg_coap_free_options(&cm); ASSERT_EQ(res, 0); res = mg_coap_parse(&packet_out, &cm); ASSERT_EQ((res & MG_COAP_ERROR), 0); ASSERT_EQ(cm.options->number, 0xAABB); ASSERT_EQ(cm.options->value.len, 2); ASSERT_EQ(memcmp(cm.options->value.p, value16, cm.options->value.len), 0); mg_coap_free_options(&cm); mbuf_free(&packet_out); } memset(&cm, 0, sizeof(cm)); cm.msg_id = 1; cm.msg_type = MG_COAP_MSG_MAX + 1; res = mg_coap_compose(&cm, &packet_out); ASSERT((res & MG_COAP_ERROR) != 0 && (res & MG_COAP_MSG_TYPE_FIELD) != 0); cm.msg_type = MG_COAP_MSG_ACK; cm.token.len = 10000; res = mg_coap_compose(&cm, &packet_out); ASSERT((res & MG_COAP_ERROR) != 0 && (res & MG_COAP_TOKEN_FIELD) != 0); cm.token.len = 0; cm.code_class = 0xFF; res = mg_coap_compose(&cm, &packet_out); ASSERT((res & MG_COAP_ERROR) != 0 && (res & MG_COAP_CODE_CLASS_FIELD) != 0); cm.code_class = 0; cm.code_detail = 0xFF; res = mg_coap_compose(&cm, &packet_out); ASSERT((res & MG_COAP_ERROR) != 0 && (res & MG_COAP_CODE_DETAIL_FIELD) != 0); cm.code_detail = 0; mg_coap_add_option(&cm, 0xFFFFFFF, 0, 0); res = mg_coap_compose(&cm, &packet_out); ASSERT((res & MG_COAP_ERROR) != 0 && (res & MG_COAP_OPTIOMG_FIELD) != 0); mg_coap_free_options(&cm); { struct mg_mgr mgr; struct mg_connection *nc; const char *address = "tcp://127.0.0.1:8686"; mg_mgr_init(&mgr, 0); nc = mg_bind(&mgr, address, coap_handler_1); ASSERT(nc != NULL); ASSERT_EQ(mg_set_protocol_coap(nc), -1); mg_mgr_free(&mgr); } { struct results res; struct mg_mgr mgr; struct mg_connection *nc1, *nc2; const char *address = "udp://127.0.0.1:5683"; mg_mgr_init(&mgr, 0); nc1 = mg_bind(&mgr, address, coap_handler_1); mg_set_protocol_coap(nc1); nc1->user_data = &res; nc2 = mg_connect(&mgr, address, coap_handler_1); mg_set_protocol_coap(nc2); nc2->user_data = &res; poll_until(&mgr, 5, c_int_eq, &res.client, (void *) 3); mg_mgr_free(&mgr); ASSERT_EQ(res.server, 1); ASSERT_EQ(res.client, 3); } return NULL; } #endif static const char *test_strcmp(void) { struct mg_str s1; s1.p = "aa"; s1.len = strlen(s1.p); ASSERT_EQ(mg_vcasecmp(&s1, "aa"), 0); ASSERT_EQ(mg_vcmp(&s1, "aa"), 0); ASSERT(mg_vcasecmp(&s1, "ab") < 0); ASSERT(mg_vcmp(&s1, "ab") < 0); ASSERT(mg_vcasecmp(&s1, "abb") < 0); ASSERT(mg_vcmp(&s1, "abb") < 0); ASSERT(mg_vcasecmp(&s1, "b") < 0); ASSERT(mg_vcmp(&s1, "b") < 0); return NULL; } #if MG_ENABLE_SNTP static const char *test_sntp(void) { const char sntp_good[] = "\x24\x02\x00\xeb\x00\x00\x00\x1e\x00\x00\x07\xb6\x3e" "\xc9\xd6\xa2\xdb\xde\xea\x30\x91\x86\xb7\x10\xdb\xde" "\xed\x98\x00\x00\x00\xde\xdb\xde\xed\x99\x0a\xe2\xc7" "\x96\xdb\xde\xed\x99\x0a\xe4\x6b\xda"; const char bad_good[] = "\x55\x02\x00\xeb\x00\x00\x00\x1e\x00\x00\x07\xb6\x3e" "\xc9\xd6\xa2\xdb\xde\xea\x30\x91\x86\xb7\x10\xdb\xde" "\xed\x98\x00\x00\x00\xde\xdb\xde\xed\x99\x0a\xe2\xc7" "\x96\xdb\xde\xed\x99\x0a\xe4\x6b\xda"; int res; struct mg_sntp_message msg; struct tm *tm; time_t t; memset(&msg, 0, sizeof(0)); res = mg_sntp_parse_reply(sntp_good, sizeof(sntp_good) - 1, &msg); ASSERT_EQ(res, 0); t = (time_t) msg.time; tm = gmtime(&t); ASSERT_EQ(tm->tm_year, 116); ASSERT_EQ(tm->tm_mon, 10); ASSERT_EQ(tm->tm_mday, 22); ASSERT_EQ(tm->tm_hour, 16); ASSERT_EQ(tm->tm_min, 15); ASSERT_EQ(tm->tm_sec, 21); res = mg_sntp_parse_reply(bad_good, sizeof(bad_good) - 1, &msg); ASSERT_EQ(res, -1); return NULL; } #endif #if MG_ENABLE_SOCKS static const char *test_socks(void) { struct mg_mgr mgr; struct mg_connection *c; struct mg_connect_opts opts; const char *this_binary; const char *web_addr = "127.0.0.1:7257"; const char *proxy_addr = "127.0.0.1:7258"; char status[100] = ""; mg_mgr_init(&mgr, NULL); #if 0 mgr.hexdump_file = "-"; #endif #if 1 cs_log_set_level(LL_INFO); #endif /* Start web server */ ASSERT((c = mg_bind(&mgr, web_addr, cb1)) != NULL); mg_set_protocol_http_websocket(c); /* Start socks proxy */ ASSERT((c = mg_bind(&mgr, proxy_addr, ev_handler_empty)) != NULL); mg_set_protocol_socks(c); /* Create HTTP client that uses socks proxy */ memset(&opts, 0, sizeof(opts)); opts.iface = mg_socks_mk_iface(&mgr, proxy_addr); ASSERT((c = mg_connect_opt(&mgr, web_addr, cb7, opts)) != NULL); mg_set_protocol_http_websocket(c); c->user_data = status; /* Wine and GDB set argv0 to full path: strip the dir component */ if ((this_binary = strrchr(s_argv_0, '\\')) != NULL) { this_binary++; } else if ((this_binary = strrchr(s_argv_0, '/')) != NULL) { this_binary++; } else { this_binary = s_argv_0; } mg_printf(c, "GET /%s HTTP/1.0\n\n", this_binary); /* Run event loop. Use more cycles to let file download complete. */ poll_until(&mgr, 5, c_str_ne, status, (void *) ""); mg_mgr_free(&mgr); ASSERT_STREQ(status, "success"); return NULL; } #endif static const char *run_tests(const char *filter, double *total_elapsed) { RUN_TEST(test_mbuf); RUN_TEST(test_parse_uri); RUN_TEST(test_assemble_uri); RUN_TEST(test_parse_address); RUN_TEST(test_mg_normalize_uri_path); RUN_TEST(test_mg_uri_to_local_path); RUN_TEST(test_mg_url_encode); RUN_TEST(test_check_ip_acl); RUN_TEST(test_connect_opts); RUN_TEST(test_connect_opts_error_string); RUN_TEST(test_to64); RUN_TEST(test_alloc_vprintf); RUN_TEST(test_socketpair); RUN_TEST(test_timer); #ifdef __linux__ RUN_TEST(test_simple); #endif #if MG_ENABLE_THREADS RUN_TEST(test_thread); #endif RUN_TEST(test_mgr); RUN_TEST(test_parse_http_message); RUN_TEST(test_get_http_var); RUN_TEST(test_http_serve_file); RUN_TEST(test_http_serve_file_streaming); RUN_TEST(test_http); RUN_TEST(test_http_send_redirect); RUN_TEST(test_http_digest_auth); RUN_TEST(test_http_errors); RUN_TEST(test_http_index); RUN_TEST(test_http_parse_header); RUN_TEST(test_ssi); #ifndef NO_CGI_TEST RUN_TEST(test_cgi); #endif RUN_TEST(test_http_rewrites); RUN_TEST(test_http_dav); RUN_TEST(test_http_range); RUN_TEST(test_http_multipart); #if MG_ENABLE_HTTP_STREAMING_MULTIPART RUN_TEST(test_http_multipart2); #endif RUN_TEST(test_parse_date_string); RUN_TEST(test_websocket); RUN_TEST(test_websocket_endpoint); RUN_TEST(test_connect_ws); RUN_TEST(test_websocket_big); RUN_TEST(test_http_chunk); RUN_TEST(test_http_chunk2); RUN_TEST(test_http_not_modified); RUN_TEST(test_http_extra_headers); RUN_TEST(test_http_endpoints); RUN_TEST(test_mqtt_handshake); RUN_TEST(test_mqtt_publish); RUN_TEST(test_mqtt_subscribe); RUN_TEST(test_mqtt_unsubscribe); RUN_TEST(test_mqtt_connack); RUN_TEST(test_mqtt_suback); RUN_TEST(test_mqtt_simple_acks); RUN_TEST(test_mqtt_nullary); RUN_TEST(test_mqtt_parse_mqtt); RUN_TEST(test_mqtt_parse_mqtt_qos1); RUN_TEST(test_mqtt_match_topic_expression); RUN_TEST(test_mqtt_client_keep_alive); #if MG_ENABLE_MQTT_BROKER RUN_TEST(test_mqtt_broker); #endif RUN_TEST(test_dns_encode); RUN_TEST(test_dns_uncompress); RUN_TEST(test_dns_decode); RUN_TEST(test_dns_decode_truncated); RUN_TEST(test_dns_reply_encode); #if MG_ENABLE_DNS_SERVER RUN_TEST(test_dns_server); #endif RUN_TEST(test_dns_resolve); RUN_TEST(test_dns_resolve_timeout); #ifndef NO_RESOLVE_HOSTS_TEST RUN_TEST(test_dns_resolve_hosts); #endif RUN_TEST(test_buffer_limit); RUN_TEST(test_connection_errors); RUN_TEST(test_connect_fail); #ifndef NO_DNS_TEST RUN_TEST(test_resolve); #endif RUN_TEST(test_base64); RUN_TEST(test_sock_addr_to_str); RUN_TEST(test_hexdump); RUN_TEST(test_hexdump_file); RUN_TEST(test_basic_auth_helpers); RUN_TEST(test_http_auth); #if MG_ENABLE_SSL RUN_TEST(test_ssl); #ifdef OPENSSL_VERSION_NUMBER RUN_TEST(test_modern_crypto); #endif #endif RUN_TEST(test_udp); #if MG_ENABLE_COAP RUN_TEST(test_coap); #endif #if MG_ENABLE_SNTP RUN_TEST(test_sntp); #endif RUN_TEST(test_strcmp); #if MG_ENABLE_SOCKS RUN_TEST(test_socks); #endif return NULL; } #if defined(_MSC_VER) && _MSC_VER >= 1900 int __cdecl CrtDbgHook(int nReportType, char *szMsg, int *pnRet) { (void) nReportType; (void) szMsg; (void) pnRet; fprintf(stderr, "CRT debug hook: type: %d, msg: %s\n", nReportType, szMsg); /* Return true - Abort,Retry,Ignore dialog will *not* be displayed */ return 1; } #endif void setup() { test_iface = mg_if_create_iface(mg_ifaces[MG_MAIN_IFACE], NULL); } void teardown() { free(test_iface); } int __cdecl main(int argc, char *argv[]) { const char *fail_msg; const char *filter = argc > 1 ? argv[1] : ""; double total_elapsed = 0.0; setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); #if defined(_MSC_VER) && _MSC_VER >= 1900 /* NOTE: not available on wine/vc6 */ _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, CrtDbgHook); #endif setup(); s_argv_0 = argv[0]; fail_msg = run_tests(filter, &total_elapsed); printf("%s, run %d in %.3lfs\n", fail_msg ? "FAIL" : "PASS", num_tests, total_elapsed); teardown(); if (fail_msg != NULL) { /* Prevent leak analyzer from running: there will be "leaks" because of * premature return from the test, and in this case we don't care. */ _exit(EXIT_FAILURE); } return EXIT_SUCCESS; }