mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-12 10:13:29 +00:00
E.g. if `ASSERT_EQ(myfunc(), 123)` fails, `myfunc()` would be invoked again to print the actual value. This can cause confusion sometimes. To avoid this, we cast arguments to double, which is wide enough for most cases and we provide a variant for 64 bit value types for when it's not. We also perform a check and fail if argument to the check results in loss of precision. CL: none PUBLISHED_FROM=a14551289d92e8a5dead21d16471ebddebe938a8
5553 lines
172 KiB
C
5553 lines
172 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 <https://www.cesanta.com/license>.
|
|
*/
|
|
|
|
#include "mongoose.h"
|
|
#include "src/mg_internal.h"
|
|
#include "unit_test.h"
|
|
#include "common/test_main.h"
|
|
#include "common/test_util.h"
|
|
#include "common/cs_md5.h"
|
|
|
|
#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 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_EQ64(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_EQ64(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(g_argv_0, &size);
|
|
DBG(("file %s, size %d; got %d", g_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(g_argv_0, '\\')) != NULL) {
|
|
this_binary++;
|
|
} else if ((this_binary = strrchr(g_argv_0, '/')) != NULL) {
|
|
this_binary++;
|
|
} else {
|
|
this_binary = g_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"
|
|
"<p>Moved <a href='http://foo'>here</a>.\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"
|
|
"<p>Moved <a href='/bar'>here</a>.\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, "<html><head><title>") != 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_EQ64(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];
|
|
char *buf2;
|
|
|
|
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);
|
|
|
|
buf2 = buf;
|
|
ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 2), 3);
|
|
ASSERT(buf2 != buf);
|
|
free(buf2);
|
|
|
|
buf2 = buf;
|
|
ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 3), 3);
|
|
ASSERT(buf2 != buf);
|
|
free(buf2);
|
|
|
|
buf2 = buf;
|
|
ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 4), 3);
|
|
ASSERT(buf2 == buf);
|
|
|
|
buf2 = NULL;
|
|
ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 0), 3);
|
|
ASSERT_STREQ(buf2, "234");
|
|
free(buf2);
|
|
|
|
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(g_argv_0, '\\')) != NULL) {
|
|
this_binary++;
|
|
} else if ((this_binary = strrchr(g_argv_0, '/')) != NULL) {
|
|
this_binary++;
|
|
} else {
|
|
this_binary = g_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
|
|
|
|
void tests_setup(void) {
|
|
test_iface = mg_if_create_iface(mg_ifaces[MG_MAIN_IFACE], NULL);
|
|
}
|
|
|
|
const char *tests_run(const char *filter) {
|
|
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;
|
|
}
|
|
|
|
void tests_teardown(void) {
|
|
free(test_iface);
|
|
}
|