#include "mongoose.h" #include "float.h" // For DBL_EPSILON and HUGE_VAL #include "math.h" static int s_num_tests = 0; static bool s_error = false; #ifdef NO_ABORT static int s_abort = 0; #define ABORT() ++s_abort, s_error = true #else #ifdef NO_SLEEP_ABORT #define ABORT() abort() #else #define ABORT() \ sleep(2); /* 2s, GH print reason */ \ abort(); #endif #endif #define ASSERT(expr) \ do { \ s_num_tests++; \ if (!(expr)) { \ printf("FAILURE %s:%d: %s\n", __FILE__, __LINE__, #expr); \ fflush(stdout); \ ABORT(); \ } \ } while (0) #define FETCH_BUF_SIZE (256 * 1024) // Important: we use different port numbers for the Windows bug workaround. See // https://support.microsoft.com/en-ae/help/3039044/error-10013-wsaeacces-is-returned-when-a-second-bind-to-a-excluded-por static void test_match(void) { ASSERT(mg_match(mg_str_n("", 0), mg_str_n("", 0), NULL) == 1); ASSERT(mg_match(mg_str_n("a", 1), mg_str_n("*", 1), NULL) == 1); ASSERT(mg_match(mg_str_n("ab", 2), mg_str_n("*", 1), NULL) == 1); ASSERT(mg_match(mg_str_n("a", 1), mg_str_n("", 0), NULL) == 0); ASSERT(mg_match(mg_str_n("/foo", 4), mg_str_n("/", 1), NULL) == 0); ASSERT(mg_match(mg_str_n("/x/bar", 6), mg_str_n("/*/foo", 6), NULL) == 0); ASSERT(mg_match(mg_str_n("/x/foo", 6), mg_str_n("/*/foo", 6), NULL) == 1); ASSERT(mg_match(mg_str_n("/x/foox", 7), mg_str_n("/*/foo", 6), NULL) == 0); ASSERT(mg_match(mg_str_n("/x/foox", 7), mg_str_n("/*/foo*", 7), NULL) == 1); ASSERT(mg_match(mg_str_n("/abc", 4), mg_str_n("/*", 2), NULL) == 1); ASSERT(mg_match(mg_str_n("/ab/", 4), mg_str_n("/*", 2), NULL) == 0); ASSERT(mg_match(mg_str_n("/", 1), mg_str_n("/*", 2), NULL) == 1); ASSERT(mg_match(mg_str_n("/x/2", 4), mg_str_n("/x/*", 4), NULL) == 1); ASSERT(mg_match(mg_str_n("/x/2/foo", 8), mg_str_n("/x/*", 4), NULL) == 0); ASSERT(mg_match(mg_str_n("/x/2/foo", 8), mg_str_n("/x/*/*", 6), NULL) == 1); ASSERT(mg_match(mg_str_n("///", 3), mg_str_n("#", 1), NULL) == 1); ASSERT(mg_match(mg_str_n("/api/foo", 8), mg_str_n("/api/*", 6), NULL) == 1); ASSERT(mg_match(mg_str_n("/api/log/static", 15), mg_str_n("/api/*", 6), NULL) == 0); ASSERT(mg_match(mg_str_n("/api/log/static", 15), mg_str_n("/api/#", 6), NULL) == 1); ASSERT(mg_match(mg_str_n("/ssi/index.shtml", 16), mg_str_n("#.shtml", 7), NULL) == 1); ASSERT(mg_match(mg_str_n(".c", 2), mg_str_n("#.c", 3), NULL) == 1); ASSERT(mg_match(mg_str_n("ab", 2), mg_str_n("abc", 3), NULL) == 0); ASSERT(mg_match(mg_str_n("a.c", 3), mg_str_n("#.c", 3), NULL) == 1); ASSERT(mg_match(mg_str_n("..c", 3), mg_str_n("#.c", 3), NULL) == 1); ASSERT(mg_match(mg_str_n("/.c", 3), mg_str_n("#.c", 3), NULL) == 1); ASSERT(mg_match(mg_str_n("//a.c", 5), mg_str_n("#.c", 3), NULL) == 1); ASSERT(mg_match(mg_str_n("x/a.c", 5), mg_str_n("#.c", 3), NULL) == 1); ASSERT(mg_match(mg_str_n("./a.c", 5), mg_str_n("#.c", 3), NULL) == 1); ASSERT(mg_match(mg_str_n("./ssi/index.shtml", 17), mg_str_n("#.shtml", 7), NULL) == 1); ASSERT(mg_match(mg_str_n("caabba", 6), mg_str_n("#aa#bb#", 7), NULL) == 1); ASSERT(mg_match(mg_str_n("caabxa", 6), mg_str_n("#aa#bb#", 7), NULL) == 0); ASSERT(mg_match(mg_str_n("a__b_c", 6), mg_str_n("a*b*c", 5), NULL) == 1); { char *ae_value = (char *) malloc(1); // Exact size for ASAN ae_value[0] = 'g'; mg_match(mg_str_n(ae_value, 1), mg_str("*gzip*"), NULL); // Check for OOB free(ae_value); } { struct mg_str caps[3]; ASSERT(mg_match(mg_str("//a.c"), mg_str("#.c"), NULL) == true); ASSERT(mg_match(mg_str("a"), mg_str("#"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("a")) == 0); ASSERT(mg_match(mg_str("//a.c"), mg_str("#.c"), caps) == true); ASSERT(mg_match(mg_str("a_b_c_"), mg_str("a*b*c"), caps) == false); ASSERT(mg_match(mg_str("a__b_c"), mg_str("a*b*c"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("__")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("_")) == 0); ASSERT(mg_match(mg_str("a_b_c__c"), mg_str("a*b*c"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("_")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("_c__")) == 0); ASSERT(mg_match(mg_str("a_xb_.c__c"), mg_str("a*b*c"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("_x")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("_.c__")) == 0); ASSERT(mg_match(mg_str("a"), mg_str("#a"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("")) == 0); ASSERT(mg_match(mg_str(".aa..b...b"), mg_str("#a#b"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str(".")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("a..b...")) == 0); ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); ASSERT(mg_match(mg_str("/foo/bar"), mg_str("/*/*"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("foo")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("bar")) == 0); ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); ASSERT(mg_match(mg_str("/foo/"), mg_str("/*/*"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("foo")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("")) == 0); ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); ASSERT(mg_match(mg_str("abc"), mg_str("?#"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("a")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("bc")) == 0); ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); ASSERT(mg_match(mg_str("a#c"), mg_str("?#"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("a")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("#c")) == 0); ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); ASSERT(mg_match(mg_str("a*c"), mg_str("?*"), caps) == true); ASSERT(mg_strcmp(caps[0], mg_str("a")) == 0); ASSERT(mg_strcmp(caps[1], mg_str("*c")) == 0); ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); } } static void test_http_get_var(void) { char buf[256]; struct mg_str body; body = mg_str("key1=value1&key2=value2&key3=value%203&key4=value+4"); ASSERT(mg_http_get_var(&body, "key1", buf, sizeof(buf)) == 6); ASSERT(strcmp(buf, "value1") == 0); ASSERT(mg_http_get_var(&body, "KEY1", buf, sizeof(buf)) == 6); ASSERT(strcmp(buf, "value1") == 0); ASSERT(mg_http_get_var(&body, "key2", buf, sizeof(buf)) == 6); ASSERT(strcmp(buf, "value2") == 0); ASSERT(mg_http_get_var(&body, "key3", buf, sizeof(buf)) == 7); ASSERT(strcmp(buf, "value 3") == 0); ASSERT(mg_http_get_var(&body, "key4", buf, sizeof(buf)) == 7); ASSERT(strcmp(buf, "value 4") == 0); ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -4); ASSERT(mg_http_get_var(&body, "key1", NULL, sizeof(buf)) == -2); ASSERT(mg_http_get_var(&body, "key1", buf, 0) == -2); ASSERT(mg_http_get_var(&body, NULL, buf, sizeof(buf)) == -1); ASSERT(mg_http_get_var(&body, "key1", buf, 1) == -3); body = mg_str("key=broken%2"); ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -3); body = mg_str("key=broken%2x"); ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -3); memset(buf, 'x', sizeof(buf)); body = mg_str("access_token=a%zz"); ASSERT(mg_http_get_var(&body, "access_token", buf, sizeof(buf)) == -3); ASSERT(memchr(buf, '\0', sizeof(buf)) != NULL); ASSERT(mg_http_get_var(&body, "inexistent", buf, sizeof(buf)) == -4); body = mg_str("key=%"); ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -3); body = mg_str("&&&kEy=%"); ASSERT(mg_http_get_var(&body, "kEy", buf, sizeof(buf)) == -3); } static int vcmp(struct mg_str s1, const char *s2) { return mg_strcmp(s1, mg_str(s2)) == 0; } static bool is_space(int c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; } static struct mg_str strstrip(struct mg_str s) { while (s.len > 0 && is_space((int) *s.buf)) s.buf++, s.len--; while (s.len > 0 && is_space((int) *(s.buf + s.len - 1))) s.len--; return s; } static const char *mgstrstr(const struct mg_str haystack, const struct mg_str needle) { size_t i; if (needle.len > haystack.len) return NULL; if (needle.len == 0) return haystack.buf; for (i = 0; i <= haystack.len - needle.len; i++) { if (memcmp(haystack.buf + i, needle.buf, needle.len) == 0) { return haystack.buf + i; } } return NULL; } static void test_url(void) { // Host ASSERT(vcmp(mg_url_host("foo"), "foo")); ASSERT(vcmp(mg_url_host("//foo"), "foo")); ASSERT(vcmp(mg_url_host("foo:1234"), "foo")); ASSERT(vcmp(mg_url_host(":1234"), "")); ASSERT(vcmp(mg_url_host("//foo:1234"), "foo")); ASSERT(vcmp(mg_url_host("p://foo"), "foo")); ASSERT(vcmp(mg_url_host("p://foo/"), "foo")); ASSERT(vcmp(mg_url_host("p://foo/x"), "foo")); ASSERT(vcmp(mg_url_host("p://foo/x/"), "foo")); ASSERT(vcmp(mg_url_host("p://foo/x//"), "foo")); ASSERT(vcmp(mg_url_host("p://foo//x"), "foo")); ASSERT(vcmp(mg_url_host("p://foo///x"), "foo")); ASSERT(vcmp(mg_url_host("p://foo///x//"), "foo")); ASSERT(vcmp(mg_url_host("p://bar:1234"), "bar")); ASSERT(vcmp(mg_url_host("p://bar:1234/"), "bar")); ASSERT(vcmp(mg_url_host("p://bar:1234/a"), "bar")); ASSERT(vcmp(mg_url_host("p://u@bar:1234/a"), "bar")); ASSERT(vcmp(mg_url_host("p://u:p@bar:1234/a"), "bar")); ASSERT(vcmp(mg_url_host("p://u:p@[::1]:1234/a"), "[::1]")); ASSERT(vcmp(mg_url_host("p://u:p@[1:2::3]:1234/a"), "[1:2::3]")); ASSERT(vcmp(mg_url_host("p://foo/x:y/z"), "foo")); // Port ASSERT(mg_url_port("foo:1234") == 1234); ASSERT(mg_url_port(":1234") == 1234); ASSERT(mg_url_port("x://foo:1234") == 1234); ASSERT(mg_url_port("x://foo:1234/") == 1234); ASSERT(mg_url_port("x://foo:1234/xx") == 1234); ASSERT(mg_url_port("x://foo:1234") == 1234); ASSERT(mg_url_port("p://bar:1234/a") == 1234); ASSERT(mg_url_port("p://bar:1234/a:b") == 1234); ASSERT(mg_url_port("http://bar") == 80); ASSERT(mg_url_port("http://localhost:1234") == 1234); ASSERT(mg_url_port("https://bar") == 443); ASSERT(mg_url_port("wss://bar") == 443); ASSERT(mg_url_port("wss://u:p@bar") == 443); ASSERT(mg_url_port("wss://u:p@bar:123") == 123); ASSERT(mg_url_port("wss://u:p@bar:123/") == 123); ASSERT(mg_url_port("wss://u:p@bar:123/abc") == 123); ASSERT(mg_url_port("http://u:p@[::1]/abc") == 80); ASSERT(mg_url_port("http://u:p@[::1]:2121/abc") == 2121); ASSERT(mg_url_port("http://u:p@[::1]:2121/abc/cd:ef") == 2121); // User / pass ASSERT(vcmp(mg_url_user("p://foo"), "")); ASSERT(vcmp(mg_url_pass("p://foo"), "")); ASSERT(vcmp(mg_url_user("p://:@foo"), "")); ASSERT(vcmp(mg_url_pass("p://:@foo"), "")); ASSERT(vcmp(mg_url_user("p://u@foo"), "u")); ASSERT(vcmp(mg_url_pass("p://u@foo"), "")); ASSERT(vcmp(mg_url_user("p://u:@foo"), "u")); ASSERT(vcmp(mg_url_pass("p://u:@foo"), "")); ASSERT(vcmp(mg_url_user("p://:p@foo"), "")); ASSERT(vcmp(mg_url_pass("p://:p@foo"), "p")); ASSERT(vcmp(mg_url_user("p://u:p@foo"), "u")); ASSERT(vcmp(mg_url_pass("p://u:p@foo"), "p")); ASSERT(vcmp(mg_url_pass("p://u:p@foo//a@b"), "p")); ASSERT(vcmp(mg_url_user("p://foo/q?mail=a@b.c"), "")); ASSERT(vcmp(mg_url_pass("p://foo/q?mail=a@b.c"), "")); // URI ASSERT(strcmp(mg_url_uri("p://foo"), "/") == 0); ASSERT(strcmp(mg_url_uri("p://foo/"), "/") == 0); ASSERT(strcmp(mg_url_uri("p://foo:12/"), "/") == 0); ASSERT(strcmp(mg_url_uri("p://foo:12/abc"), "/abc") == 0); ASSERT(strcmp(mg_url_uri("p://foo:12/a/b/c"), "/a/b/c") == 0); ASSERT(strcmp(mg_url_uri("p://[::1]:12/a/b/c"), "/a/b/c") == 0); ASSERT(strcmp(mg_url_uri("p://[ab::1]:12/a/b/c"), "/a/b/c") == 0); ASSERT(strcmp(mg_url_uri("p://foo/q?mail=a@b.c"), "/q?mail=a@b.c") == 0); } static void test_base64(void) { char buf[128]; ASSERT(mg_base64_encode((uint8_t *) "", 0, buf, sizeof(buf)) == 0); ASSERT(strcmp(buf, "") == 0); ASSERT(mg_base64_encode((uint8_t *) "x", 1, buf, sizeof(buf)) == 4); ASSERT(strcmp(buf, "eA==") == 0); ASSERT(mg_base64_encode((uint8_t *) "xyz", 3, buf, sizeof(buf)) == 4); ASSERT(strcmp(buf, "eHl6") == 0); ASSERT(mg_base64_encode((uint8_t *) "abcdef", 6, buf, sizeof(buf)) == 8); ASSERT(strcmp(buf, "YWJjZGVm") == 0); ASSERT(mg_base64_encode((uint8_t *) "ы", 2, buf, sizeof(buf)) == 4); ASSERT(strcmp(buf, "0Ys=") == 0); ASSERT(mg_base64_encode((uint8_t *) "xy", 3, buf, sizeof(buf)) == 4); ASSERT(strcmp(buf, "eHkA") == 0); ASSERT(mg_base64_encode((uint8_t *) "test", 4, buf, sizeof(buf)) == 8); ASSERT(strcmp(buf, "dGVzdA==") == 0); ASSERT(mg_base64_encode((uint8_t *) "abcde", 5, buf, sizeof(buf)) == 8); ASSERT(strcmp(buf, "YWJjZGU=") == 0); ASSERT(mg_base64_encode((uint8_t *) "a", 1, buf, 0) == 0); ASSERT(mg_base64_encode((uint8_t *) "a", 1, buf, 1) == 0); ASSERT(mg_base64_encode((uint8_t *) "a", 1, buf, 2) == 0); ASSERT(mg_base64_encode((uint8_t *) "a", 1, buf, 3) == 0); ASSERT(mg_base64_encode((uint8_t *) "a", 1, buf, 4) == 0); ASSERT(mg_base64_encode((uint8_t *) "a", 1, buf, 5) == 4); ASSERT(mg_base64_decode("кю", 4, buf, sizeof(buf)) == 0); ASSERT(mg_base64_decode("A", 1, buf, sizeof(buf)) == 0); ASSERT(mg_base64_decode("A=", 2, buf, sizeof(buf)) == 0); ASSERT(mg_base64_decode("AA=", 3, buf, sizeof(buf)) == 0); ASSERT(mg_base64_decode("AAA=", 4, buf, sizeof(buf)) == 2); ASSERT(mg_base64_decode("AAAA====", 8, buf, sizeof(buf)) == 0); ASSERT(mg_base64_decode("AAAA----", 8, buf, sizeof(buf)) == 0); ASSERT(mg_base64_decode("Q2VzYW50YQ==", 12, buf, sizeof(buf)) == 7); ASSERT(strcmp(buf, "Cesanta") == 0); ASSERT(mg_base64_decode("AAA=", 4, buf, 0) == 0); ASSERT(mg_base64_decode("AAA=", 4, buf, 1) == 0); ASSERT(mg_base64_decode("AAA=", 4, buf, 2) == 0); ASSERT(mg_base64_decode("AAA=", 4, buf, 3) == 0); ASSERT(mg_base64_decode("AAA=", 4, buf, 4) == 2); ASSERT(mg_base64url_encode((uint8_t *) "???", 3, buf, sizeof(buf)) == 4); ASSERT(strcmp(buf, "Pz8_") == 0); ASSERT(mg_base64url_decode("Pz8_", 4, buf, sizeof(buf)) == 3); ASSERT(strcmp(buf, "???") == 0); ASSERT(mg_base64url_decode("Pz8/", 4, buf, sizeof(buf)) == 0); ASSERT(mg_base64url_decode("A", 1, buf, sizeof(buf)) == 0); } static void test_iobuf(void) { struct mg_iobuf io = {0, 0, 0, 10}; ASSERT(io.buf == NULL && io.size == 0 && io.len == 0); mg_iobuf_resize(&io, 1); ASSERT(io.buf != NULL && io.size == 10 && io.len == 0); ASSERT(memcmp(io.buf, "\x00", 1) == 0); mg_iobuf_add(&io, 3, "hi", 2); ASSERT(io.buf != NULL && io.size == 10 && io.len == 5); ASSERT(memcmp(io.buf, "\x00\x00\x00hi", 5) == 0); mg_iobuf_add(&io, io.len, "!", 1); ASSERT(io.buf != NULL && io.size == 10 && io.len == 6); ASSERT(memcmp(io.buf, "\x00\x00\x00hi!", 6) == 0); mg_iobuf_add(&io, 0, "x", 1); ASSERT(memcmp(io.buf, "x\x00\x00\x00hi!", 7) == 0); ASSERT(io.buf != NULL && io.size == 10 && io.len == 7); mg_iobuf_del(&io, 1, 3); ASSERT(io.buf != NULL && io.size == 10 && io.len == 4); ASSERT(memcmp(io.buf, "xhi!", 3) == 0); mg_iobuf_del(&io, 10, 100); ASSERT(io.buf != NULL && io.size == 10 && io.len == 4); ASSERT(memcmp(io.buf, "xhi!", io.len) == 0); mg_iobuf_add(&io, io.len, "123456", 6); ASSERT(io.buf != NULL && io.size == 10 && io.len == 10); mg_iobuf_add(&io, io.len, "a", 1); ASSERT(io.buf != NULL && io.size == 20 && io.len == 11); ASSERT(memcmp(io.buf, "xhi!123456a", io.len) == 0); mg_iobuf_resize(&io, 1); ASSERT(io.buf != NULL && io.size == 10 && io.len == 10); mg_iobuf_free(&io); } static void sntp_cb(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_SNTP_TIME) { int64_t received = *(int64_t *) ev_data; *(int64_t *) c->fn_data = received; MG_DEBUG(("got time: %lld", received)); #if MG_ARCH == MG_ARCH_UNIX && defined(__STDC__) && defined(__STDC_VERSION__) struct timeval tv = {0, 0}; gettimeofday(&tv, 0); int64_t ms = (int64_t) tv.tv_sec * 1000 + tv.tv_usec / 1000; int64_t diff = ms > received ? ms - received : received - ms; MG_DEBUG(("diff: %lld", diff)); // ASSERT(diff < 100); #endif } else if (ev == MG_EV_OPEN) { c->is_hexdumping = 1; } (void) c; } static bool test_sntp_server(const char *url) { int64_t ms = 0; struct mg_mgr mgr; struct mg_connection *c = NULL; int i; mg_mgr_init(&mgr); c = mg_sntp_connect(&mgr, url, sntp_cb, &ms); ASSERT(c != NULL); ASSERT(c->is_udp == 1); for (i = 0; i < 60 && ms == 0; i++) mg_mgr_poll(&mgr, 50); MG_DEBUG(("server: %s, ms: %lld", url ? url : "(default)", ms)); mg_mgr_free(&mgr); return ms > 0; } static void test_sntp(void) { bool result; const unsigned char bad[] = "\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"; ASSERT(mg_sntp_parse(bad, sizeof(bad)) < 0); ASSERT(mg_sntp_parse(NULL, 0) == -1); // NOTE(): historical NTP port blockage issue; expect at least one to be // reachable and work. https://github.com/actions/runner-images/issues/5615 result = test_sntp_server("udp://time.apple.com:123") || test_sntp_server("udp://time.windows.com:123") || test_sntp_server(NULL); #if defined(NO_SNTP_CHECK) (void) result; #else ASSERT(result); #endif } #ifdef MQTT_LOCALHOST #define MQTT_URL "mqtt://127.0.0.1:1883" #else #define MQTT_URL "mqtt://broker.hivemq.com:1883" #endif #if MG_TLS == MG_TLS_BUILTIN #define MQTTS_URL "mqtts://mongoose.ws:8883" // test requires TLS 1.3 #define MQTTS_CA mg_str(s_ca_cert) static const char *s_ca_cert = "-----BEGIN CERTIFICATE-----\n" "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" "-----END CERTIFICATE-----\n"; #elif MG_TLS #ifdef MQTT_LOCALHOST #define MQTTS_URL "mqtts://127.0.0.1:8883" #define MQTTS_CA mg_str(s_ca_cert) static const char *s_ca_cert = "-----BEGIN CERTIFICATE-----\n" "MIIBFTCBvAIJAMNTFtpfcq8NMAoGCCqGSM49BAMCMBMxETAPBgNVBAMMCE1vbmdv\n" "b3NlMB4XDTI0MDUwNzE0MzczNloXDTM0MDUwNTE0MzczNlowEzERMA8GA1UEAwwI\n" "TW9uZ29vc2UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASuP+86T/rOWnGpEVhl\n" "fxYZ+pjMbCmDZ+vdnP0rjoxudwRMRQCv5slRlDK7Lxue761sdvqxWr0Ma6TFGTNg\n" "epsRMAoGCCqGSM49BAMCA0gAMEUCIQCwb2CxuAKm51s81S6BIoy1IcandXSohnqs\n" "us64BAA7QgIgGGtUrpkgFSS0oPBlCUG6YPHFVw42vTfpTC0ySwAS0M4=\n" "-----END CERTIFICATE-----\n"; #else #define MQTTS_URL "mqtts://broker.hivemq.com:8883" #define MQTTS_CA mg_unpacked("/data/ca.pem") #endif // MQTT_LOCALHOST #endif struct mqtt_data { char *topic; char *msg; size_t topicsize; size_t msgsize; int flags; }; #define flags_subscribed (1 << 0) #define flags_published (1 << 1) #define flags_received (1 << 2) #define flags_released (1 << 3) #define flags_completed (1 << 4) #define flags_unsubscribed (1 << 5) static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data) { struct mqtt_data *test_data = (struct mqtt_data *) c->fn_data; char *buf = test_data->msg; #if MG_TLS if (c->is_tls && ev == MG_EV_CONNECT) { struct mg_tls_opts opts; memset(&opts, 0, sizeof(opts)); opts.ca = MQTTS_CA; #if defined(MQTT_LOCALHOST) && MG_TLS != MG_TLS_BUILTIN printf("\nHostname not tested\n"); #else opts.name = mg_url_host(MQTTS_URL); #endif mg_tls_init(c, &opts); } else #endif if (ev == MG_EV_MQTT_OPEN) { buf[0] = *(int *) ev_data == 0 ? 'X' : 'Y'; } else if (ev == MG_EV_CLOSE) { buf[0] = 0; } else if (ev == MG_EV_MQTT_CMD) { struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; if (mm->cmd == MQTT_CMD_SUBACK) { test_data->flags = flags_subscribed; } else if (mm->cmd == MQTT_CMD_PUBACK) { // here we assume the broker test_data->flags = flags_published; // reported no errors, } else if (mm->cmd == MQTT_CMD_PUBREC) { // either no var header or test_data->flags |= flags_received; // reason code 0x00 } else if (mm->cmd == MQTT_CMD_PUBREL) { test_data->flags |= flags_released; } else if (mm->cmd == MQTT_CMD_PUBCOMP) { test_data->flags |= flags_completed; } else if (mm->cmd == MQTT_CMD_UNSUBACK) { test_data->flags = flags_unsubscribed; } } else if (ev == MG_EV_MQTT_MSG) { struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data; snprintf(test_data->topic, test_data->topicsize, "%.*s", (int) mm->topic.len, mm->topic.buf); snprintf(buf + 1, test_data->msgsize - 2, "%.*s", (int) mm->data.len, mm->data.buf); if (mm->cmd == MQTT_CMD_PUBLISH && c->is_mqtt5) { size_t pos = 0, i = 0, j = 0; struct mg_mqtt_prop prop; for (i = 0; i < 5; i++) { ASSERT((pos = mg_mqtt_next_prop(mm, &prop, pos)) > 0); if (prop.id == MQTT_PROP_MESSAGE_EXPIRY_INTERVAL) { ASSERT(prop.iv == 10); j += 1; } else if (prop.id == MQTT_PROP_PAYLOAD_FORMAT_INDICATOR) { j += 2; continue; } else if (prop.id == MQTT_PROP_CONTENT_TYPE) { ASSERT(strncmp(prop.val.buf, "test_content_val_2", prop.val.len) == 0 && prop.val.len == strlen("test_content_val_2")); j += 4; } else if (prop.id == MQTT_PROP_USER_PROPERTY) { if (strncmp(prop.key.buf, "test_key_1", prop.key.len) == 0 && prop.key.len == strlen("test_key_1")) { ASSERT(strncmp(prop.val.buf, "test_value_1", prop.val.len) == 0 && prop.val.len == strlen("test_value_1")); j += 8; } else if (strncmp(prop.key.buf, "test_key_2", prop.key.len) == 0 && prop.key.len == strlen("test_key_2")) { ASSERT(strncmp(prop.val.buf, "test_value_2", prop.val.len) == 0 && prop.val.len == strlen("test_value_2")); j += 16; } else { ASSERT(0); } } } ASSERT((pos = mg_mqtt_next_prop(mm, &prop, pos)) == 0); ASSERT(j == 31); } } (void) c; } static void construct_props(struct mg_mqtt_prop *props) { props[0].id = MQTT_PROP_MESSAGE_EXPIRY_INTERVAL; props[0].iv = 10; props[1].id = MQTT_PROP_USER_PROPERTY; props[1].key = mg_str("test_key_1"); props[1].val = mg_str("test_value_1"); props[2].id = MQTT_PROP_USER_PROPERTY; props[2].key = mg_str("test_key_2"); props[2].val = mg_str("test_value_2"); props[3].id = MQTT_PROP_CONTENT_TYPE; props[3].val = mg_str("test_content_val_2"); props[4].id = MQTT_PROP_PAYLOAD_FORMAT_INDICATOR; props[4].iv = 1; } static void test_mqtt_base(void); static void test_mqtt_base(void) { char buf[10] = {0}; // we won't use it struct mqtt_data test_data = {buf, buf, 10, 10, 0}; struct mg_mgr mgr; struct mg_connection *c; const char *url = MQTT_URL; int i; mg_mgr_init(&mgr); // Ping the client c = mg_mqtt_connect(&mgr, url, NULL, mqtt_cb, &test_data); mg_mqtt_ping(c); for (i = 0; i < 300 && !(c->is_client && !c->is_connecting); i++) mg_mgr_poll(&mgr, 10); ASSERT(c->is_client && !c->is_connecting); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void check_mqtt_message(struct mg_mqtt_opts *opts, struct mqtt_data *data, bool enforce) { if (opts->topic.len != strlen(data->topic) || strcmp(opts->topic.buf, data->topic)) { MG_INFO(("TOPIC[%s]", data->topic)); if (enforce) ASSERT(0); } if (*data->msg != 'X' || opts->message.len != (strlen(&data->msg[1])) || strcmp(opts->message.buf, &data->msg[1])) { MG_INFO(("MSG[%s]", data->msg)); if (enforce) ASSERT(0); } } // generate a random string ending in three digits taken from current time static struct mg_str genstring(char *t, unsigned int sz) { mg_random_str(t, sz - 3); snprintf(t + sz - 4, 4, "%03u", (unsigned int) mg_millis() % 1000); return mg_str(t); } static void test_mqtt_basic(void) { char tbuf[16], mbuf[50] = {0}, topic[16]; struct mqtt_data test_data = {tbuf, mbuf, 16, 50, 0}; struct mg_mgr mgr; struct mg_connection *c; struct mg_mqtt_opts opts; #if MG_TLS const char *url = MQTTS_URL; #else const char *url = MQTT_URL; #endif int i, retries; // Connect with empty client ID, no options, ergo MQTT = 3.1.1 mg_mgr_init(&mgr); c = mg_mqtt_connect(&mgr, url, NULL, mqtt_cb, &test_data); for (i = 0; i < 1000 && mbuf[0] == 0; i++) mg_mgr_poll(&mgr, 10); if (mbuf[0] != 'X') MG_INFO(("[%s]", mbuf)); ASSERT(mbuf[0] == 'X'); ASSERT(test_data.flags == 0); // Subscribe with QoS1 opts.topic = genstring(topic, sizeof(topic)); opts.qos = 1; mg_mqtt_sub(c, &opts); for (i = 0; i < 500 && test_data.flags == 0; i++) mg_mgr_poll(&mgr, 10); ASSERT(test_data.flags == flags_subscribed); test_data.flags = 0; // Publish with QoS0 to subscribed topic and check reception // keep former opts.topic opts.message = mg_str("hi0"), opts.qos = 0, opts.retain = false; mg_mqtt_pub(c, &opts); for (i = 0; i < 500 && mbuf[1] == 0; i++) mg_mgr_poll(&mgr, 10); ASSERT(!(test_data.flags & flags_published)); // No PUBACK for QoS0 check_mqtt_message(&opts, &test_data, false); // We may not get the msg memset(mbuf + 1, 0, sizeof(mbuf) - 1); test_data.flags = 0; // Publish with QoS1 to subscribed topic and check reception // keep former opts.topic opts.message = mg_str("hi1"), opts.qos = 1, opts.retain = false, opts.retransmit_id = 0; retries = 0; // don't do retries for test speed do { // retry on failure after an expected timeout opts.retransmit_id = mg_mqtt_pub(c, &opts); // save id for possible resend for (i = 0; i < 500 && test_data.flags == 0; i++) mg_mgr_poll(&mgr, 10); } while (test_data.flags == 0 && retries--); ASSERT(test_data.flags == flags_published); for (i = 0; i < 500 && mbuf[1] == 0; i++) mg_mgr_poll(&mgr, 10); check_mqtt_message(&opts, &test_data, true); memset(mbuf + 1, 0, sizeof(mbuf) - 1); test_data.flags = 0; opts.retransmit_id = 0; #if MG_TLS // send more than 1 record, content is not relevant { static char somedata[21098]; mg_random(somedata, sizeof(somedata)); opts.message = mg_str_n(somedata, sizeof(somedata)); } opts.qos = 1, opts.retain = false, opts.retransmit_id = 0; mg_mqtt_pub(c, &opts); tbuf[0] = 0; for (i = 0; i < 1000 && test_data.flags == 0; i++) mg_mgr_poll(&mgr, 10); ASSERT(test_data.flags == flags_published); for (i = 0; i < 1000 && tbuf[0] == 0; i++) mg_mgr_poll(&mgr, 10); ASSERT(tbuf[0] != 0); // just check we were able to send and receive memset(mbuf + 1, 0, sizeof(mbuf) - 1); test_data.flags = 0; opts.retransmit_id = 0; #endif // Clean Disconnect ! mg_mqtt_disconnect(c, NULL); for (i = 0; i < 10 && mbuf[0] != 0; i++) mg_mgr_poll(&mgr, 10); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_mqtt_ver(uint8_t mqtt_version) { char tbuf[16], mbuf[50] = {0}, client_id[16], topic[16]; struct mqtt_data test_data = {tbuf, mbuf, 16, 50, 0}; struct mg_mgr mgr; struct mg_connection *c; struct mg_mqtt_opts opts; struct mg_mqtt_prop properties[5]; const char *url = MQTT_URL; int i, retries; MG_DEBUG(("ver: %u", mqtt_version)); // Connect with options: version, clean session, last will, keepalive // time. Don't set retain, some runners are not random test_data.flags = 0; memset(mbuf, 0, sizeof(mbuf)); memset(&opts, 0, sizeof(opts)); mg_mgr_init(&mgr); opts.clean = true, opts.qos = 1, opts.retain = false, opts.keepalive = 20; opts.version = mqtt_version; opts.topic = genstring(topic, sizeof(topic)); opts.message = mg_str("mg_will_messsage"); opts.client_id = genstring(client_id, sizeof(client_id)); c = mg_mqtt_connect(&mgr, url, &opts, mqtt_cb, &test_data); for (i = 0; i < 1000 && mbuf[0] == 0; i++) mg_mgr_poll(&mgr, 10); if (mbuf[0] != 'X') MG_INFO(("[%s]", mbuf)); ASSERT(mbuf[0] == 'X'); ASSERT(test_data.flags == 0); // Subscribe with QoS2 (reception downgrades to published QoS) opts.topic = genstring(topic, sizeof(topic)); opts.qos = 2; mg_mqtt_sub(c, &opts); for (i = 0; i < 500 && test_data.flags == 0; i++) mg_mgr_poll(&mgr, 10); ASSERT(test_data.flags == flags_subscribed); test_data.flags = 0; // Publish with QoS1 to subscribed topic and check reception // keep former opts.topic opts.message = mg_str("hi1"), opts.qos = 1, opts.retain = false; if (mqtt_version == 5) { opts.props = properties; opts.num_props = 5; construct_props(properties); } retries = 0; // don't do retries for test speed do { // retry on failure after an expected timeout opts.retransmit_id = mg_mqtt_pub(c, &opts); // save id for possible resend for (i = 0; i < 500 && test_data.flags == 0; i++) mg_mgr_poll(&mgr, 10); } while (test_data.flags == 0 && retries--); ASSERT(test_data.flags == flags_published); for (i = 0; i < 500 && mbuf[1] == 0; i++) mg_mgr_poll(&mgr, 10); check_mqtt_message(&opts, &test_data, true); memset(mbuf + 1, 0, sizeof(mbuf) - 1); test_data.flags = 0; opts.retransmit_id = 0; // Publish with QoS2 to subscribed topic and check (simultaneous) reception // keep former opts.topic opts.message = mg_str("hi2"), opts.qos = 2, opts.retain = false; if (mqtt_version == 5) { opts.props = properties; opts.num_props = 5; construct_props(properties); } retries = 0; // don't do retries for test speed do { // retry on failure after an expected timeout opts.retransmit_id = mg_mqtt_pub(c, &opts); // save id for possible resend for (i = 0; i < 500 && !(test_data.flags & flags_received); i++) mg_mgr_poll(&mgr, 10); } while (!(test_data.flags & flags_received) && retries--); ASSERT(test_data.flags & flags_received); test_data.flags &= ~flags_received; opts.retransmit_id = 0; // Mongoose sent PUBREL, wait for PUBCOMP for (i = 0; i < 500 && !(test_data.flags & flags_completed); i++) mg_mgr_poll(&mgr, 10); // TODO(): retry sending PUBREL on failure after an expected timeout // or broker sends PUBREC again ASSERT(test_data.flags & flags_completed); for (i = 0; i < 500 && mbuf[1] == 0; i++) mg_mgr_poll(&mgr, 10); check_mqtt_message(&opts, &test_data, true); for (i = 0; i < 500 && !(test_data.flags & flags_released); i++) mg_mgr_poll(&mgr, 10); ASSERT(test_data.flags & flags_released); // Mongoose sent PUBCOMP memset(mbuf + 1, 0, sizeof(mbuf) - 1); test_data.flags = 0; opts.props = 0; opts.num_props = 0; mg_mqtt_unsub(c, &opts); for (i = 0; i < 500 && test_data.flags == 0; i++) mg_mgr_poll(&mgr, 10); ASSERT(test_data.flags == flags_unsubscribed); test_data.flags = 0; // dirty disconnect mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_mqtt_parse(void) { struct mg_mqtt_message mm; struct mg_mqtt_prop prop; // MQTT5 PUBLISH with Subscription Identifier = 5 const uint8_t sub_id_pkt[] = {0x30, 0x06, 0x00, 0x01, 'x', 0x02, 0x0B, 0x05}; memset(&mm, 0, sizeof(struct mg_mqtt_message)); memset(&prop, 0, sizeof(struct mg_mqtt_prop)); ASSERT(mg_mqtt_parse(sub_id_pkt, sizeof(sub_id_pkt), 5, &mm) == MQTT_OK); mg_mqtt_next_prop(&mm, &prop, 0); } static void test_mqtt(void) { test_mqtt_base(); #ifdef NO_MQTT_TESTS printf("\nMQTT tests skipped on request\n"); (void) test_mqtt_basic, (void) test_mqtt_ver; #else test_mqtt_parse(); test_mqtt_basic(); test_mqtt_ver(4); test_mqtt_ver(5); #endif } static void eh1(struct mg_connection *c, int ev, void *ev_data) { struct mg_tls_opts *topts = (struct mg_tls_opts *) c->fn_data; if (ev == MG_EV_ACCEPT && topts != NULL) mg_tls_init(c, topts); if (ev == MG_EV_HTTP_HDRS && mg_match(((struct mg_http_message *) ev_data)->uri, mg_str("/big.bin"), NULL)) { // mg_http_serve_upload: streams big uploads, called from MG_EV_HTTP_HDRS struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); sopts.root_dir = "./data"; sopts.enable_upload = true; mg_http_serve_upload(c, (struct mg_http_message *) ev_data, &sopts); } else if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; MG_INFO(("[%.*s %.*s] message len %d", (int) hm->method.len, hm->method.buf, (int) hm->uri.len, hm->uri.buf, (int) hm->message.len)); if (mg_match(hm->uri, mg_str("/foo/*"), NULL)) { mg_http_reply(c, 200, "", "uri: %.*s", hm->uri.len - 5, hm->uri.buf + 5); } else if (mg_match(hm->uri, mg_str("/ws"), NULL)) { mg_ws_upgrade(c, hm, NULL); } else if (mg_match(hm->uri, mg_str("/body"), NULL)) { mg_http_reply(c, 200, "", "%.*s", (int) hm->body.len, hm->body.buf); } else if (mg_match(hm->uri, mg_str("/bar"), NULL)) { mg_http_reply(c, 404, "", "not found"); } else if (mg_match(hm->uri, mg_str("/no_reason"), NULL)) { mg_printf(c, "%s", "HTTP/1.0 200\r\nContent-Length: 2\r\n\r\nok"); } else if (mg_match(hm->uri, mg_str("/badroot"), NULL)) { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); sopts.root_dir = "/BAAADDD!"; mg_http_serve_dir(c, hm, &sopts); } else if (mg_match(hm->uri, mg_str("/creds"), NULL)) { char user[100], pass[100]; mg_http_creds(hm, user, sizeof(user), pass, sizeof(pass)); mg_http_reply(c, 200, "", "[%s]:[%s]", user, pass); } else if (mg_match(hm->uri, mg_str("/upload"), NULL)) { mg_http_upload(c, hm, &mg_fs_posix, ".", 99999); c->is_hexdumping = 1; } else if (mg_match(hm->uri, mg_str("/dirtest/"), NULL)) { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); sopts.root_dir = "."; sopts.extra_headers = "A: B\r\nE: F\r\n"; sopts.enable_dir_listing = true; mg_http_serve_dir(c, hm, &sopts); } else if (mg_match(hm->uri, mg_str("/servefile"), NULL)) { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); sopts.mime_types = "foo=a/b,txt=c/d"; mg_http_serve_file(c, hm, "data/a.txt", &sopts); } else if (mg_match(hm->uri, mg_str("/servefile2"), NULL)) { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); sopts.mime_types = "*=a/b,txt=c/d"; mg_http_serve_file(c, hm, "data/a.txt", &sopts); } else { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); sopts.root_dir = "./data"; sopts.ssi_pattern = "#.shtml"; sopts.extra_headers = "C: D\r\n"; sopts.enable_delete = true; sopts.enable_upload = true; mg_http_serve_dir(c, hm, &sopts); } } else if (ev == MG_EV_WS_OPEN) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; ASSERT(mg_strcmp(hm->uri, mg_str("/ws")) == 0); mg_ws_send(c, "opened", 6, WEBSOCKET_OP_BINARY); } else if (ev == MG_EV_WS_MSG) { struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; mg_ws_send(c, wm->data.buf, wm->data.len, WEBSOCKET_OP_BINARY); } } struct fetch_data { char *buf; int code, closed; }; static void fcb(struct mg_connection *c, int ev, void *ev_data) { struct fetch_data *fd = (struct fetch_data *) c->fn_data; if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; snprintf(fd->buf, FETCH_BUF_SIZE, "%.*s", (int) hm->message.len, hm->message.buf); fd->code = atoi(hm->uri.buf); fd->closed = 1; c->is_closing = 1; (void) c; } else if (ev == MG_EV_CLOSE) { fd->closed = 1; } } static int fetch(struct mg_mgr *mgr, char *buf, const char *url, const char *fmt, ...) { struct fetch_data fd = {buf, 0, 0}; int i; struct mg_connection *c = NULL; va_list ap; c = mg_http_connect(mgr, url, fcb, &fd); ASSERT(c != NULL); if (c != NULL && mg_url_is_ssl(url)) { struct mg_tls_opts opts; memset(&opts, 0, sizeof(opts)); // read CA from packed_fs opts.name = mg_url_host(url); opts.ca = mg_unpacked("/data/ca.pem"); if (strstr(url, "localhost") != NULL) { // Local connection, use self-signed certificates opts.ca = mg_unpacked("/certs/ca.crt"); if (strstr(url, "23456") != NULL) { // hinted from caller opts.cert = mg_unpacked("/certs/client.crt"); opts.key = mg_unpacked("/certs/client.key"); } } mg_tls_init(c, &opts); } // c->is_hexdumping = 1; va_start(ap, fmt); mg_vprintf(c, fmt, &ap); va_end(ap); buf[0] = '\0'; for (i = 0; i < 100 && buf[0] == '\0'; i++) mg_mgr_poll(mgr, 1); if (!fd.closed) c->is_closing = 1; mg_mgr_poll(mgr, 1); return fd.code; } static struct mg_http_message gethm(const char *buf) { struct mg_http_message hm; memset(&hm, 0, sizeof(hm)); mg_http_parse(buf, strlen(buf), &hm); return hm; } static int cmpbody(const char *buf, const char *str) { struct mg_str s = mg_str(str); struct mg_http_message hm = gethm(buf); size_t len = strlen(buf); if (hm.body.len > len) hm.body.len = len - (size_t) (hm.body.buf - buf); return mg_strcmp(hm.body, s); } static bool cmpheader(const char *buf, const char *name, const char *value) { struct mg_http_message hm = gethm(buf); struct mg_str *h = mg_http_get_header(&hm, name); return h != NULL && mg_strcmp(*h, mg_str(value)) == 0; } static void wcb(struct mg_connection *c, int ev, void *ev_data) { int *p = (int *) c->fn_data; if (ev == MG_EV_WS_OPEN) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_str *wsproto = mg_http_get_header(hm, "Sec-WebSocket-Protocol"); ASSERT(wsproto != NULL); mg_ws_send(c, "", 0, WEBSOCKET_OP_TEXT); mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%.3s", "boo!!!!"); mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%s", "foobar"); mg_ws_send(c, "", 0, WEBSOCKET_OP_PING); p[0] += 100; } else if (ev == MG_EV_WS_MSG) { struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; if (mgstrstr(wm->data, mg_str("foobar"))) mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE); if (mgstrstr(wm->data, mg_str("boo"))) p[0] += 2; if (mgstrstr(wm->data, mg_str("foobar"))) p[0] += 3; } else if (ev == MG_EV_CLOSE) { p[0] += 10; } } static void wbad(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_WS_OPEN) { mg_ws_send(c, "ABC", 3, WEBSOCKET_OP_CONTINUE); } else if (ev == MG_EV_CLOSE) { *(int *) c->fn_data = 1; } (void) ev_data; } static void ew2(struct mg_connection *c, int ev, void *ev_data) { size_t size = 65 * 1024 + 737; if (ev == MG_EV_WS_OPEN) { char *msg = (char *) calloc(1, size + 1); memset(msg, 'A', size); mg_ws_printf(c, WEBSOCKET_OP_TEXT, "%s", msg); free(msg); } else if (ev == MG_EV_WS_MSG) { struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; if (wm->data.len == 6) { // Ignore the "opened" message from server } else { size_t ok = 1, i; ASSERT(wm->data.len == size); for (i = 0; i < size; i++) { if (wm->data.buf[i] != 'A') ok = 0; } ASSERT(ok == 1); *(int *) c->fn_data = 1; } } } static void test_ws(void) { char buf[FETCH_BUF_SIZE]; const char *url = "ws://LOCALHOST:12343/ws"; struct mg_mgr mgr; int i, done = 0; mg_mgr_init(&mgr); ASSERT(mg_http_listen(&mgr, url, eh1, NULL) != NULL); mg_ws_connect(&mgr, url, wcb, &done, "%s", "Sec-WebSocket-Protocol: meh\r\n"); for (i = 0; i < 30; i++) mg_mgr_poll(&mgr, 1); // MG_INFO(("--> %d", done)); ASSERT(done == 115); // Test that non-WS requests fail ASSERT(fetch(&mgr, buf, url, "GET /ws HTTP/1.0\r\n\n") == 426); // Test large WS frames, over 64k done = 0; mg_ws_connect(&mgr, url, ew2, &done, NULL); for (i = 0; i < 1000 && done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(done == 1); done = 0; mg_ws_connect(&mgr, url, wbad, &done, NULL); for (i = 0; i < 30 && done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(done == 1); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void eh9(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_ERROR) { ASSERT(!strcmp((char *) ev_data, "socket error")); *(int *) c->fn_data = 7; } (void) c; } struct fpr_data { char *buf; int len, reqs, closed; }; static void fprcb(struct mg_connection *c, int ev, void *ev_data) { struct fpr_data *fd = (struct fpr_data *) c->fn_data; if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; int code = atoi(hm->uri.buf); if (code == 200) { snprintf(fd->buf + fd->len, FETCH_BUF_SIZE - (unsigned int) fd->len, "%.*s", (int) hm->message.len, hm->message.buf); fd->len += (int) hm->message.len; ++fd->reqs; if (fd->reqs == 2) { fd->closed = 1; c->is_closing = 1; } } else { // allow testing for other codes and catching wrong responses MG_INFO(("reqs: %d, code: %d", fd->reqs, code)); fd->reqs += code; } (void) c; } else if (ev == MG_EV_CLOSE) { fd->closed = 1; } } static int fpr(struct mg_mgr *mgr, char *buf, const char *url, const char *fmt, ...) { struct fpr_data fd = {buf, 0, 0, 0}; int i; struct mg_connection *c = NULL; va_list ap; c = mg_http_connect(mgr, url, fprcb, &fd); ASSERT(c != NULL); va_start(ap, fmt); mg_vprintf(c, fmt, &ap); va_end(ap); buf[0] = '\0'; for (i = 0; i < 500 && !fd.closed; i++) mg_mgr_poll(mgr, 1); if (!fd.closed) c->is_closing = 1; mg_mgr_poll(mgr, 1); return fd.reqs; } static void test_http_server(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12346"; char buf[FETCH_BUF_SIZE]; mg_mgr_init(&mgr); mg_http_listen(&mgr, url, eh1, NULL); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(cmpheader(buf, "C", "D")); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\nA:\tB\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(cmpheader(buf, "A", "B") == 0); ASSERT(fetch(&mgr, buf, url, "GET /%%61.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); // Invalid header: failure ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\nA B\n\n") == 0); // Pipelined requests ASSERT(fpr(&mgr, buf, url, "GET /foo/bar HTTP/1.1\n\nGET /foo/foobar HTTP/1.1\n\n") == 2); // Pipelined requests with file requests other than the last one (see #2796) ASSERT(fpr(&mgr, buf, url, "GET /a.txt HTTP/1.1\n\nGET /a.txt HTTP/1.1\n\n") == 2); /*ASSERT(fpr(&mgr, buf, url, "HEAD /a.txt HTTP/1.1\n\nGET /a.txt HTTP/1.1\n\n") == 2);*/ // Connection: close ASSERT(fpr(&mgr, buf, url, "GET /foo/bar HTTP/1.1\nConnection: close\n\nGET /foo/foobar " "HTTP/1.1\n\n") == 1); ASSERT(cmpbody(buf, "uri: bar") == 0); // Responses with missing reason phrase must also work ASSERT(fetch(&mgr, buf, url, "GET /no_reason HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "ok") == 0); // Fetch file with unicode chars in filename ASSERT(fetch(&mgr, buf, url, "GET /київ.txt HTTP/1.0\n\n") == 200); MG_INFO(("%s", buf)); ASSERT(cmpbody(buf, "є\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /../fuzz.c HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /.%%2e/fuzz.c HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /.%%2e%%2ffuzz.c HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /..%%2f%%20fuzz.c HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /..%%2ffuzz.c%%20 HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /dredir HTTP/1.0\n\n") == 301); ASSERT(cmpheader(buf, "Location", "/dredir/")); ASSERT(fetch(&mgr, buf, url, "GET /dredir/ HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hi\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /dredirgz/ HTTP/1.0\n" "Accept-Encoding: gzip\n\n") == 200); ASSERT(cmpheader(buf, "Content-Type", "text/html; charset=utf-8")); ASSERT(cmpheader(buf, "Content-Encoding", "gzip")); ASSERT(fetch(&mgr, buf, url, "GET /gzip.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hi\n") == 0); ASSERT(gethm(buf).body.len == 3); ASSERT(cmpheader(buf, "Content-Encoding", "gzip") == false); ASSERT(fetch(&mgr, buf, url, "GET /gzip.txt HTTP/1.0\n" "Accept-Encoding: foo,gzip\n\n") == 200); mg_hexdump(buf, strlen(buf)); ASSERT(cmpheader(buf, "Content-Encoding", "gzip") == true); ASSERT(gethm(buf).body.len == 23); ASSERT(fetch(&mgr, buf, url, "GET /..ddot HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /..ddot/ HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n" "Content-Length: -123\n\n") == 0); ASSERT(fetch(&mgr, buf, url, "POST /a.txt HTTP/1.0\n" "Content-Length: -123\n\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n" "Content-Length: 19000000000000000000\n\n") == 0); ASSERT(fetch(&mgr, buf, url, "POST /a.txt HTTP/1.0\n" "Content-Length: 19000000000000000000\n\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n" ":\n" // truncated header "Content-Length: 1\n\n") == 0); { extern char *mg_http_etag(char *, size_t, size_t, time_t); struct mg_http_message hm; char etag[100]; size_t size = 0; time_t mtime = 0; ASSERT(mg_fs_posix.st("./data/a.txt", &size, &mtime) != 0); ASSERT(mg_http_etag(etag, sizeof(etag), size, mtime) == etag); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\nIf-None-Match: %s\n\n", etag) == 304); ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); MG_INFO(("%s", buf)); ASSERT(mg_http_get_header(&hm, "C") != NULL); ASSERT(mg_strcmp(*mg_http_get_header(&hm, "C"), mg_str("D")) == 0); } // Text mime type override ASSERT(fetch(&mgr, buf, url, "GET /servefile HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); { struct mg_http_message hm; struct mg_str *s; mg_http_parse(buf, strlen(buf), &hm); ASSERT((s = mg_http_get_header(&hm, "Content-Type")) != NULL); ASSERT(mg_strcmp(*s, mg_str("c/d")) == 0); } ASSERT(fetch(&mgr, buf, url, "GET /servefile2 HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); { struct mg_http_message hm; struct mg_str *s; mg_http_parse(buf, strlen(buf), &hm); ASSERT((s = mg_http_get_header(&hm, "Content-Type")) != NULL); ASSERT(mg_strcmp(*s, mg_str("a/b")) == 0); } ASSERT(fetch(&mgr, buf, url, "GET /foo/1 HTTP/1.0\r\n\n") == 200); // MG_INFO(("%d %.*s", (int) hm.len, (int) hm.len, hm.buf)); ASSERT(cmpbody(buf, "uri: 1") == 0); ASSERT(fetch(&mgr, buf, url, "%s", "POST /body HTTP/1.1\r\n" "Content-Length: 4\r\n\r\nkuku") == 200); ASSERT(cmpbody(buf, "kuku") == 0); ASSERT(fetch(&mgr, buf, url, "GET /ssi HTTP/1.1\r\n\r\n") == 301); ASSERT(fetch(&mgr, buf, url, "GET /ssi/ HTTP/1.1\r\n\r\n") == 200); ASSERT(cmpbody(buf, "this is index\n" "this is nested\n\n" "this is f1\n\n\n\n" "recurse\n\n" "recurse\n\n" "recurse\n\n" "recurse\n\n" "recurse\n\n") == 0); { struct mg_http_message hm; mg_http_parse(buf, strlen(buf), &hm); ASSERT(mg_http_get_header(&hm, "Content-Length") != NULL); ASSERT(mg_http_get_header(&hm, "Content-Type") != NULL); ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Type"), mg_str("text/html; charset=utf-8")) == 0); } ASSERT(fetch(&mgr, buf, url, "GET /badroot HTTP/1.0\r\n\n") == 404); // ASSERT(cmpbody(buf, "Invalid web root [/BAAADDD!]\n") == 0); { struct mg_str data = mg_file_read(&mg_fs_posix, "./data/ca.pem"); ASSERT(fetch(&mgr, buf, url, "GET /ca.pem HTTP/1.0\r\n\n") == 200); ASSERT(cmpbody(buf, data.buf) == 0); mg_free((void *) data.buf); } { // Test mime type struct mg_http_message hm; ASSERT(fetch(&mgr, buf, url, "GET /empty.js HTTP/1.0\r\n\n") == 200); mg_http_parse(buf, strlen(buf), &hm); ASSERT(mg_http_get_header(&hm, "Content-Type") != NULL); ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Type"), mg_str("text/javascript; charset=utf-8")) == 0); } { // Test connection refused int i, errored = 0; mg_connect(&mgr, "tcp://127.0.0.1:55117", eh9, &errored); // Give it a couple of seconds, see #1605 for (i = 0; i < 200 && errored == 0; i++) mg_mgr_poll(&mgr, 10); MG_INFO(("errored: %d, expected: 7", errored)); ASSERT(errored == 7); } // Directory listing #if MG_ARCH == MG_ARCH_UNIX ASSERT(system("touch 'dirtest/a.txt'") == 0); #endif ASSERT(fetch(&mgr, buf, url, "GET /dirtest/ HTTP/1.0\n\n") == 200); MG_DEBUG(("%s", buf)); ASSERT(mgstrstr(mg_str(buf), mg_str("fuzz.c")) != NULL); #if MG_ARCH == MG_ARCH_UNIX ASSERT(mgstrstr(mg_str(buf), mg_str("a.txt")) != NULL); if (system("rm 'dirtest/a.txt'") == 0) (void) 0; #endif ASSERT(cmpheader(buf, "A", "B")); ASSERT(!cmpheader(buf, "C", "D")); ASSERT(cmpheader(buf, "E", "F")); { // mg_http_serve_dir: allow_upload, small file first (buffered), then a // big one (mg_http_serve_upload, streamed - see the MG_EV_HTTP_HDRS // branch in eh1), then allow_delete struct mg_str big = mg_file_read(&mg_fs_posix, "mongoose.c"); struct mg_str got; ASSERT(big.len > MG_IO_SIZE); ASSERT(fetch(&mgr, buf, url, "POST /del_me.txt HTTP/1.0\r\nContent-Length: 1\r\n\r\nx") == 200); ASSERT(fetch(&mgr, buf, url, "GET /del_me.txt HTTP/1.0\n\n") == 200); ASSERT(fetch(&mgr, buf, url, "POST /big.bin HTTP/1.0\r\nContent-Length: %d\r\n\r\n%.*s", (int) big.len, (int) big.len, big.buf) == 200); got = mg_file_read(&mg_fs_posix, "data/big.bin"); ASSERT(got.len == big.len && memcmp(got.buf, big.buf, big.len) == 0); mg_free((void *) got.buf); mg_free((void *) big.buf); remove("data/big.bin"); ASSERT(fetch(&mgr, buf, url, "DELETE /del_me.txt HTTP/1.0\n\n") == 200); ASSERT(fetch(&mgr, buf, url, "GET /del_me.txt HTTP/1.0\n\n") == 404); ASSERT(fetch(&mgr, buf, url, "DELETE /del_me.txt HTTP/1.0\n\n") == 404); } { // Credentials struct mg_http_message hm; ASSERT(fetch(&mgr, buf, url, "%s", "GET /creds?access_token=x HTTP/1.0\r\n\r\n") == 200); mg_http_parse(buf, strlen(buf), &hm); ASSERT(mg_strcmp(hm.body, mg_str("[]:[x]")) == 0); ASSERT(fetch(&mgr, buf, url, "%s", "GET /creds HTTP/1.0\r\n" "Authorization: Bearer x\r\n\r\n") == 200); mg_http_parse(buf, strlen(buf), &hm); ASSERT(mg_strcmp(hm.body, mg_str("[]:[x]")) == 0); ASSERT(fetch(&mgr, buf, url, "%s", "GET /creds HTTP/1.0\r\n" "Authorization: Basic Zm9vOmJhcg==\r\n\r\n") == 200); mg_http_parse(buf, strlen(buf), &hm); ASSERT(mg_strcmp(hm.body, mg_str("[foo]:[bar]")) == 0); ASSERT(fetch(&mgr, buf, url, "%s", "GET /creds HTTP/1.0\r\n" "Cookie: blah; access_token=hello\r\n\r\n") == 200); mg_http_parse(buf, strlen(buf), &hm); ASSERT(mg_strcmp(hm.body, mg_str("[]:[hello]")) == 0); } { // Test upload struct mg_str s; remove("uploaded.txt"); s = mg_file_read(&mg_fs_posix, "uploaded.txt"); ASSERT(s.buf == NULL); ASSERT(fetch(&mgr, buf, url, "POST /upload HTTP/1.0\n" "Content-Length: 1\n\nx") == 400); ASSERT(fetch(&mgr, buf, url, "POST /upload?file=uploaded.txt HTTP/1.0\r\n" "Content-Length: 5\r\n" "\r\nhello") == 200); ASSERT(fetch(&mgr, buf, url, "POST /upload?file=uploaded.txt&offset=5 HTTP/1.0\r\n" "Content-Length: 6\r\n" "\r\n\nworld") == 200); s = mg_file_read(&mg_fs_posix, "uploaded.txt"); ASSERT(s.buf != NULL); ASSERT(strcmp(s.buf, "hello\nworld") == 0); mg_free((void *) s.buf); remove("uploaded.txt"); } { // Test upload directory traversal struct mg_str s; remove("uploaded.txt"); s = mg_file_read(&mg_fs_posix, "uploaded.txt"); ASSERT(s.buf == NULL); ASSERT(fetch(&mgr, buf, url, "POST /upload?file=../uploaded.txt HTTP/1.0\r\n" "Content-Length: 5\r\n" "\r\nhello") == 400); s = mg_file_read(&mg_fs_posix, "uploaded.txt"); ASSERT(s.buf == NULL); } // HEAD request ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n\n") == 200); ASSERT(fetch(&mgr, buf, url, "HEAD /a.txt HTTP/1.0\n\n") == 200); // Pre-compressed files { struct mg_http_message hm; ASSERT(fetch(&mgr, buf, url, "HEAD /hello.txt HTTP/1.0\n" "Accept-Encoding: gzip\n\n") == 200); mg_http_parse(buf, strlen(buf), &hm); ASSERT(mg_http_get_header(&hm, "Content-Encoding") != NULL); ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Encoding"), mg_str("gzip")) == 0); } #if MG_ENABLE_IPV6 { const char *url6 = "http://[::1]:12366"; ASSERT(mg_http_listen(&mgr, url6, eh1, NULL) != NULL); ASSERT(fetch(&mgr, buf, url6, "GET /a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); } #endif // #2552, reject chunks with no length ASSERT(fetch(&mgr, buf, url, "POST / HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n\r\n" "1\r\n" "Z\r\n" "?\r\n" "\r\n") == 0); ASSERT(fetch(&mgr, buf, url, "POST / HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n\r\n" "1\r\n" "Z\r\n" "\r\n" "\r\n") == 0); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void h4(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; MG_INFO(("[%.*s %.*s] message len %d", (int) hm->method.len, hm->method.buf, (int) hm->uri.len, hm->uri.buf, (int) hm->message.len)); if (mg_match(hm->uri, mg_str("/a/#"), NULL)) { struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); opts.root_dir = "/a=./data"; opts.page404 = "./data/404.html"; // existing 404 page mg_http_serve_dir(c, hm, &opts); } else if (mg_match(hm->uri, mg_str("/b/#"), NULL)) { struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); opts.root_dir = "/b=./data"; opts.page404 = "./data/nooooo.html"; // non-existing 404 page mg_http_serve_dir(c, hm, &opts); } else { // null 404 page struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); opts.root_dir = "./data"; mg_http_serve_dir(c, hm, &opts); } } } static void test_http_404(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:22343"; char buf[FETCH_BUF_SIZE]; mg_mgr_init(&mgr); mg_http_listen(&mgr, url, h4, NULL); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /a/a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /b/a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /xx.txt HTTP/1.0\n\n") == 404); ASSERT(cmpbody(buf, "Not found\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /a/xx.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "boo\n") == 0); { // txt requested, existent 404 is html struct mg_http_message hm; struct mg_str *cl; mg_http_parse(buf, strlen(buf), &hm); cl = mg_http_get_header(&hm, "Content-Type"); ASSERT(cl != NULL); if (cl->len > 9) cl->len = 9; // restrict to text/html len max ASSERT(mg_strcmp(*cl, mg_str("text/html")) == 0); } ASSERT(fetch(&mgr, buf, url, "GET /b/xx.txt HTTP/1.0\n\n") == 404); ASSERT(cmpbody(buf, "Not found\n") == 0); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_tls(void) { // return; #if MG_TLS struct mg_mgr mgr; struct mg_connection *c; const char *url = "https://localhost:12347"; char buf[FETCH_BUF_SIZE]; struct mg_tls_opts opts; struct mg_str data = mg_unpacked("/fuzz.c"); char bigdata[FETCH_BUF_SIZE - 256]; // leave extra room struct mg_str bd; ASSERT(data.buf != NULL && data.len > 0); memset(&opts, 0, sizeof(opts)); opts.cert = mg_unpacked("/certs/server.crt"); opts.key = mg_unpacked("/certs/server.key"); mg_mgr_init(&mgr); c = mg_http_listen(&mgr, url, eh1, &opts); ASSERT(c != NULL); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n\n") == 200); // MG_INFO(("%s", buf)); ASSERT(cmpbody(buf, "hello\n") == 0); // POST a large file, several MSS but less than max TLS record length // make sure we drain TLS buffers and read all, #2619 ASSERT(data.len > 3100 && data.len < 16384); // pick another file on failure ASSERT(fetch(&mgr, buf, url, "POST /body HTTP/1.0\n" "Content-Length: %lu\n\n" "%s", data.len, data.buf) == 200); // /body returns data back, so verify contents (both server and client send // and receive the whole file ASSERT(cmpbody(buf, data.buf) == 0); // repeat with a really large "file", several times max TLS record length bd = genstring(bigdata, sizeof(bigdata)); ASSERT(fetch(&mgr, buf, url, "POST /body HTTP/1.0\n" "Content-Length: %lu\n\n" "%s", bd.len, bd.buf) == 200); ASSERT(cmpbody(buf, bd.buf) == 0); #if MG_TLS == MG_TLS_BUILTIN && defined(__linux__) && \ MG_ENABLE_CHACHA20 // skip for non-CHACHA tests // fire patched server, test multiple TLS records per TCP segment handling // skip other TLS stacks to avoid "bad client hello", we are 1.3 only if (access("tls_multirec/server", X_OK) == 0) { ASSERT(system("tls_multirec/server -d tls_multirec &") == 0); sleep(1); // fetch() needs to loop enough times in order to process all TLS records; // otherwise it will end with 200 and shorter file contents ASSERT(fetch(&mgr, buf, "https://localhost:8443", "GET /thefile HTTP/1.0\n\n") == 200); data = mg_unpacked("/Makefile"); ASSERT(cmpbody(buf, data.buf) == 0); // "thefile" links to Makefile ASSERT(system("killall tls_multirec/server") == 0); } else { printf("\nSKIPPED TLS MULTIPLE RECORDS TEST, tls_multirec/server NOT PRESENT\n"); } #else printf( "\n Skipping multiple TLS records per TCP segment handling test, server " "is 1.3 only; re-enable when other stacks can be easily configured for " "1.3\n"); #endif // Repeat the simplest test with two-way authentication opts.ca = mg_unpacked("/certs/ca.crt"); // configure the server for two-way // make it fail: the client will not use 2-way ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n\n") != 200); // make it work mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); mg_mgr_init(&mgr); url = "https://localhost:23456"; // port # hints the client to use two-way c = mg_http_listen(&mgr, url, eh1, &opts); ASSERT(c != NULL); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); #endif } static void f3(struct mg_connection *c, int ev, void *ev_data) { int *ok = (int *) c->fn_data; // MG_INFO(("%d", ev)); if (ev == MG_EV_CONNECT) { // c->is_hexdumping = 1; ASSERT((c->loc.addr.ip[0] != 0)); // Make sure that c->loc address is populated mg_printf(c, "GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", c->rem.is_ip6 ? "" : "/robots.txt", c->rem.is_ip6 ? "ipv6.google.com" : "cesanta.com"); } else if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; // MG_INFO(("-->[%.*s]", (int) hm->message.len, hm->message.buf)); // ASSERT(vcmp(hm->method, "HTTP/1.1")); // ASSERT(vcmp(hm->uri, "301")); *ok = mg_http_status(hm); c->is_closing = 1; } else if (ev == MG_EV_CLOSE) { if (*ok == 0) *ok = 888; } else if (ev == MG_EV_ERROR) { if (*ok == 0) *ok = 777; } } static void test_http_client(void) { struct mg_mgr mgr; struct mg_connection *c = NULL; const char *url = "http://cesanta.com"; int i, ok = 0; struct mg_tls_opts opts; memset(&opts, 0, sizeof(opts)); opts.ca = mg_unpacked("/data/ca.pem"); opts.name = mg_url_host(url); ASSERT(opts.ca.len > 0); ASSERT(opts.name.len > 0); mg_mgr_init(&mgr); c = mg_http_connect(&mgr, url, f3, &ok); ASSERT(c != NULL); for (i = 0; i < 1000 && ok <= 0; i++) mg_mgr_poll(&mgr, 1); MG_INFO(("OK: %d", ok)); ASSERT(ok == 301 || ok == 200); mg_mgr_poll(&mgr, 0); ok = 0; #if MG_TLS url = "https://cesanta.com"; opts.name = mg_url_host(url); c = mg_http_connect(&mgr, url, f3, &ok); ASSERT(c != NULL); mg_tls_init(c, &opts); for (i = 0; i < 1500 && ok <= 0; i++) mg_mgr_poll(&mgr, 1); MG_INFO(("OK: %d", ok)); ASSERT(ok == 200); mg_mgr_poll(&mgr, 1); // Make host validation fail c = mg_http_connect(&mgr, url, f3, &ok); ASSERT(c != NULL); opts.name = mg_str("dummy"); // Set some invalid hostname value mg_tls_init(c, &opts); ok = 0; for (i = 0; i < 500 && ok <= 0; i++) mg_mgr_poll(&mgr, 10); MG_INFO(("OK: %d", ok)); ASSERT(ok == 777); mg_mgr_poll(&mgr, 1); // Skip host validation c = mg_http_connect(&mgr, url, f3, &ok); ASSERT(c != NULL); opts.name = mg_str(""); mg_tls_init(c, &opts); ok = 0; for (i = 0; i < 500 && ok <= 0; i++) mg_mgr_poll(&mgr, 10); MG_INFO(("OK: %d", ok)); ASSERT(ok == 200); mg_mgr_poll(&mgr, 1); opts.name = mg_url_host(url); // Test empty CA opts.ca = mg_str(""); opts.name = mg_url_host(url); c = mg_http_connect(&mgr, url, f3, &ok); mg_tls_init(c, &opts); ok = 0; for (i = 0; i < 1000 && ok <= 0; i++) mg_mgr_poll(&mgr, 10); MG_INFO(("OK: %d", ok)); ASSERT(ok == 200); mg_mgr_poll(&mgr, 1); // Make host validation fail c = mg_http_connect(&mgr, url, f3, &ok); ASSERT(c != NULL); opts.name = mg_str("dummy"); // Set some invalid hostname value mg_tls_init(c, &opts); ok = 0; for (i = 0; i < 500 && ok <= 0; i++) mg_mgr_poll(&mgr, 10); MG_INFO(("OK: %d", ok)); #if MG_TLS != MG_TLS_MBED || !defined(MBEDTLS_VERSION_NUMBER) || \ MBEDTLS_VERSION_NUMBER < 0x03030000 || \ MBEDTLS_VERSION_NUMBER >= 0x04000000 // Ignore result for 3.3.0 <= mbedTLS < 4.0.0 : https://github.com/Mbed-TLS/mbedtls/issues/7075 ASSERT(ok == 777); #endif mg_mgr_poll(&mgr, 1); #endif #if MG_ENABLE_IPV6 ok = 0; // ipv6.google.com does not have IPv4 address, only IPv6, therefore // it is guaranteed to hit IPv6 resolution path. c = mg_http_connect(&mgr, "http://ipv6.google.com", f3, &ok); for (i = 0; i < 500 && ok <= 0; i++) mg_mgr_poll(&mgr, 10); MG_INFO(("OK: %d", ok)); ASSERT(ok == 200); #endif mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void f4(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; mg_printf(c, "HTTP/1.0 200 OK\n\n%.*s/%s", (int) hm->uri.len, hm->uri.buf, "abcdef"); strcat((char *) c->fn_data, "m"); c->is_draining = 1; } else if (ev == MG_EV_CLOSE) { strcat((char *) c->fn_data, "c"); } } static void f4c(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_CONNECT) { mg_printf(c, "GET /foo/bar HTTP/1.0\n\n"); } else if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; ASSERT(mg_strcmp(hm->body, mg_str("/foo/bar/abcdef")) == 0); strcat((char *) c->fn_data, "m"); } else if (ev == MG_EV_CLOSE) { strcat((char *) c->fn_data, "c"); } } static void f41(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; mg_printf(c, "HTTP/1.0 200 OK\n\n%.*s/%s", (int) hm->uri.len, hm->uri.buf, "abcdef"); } } static void test_http_no_content_length(void) { char buf1[10] = {0}, buf2[10] = {0}; char buf[100]; struct mg_mgr mgr; const char *url = "http://127.0.0.1:12348"; const char *url2 = "http://127.0.0.1:12349"; int i; const char *post_req = "POST / HTTP/1.1\r\nContent-Type:" "b/a\r\nContent-Length: 15\r\n\r\n" "{\"key\": \"value\"}"; mg_mgr_init(&mgr); mg_http_listen(&mgr, url, f4, (void *) buf1); mg_http_connect(&mgr, url, f4c, (void *) buf2); for (i = 0; i < 1000 && strchr(buf2, 'c') == NULL; i++) mg_mgr_poll(&mgr, 10); MG_INFO(("[%s] [%s]", buf1, buf2)); ASSERT(strcmp(buf1, "mc") == 0); ASSERT(strcmp(buf2, "mc") == 0); mg_mgr_free(&mgr); // 12348 is in TIME_WAIT, use another port mg_mgr_init(&mgr); mg_http_listen(&mgr, url2, f41, (void *) NULL); ASSERT(fetch(&mgr, buf, url2, "POST / HTTP/1.1\r\n\r\n") == 200); ASSERT(fetch(&mgr, buf, url2, "HTTP/1.1 200\r\n\r\n") == 411); ASSERT(fetch(&mgr, buf, url2, "HTTP/1.1 100\r\n\r\n") != 411); ASSERT(fetch(&mgr, buf, url2, "HTTP/1.1 304\r\n\r\n") != 411); ASSERT(fetch(&mgr, buf, url2, "HTTP/1.1 305\r\n\r\n") == 411); ASSERT(fetch(&mgr, buf, url2, post_req) != 411); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void f5(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; mg_http_reply(c, 200, "", "%.*s", (int) hm->uri.len, hm->uri.buf); (*(int *) c->fn_data)++; } } static void f6(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { (*(int *) c->fn_data)++; (void) ev_data; } } static void test_http_pipeline(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12377"; struct mg_connection *c; int i, ok = 0, ok2 = 0; mg_mgr_init(&mgr); mg_http_listen(&mgr, url, f5, (void *) &ok); c = mg_http_connect(&mgr, url, f6, &ok2); mg_printf(c, "POST / HTTP/1.0\nContent-Length: 5\n\n12345GET / HTTP/1.0\n\n"); for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 1); ASSERT(ok == 2); ASSERT(ok2 == 2); // Fire a valid, then invalid request, see #2592. First one should serve ok = ok2 = 0; c = mg_http_connect(&mgr, url, f6, (void *) &ok2); mg_printf(c, "GET / HTTP/1.1\n\nInvalid\n\n"); for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 1); ASSERT(ok == 1); ASSERT(ok2 == 1); // A bodyless POST must not consume the following pipelined request ok = ok2 = 0; c = mg_http_connect(&mgr, url, f6, (void *) &ok2); mg_printf(c, "POST /x HTTP/1.1\r\nHost: h\r\n\r\n" "GET /next HTTP/1.1\r\nHost: h\r\n\r\n"); for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 1); ASSERT(ok == 2); ASSERT(ok2 == 2); // MG_INFO(("-----> [%d] [%d]", ok, ok2)); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_http_parse(void) { struct mg_str *v; struct mg_http_message req; { const char *s = "GET / HTTP/1.0\n\n"; ASSERT(mg_http_parse("\b23", 3, &req) == -1); ASSERT(mg_http_parse("get\n\n", 5, &req) == -1); ASSERT(mg_http_parse(s, strlen(s) - 1, &req) == 0); ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT(req.message.len == strlen(s)); ASSERT(req.body.len == 0); } { const char *s = "GET / HTTP/1.0\r\nHost: test\r\n" "Transfer-Encoding: chunked\r\n\r\n"; mg_http_parse(s, strlen(s), &req); ASSERT(req.proto.len == 8 && mg_strcmp(req.proto, mg_str("HTTP/1.0")) == 0); } { const char *s = "GET / \r\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == 0); } { const char *s = "GET / invalid\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); } { const char *s = "GET /blah HTTP/1.0\r\nFoo: bar \r\n\r\n"; size_t idx, len = strlen(s); ASSERT(mg_http_parse(s, strlen(s), &req) == (int) len); ASSERT(vcmp(req.headers[0].name, "Foo")); ASSERT(vcmp(req.headers[0].value, "bar")); ASSERT(req.headers[1].name.len == 0); ASSERT(req.headers[1].name.buf == NULL); ASSERT(req.query.len == 0); ASSERT(req.message.len == len); ASSERT(req.body.len == 0); ASSERT(vcmp(req.method, "GET")); ASSERT(vcmp(req.uri, "/blah")); ASSERT(vcmp(req.proto, "HTTP/1.0")); for (idx = 0; idx < len; idx++) ASSERT(mg_http_parse(s, idx, &req) == 0); } { const char *s = "get b HTTP/1.1\nb: t\nv:vv\n\n xx"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s) - 3); } { const char *s = "get b HTTP/1.1\nb: t\nv:\n\n xx"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s) - 3); } { const char *s = "get b HTTP/1.1\nb: t\nv v\n\n xx"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); } { const char *s = "get b HTTP/1.1\nb: t\n : aa\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); } { const char *s = "get b HTTP/1.1\nz: k \nb: t\nv:k\n\n xx"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s) - 3); ASSERT(req.headers[3].name.len == 0); ASSERT(vcmp(req.headers[0].name, "z")); ASSERT(vcmp(req.headers[0].value, "k")); ASSERT(vcmp(req.headers[1].name, "b")); ASSERT(vcmp(req.headers[1].value, "t")); ASSERT(vcmp(req.headers[2].name, "v")); ASSERT(vcmp(req.headers[2].value, "k")); ASSERT(req.body.len == 0); } // #2292: fail on stray \r inside the headers ASSERT(mg_http_parse("a є HTTP/1.0\n\n", 15, &req) == 15); ASSERT(mg_http_parse("a b HTTP/1.0\n\n", 14, &req) == 14); ASSERT(mg_http_parse("a b HTTP/1.0\na:\n\n", 17, &req) > 0); ASSERT(mg_http_parse("a b HTTP/1.0\na:\r\n\n", 18, &req) > 0); ASSERT(mg_http_parse("a b HTTP/1.0\n\ra:\r\n\n", 19, &req) == -1); ASSERT(mg_http_parse("a b HTTP/1.0\na:\r1\n\n", 19, &req) == -1); ASSERT(mg_http_parse("a b HTTP/1.0\na: \r1\n\n", 20, &req) == -1); ASSERT(mg_http_parse("a b HTTP/1.0\na: \rb:\n\n", 21, &req) == -1); ASSERT(mg_http_parse("a b HTTP/1.0\na: \nb:\n\n", 21, &req) > 0); { const char *s = "ґєт /слеш HTTP/1.0\nмісто: кіїв \n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT(req.body.len == 0); ASSERT(req.headers[1].name.len == 0); ASSERT(vcmp(req.headers[0].name, "місто")); ASSERT(vcmp(req.headers[0].value, "кіїв")); ASSERT((v = mg_http_get_header(&req, "місто")) != NULL); ASSERT(vcmp(req.method, "ґєт")); ASSERT(vcmp(req.uri, "/слеш")); ASSERT(vcmp(req.proto, "HTTP/1.0")); } { const char *s = "a b HTTP/1.0\r\nContent-Length: 21 \r\nb: t\r\nv:v\r\n\r\nabc"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s) - 3); ASSERT(req.body.len == 21); ASSERT(req.message.len == 21 - 3 + strlen(s)); ASSERT(mg_http_get_header(&req, "foo") == NULL); ASSERT((v = mg_http_get_header(&req, "contENT-Length")) != NULL); ASSERT(vcmp(*v, "21")); ASSERT((v = mg_http_get_header(&req, "B")) != NULL); ASSERT(vcmp(*v, "t")); } { const char *s = "GET /foo?a=b&c=d HTTP/1.0\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT(vcmp(req.uri, "/foo")); ASSERT(vcmp(req.query, "a=b&c=d")); } { const char *s = "POST /x HTTP/1.0\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT(req.body.len == 0); } { const char *s = "WOHOO /x HTTP/1.0\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT(req.body.len == 0); } { const char *s = "HTTP/1.0 200 OK\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT(vcmp(req.method, "HTTP/1.0")); ASSERT(vcmp(req.uri, "200")); ASSERT(vcmp(req.proto, "OK")); ASSERT(req.body.len == (size_t) ~0); } { static const char *s = "HTTP/1.0 999 OMGWTFBBQ\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); } { const char *s = "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"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT((v = mg_http_get_header(&req, "Host")) != NULL); ASSERT(vcmp(*v, "127.0.0.1:18888")); ASSERT((v = mg_http_get_header(&req, "Cookie")) != NULL); ASSERT(v->len == 0); ASSERT((v = mg_http_get_header(&req, "X-PlayID")) != NULL); ASSERT(vcmp(*v, "45455")); ASSERT((v = mg_http_get_header(&req, "Range")) != NULL); ASSERT(vcmp(*v, "0-1")); } { const char *s = "a b HTTP/1.0\na:1\nb:2\nc:3\nd:4\ne:5\nf:6\ng:7\nh:8\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); ASSERT((v = mg_http_get_header(&req, "e")) != NULL); ASSERT(vcmp(*v, "5")); ASSERT((v = mg_http_get_header(&req, "g")) != NULL); ASSERT(vcmp(*v, "7")); ASSERT((v = mg_http_get_header(&req, "h")) == NULL); // MG_MAX_HTTP_HEADERS } { // no HTTP version const char *s = "a b\na:1\nb:2\nc:2\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); // HTTP version s = "a b HTTP/1.0\na:1\nb:2\nc:2\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); // Content-Length s = "a b HTTP/1.0\nContent-Length:10\nb:2\nc:2\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s)); // duplicated Content-Length s = "a b HTTP/1.0\nContent-Length:10\nb:2\nContent-Length:20\n\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); } { struct mg_connection c; struct mg_str s, res = mg_str("GET /\r\nAuthorization: Basic Zm9vOmJhcg==\r\n\r\n"); memset(&c, 0, sizeof(c)); mg_printf(&c, "%s", "GET /\r\n"); mg_http_bauth(&c, "foo", "bar"); mg_printf(&c, "%s", "\r\n"); s = mg_str_n((char *) c.send.buf, c.send.len); ASSERT(mg_strcmp(s, res) == 0); mg_iobuf_free(&c.send); } { struct mg_http_message hm; const char *s = "GET /foo?bar=baz HTTP/1.0\n\n "; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s) - 1); ASSERT(mg_strcmp(hm.uri, mg_str("/foo")) == 0); ASSERT(mg_strcmp(hm.query, mg_str("bar=baz")) == 0); } { struct mg_http_message hm; const char *s = "a b HTTP/1.0\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); s = "a b HTTP/1.0\nc:d\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); s = "a\nb:b\nc:c\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) < 0); s = "a b HTTP/1.0\nc: \xc0\n\n"; // Invalid UTF in the header value: accept ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); ASSERT((v = mg_http_get_header(&hm, "c")) != NULL); ASSERT(vcmp(*v, "\xc0")); s = "a b HTTP/1.0\n\xc0: 2\n\n"; // Invalid UTF in the header name: do NOT // accept ASSERT(mg_http_parse(s, strlen(s), &hm) == -1); } { struct mg_http_message hm; const char *s; s = "a b HTTP/1.0\nd:e\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); s = "a b HTTP/1.0\nd: e\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); s = "a b HTTP/1.0\nd:\te\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); s = "a b HTTP/1.0\nd:\t e\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); s = "a b HTTP/1.0\nd: \te\t \n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); ASSERT(mg_strcmp(hm.headers[0].value, mg_str("e")) == 0); } { // Test that query-string gets stripped struct mg_http_message hm; const char *s = "GET /foo?bar HTTP/1.1\n\n"; ASSERT(mg_http_parse(s, strlen(s), &hm) == (int) strlen(s)); ASSERT(mg_strcmp(hm.uri, mg_str("/foo")) == 0); ASSERT(mg_strcmp(hm.query, mg_str("bar")) == 0); s = "POST / HTTP/1.1\r\nHost: t\r\n" "Content-Length: 5\r\nContent-Length: 10\r\n\r\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); s = "POST / HTTP/1.1\r\nHost: t\r\n" "Transfer-Encoding: chunked\r\nTransfer-Encoding: identity\r\n\r\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); s = "POST / HTTP/1.1\r\nHost: t\r\n" "Content-Length: 5\r\nTransfer-Encoding: chunked\r\n\r\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); s = "POST / HTTP/1.1\r\nHost: t\r\n" "Transfer-Encoding: chunked\r\nContent-Length: 5\r\n\r\n"; ASSERT(mg_http_parse(s, strlen(s), &req) == -1); } } static void ehr(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); opts.root_dir = "./data"; mg_http_serve_dir(c, hm, &opts); } } static void test_http_range(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12350"; struct mg_http_message hm; char buf[FETCH_BUF_SIZE]; mg_mgr_init(&mgr); mg_http_listen(&mgr, url, ehr, NULL); ASSERT(fetch(&mgr, buf, url, "GET /range.txt HTTP/1.0\n\n") == 200); ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); ASSERT(hm.body.len == 312); fetch(&mgr, buf, url, "%s", "GET /range.txt HTTP/1.0\nRange: bytes=5-10\n\n"); ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); printf("%s", buf); ASSERT(mg_strcmp(hm.uri, mg_str("206")) == 0); ASSERT(mg_strcmp(hm.proto, mg_str("Partial Content")) == 0); ASSERT(mg_strcmp(hm.body, mg_str(" of co")) == 0); ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Range"), mg_str("bytes 5-10/312")) == 0); // Fetch till EOF fetch(&mgr, buf, url, "%s", "GET /range.txt HTTP/1.0\nRange: bytes=300-\n\n"); ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); ASSERT(mg_strcmp(hm.uri, mg_str("206")) == 0); ASSERT(mg_strcmp(hm.body, mg_str("is disease.\n")) == 0); // MG_INFO(("----%d\n[%s]", (int) hm.body.len, buf)); // Fetch past EOF, must trigger 416 response fetch(&mgr, buf, url, "%s", "GET /range.txt HTTP/1.0\nRange: bytes=999-\n\n"); ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); ASSERT(mg_strcmp(hm.uri, mg_str("416")) == 0); ASSERT(hm.body.len == 0); ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Range"), mg_str("bytes */312")) == 0); fetch(&mgr, buf, url, "%s", "GET /range.txt HTTP/1.0\nRange: bytes=0-312\n\n"); ASSERT(mg_http_parse(buf, strlen(buf), &hm) > 0); ASSERT(mg_strcmp(hm.uri, mg_str("416")) == 0); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void f1(void *arg) { (*(int *) arg)++; } static void test_timer(void) { int v1 = 0, v2 = 0, v3 = 0; struct mg_timer t1, t2, t3, *head = NULL; mg_timer_init(&head, &t1, 5, MG_TIMER_REPEAT, f1, &v1); mg_timer_init(&head, &t2, 15, MG_TIMER_ONCE, f1, &v2); mg_timer_init(&head, &t3, 10, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, f1, &v3); ASSERT(head == &t3); ASSERT(head->next == &t2); mg_timer_poll(&head, 0); ASSERT(v1 == 0); ASSERT(v2 == 0); ASSERT(v3 == 1); mg_timer_poll(&head, 1); ASSERT(v1 == 0); ASSERT(v2 == 0); ASSERT(v3 == 1); mg_timer_poll(&head, 5); ASSERT(v1 == 1); ASSERT(v2 == 0); ASSERT(v3 == 1); // Simulate long delay - timers must invalidate expiration times mg_timer_poll(&head, 100); ASSERT(v1 == 2); ASSERT(v2 == 1); ASSERT(v3 == 2); mg_timer_poll(&head, 107); ASSERT(v1 == 3); ASSERT(v2 == 1); ASSERT(v3 == 2); mg_timer_poll(&head, 114); ASSERT(v1 == 4); ASSERT(v2 == 1); ASSERT(v3 == 3); mg_timer_poll(&head, 115); ASSERT(v1 == 5); ASSERT(v2 == 1); ASSERT(v3 == 3); mg_timer_free(&head, &t2); mg_timer_init(&head, &t2, 3, 0, f1, &v2); ASSERT(head == &t2); ASSERT(head->next == &t3); ASSERT(head->next->next == &t1); ASSERT(head->next->next->next == NULL); mg_timer_poll(&head, 120); ASSERT(v1 == 6); ASSERT(v2 == 1); ASSERT(v3 == 4); mg_timer_poll(&head, 125); ASSERT(v1 == 7); ASSERT(v2 == 2); ASSERT(v3 == 4); // Test millisecond counter wrap - when time goes back. mg_timer_poll(&head, 0); ASSERT(v1 == 7); ASSERT(v2 == 2); ASSERT(v3 == 4); mg_timer_poll(&head, 7); ASSERT(v1 == 8); ASSERT(v2 == 2); ASSERT(v3 == 4); mg_timer_poll(&head, 11); ASSERT(v1 == 9); ASSERT(v2 == 2); ASSERT(v3 == 5); mg_timer_free(&head, &t1); ASSERT(head == &t2); ASSERT(head->next == &t3); ASSERT(head->next->next == NULL); mg_timer_free(&head, &t2); ASSERT(head == &t3); ASSERT(head->next == NULL); mg_timer_free(&head, &t3); ASSERT(head == NULL); // Start a timer, then shift system time a long time back and long time forth v1 = 0; mg_timer_init(&head, &t1, 5, MG_TIMER_REPEAT, f1, &v1); mg_timer_poll(&head, 0); ASSERT(v1 == 0); // Shift a long time forth, make sure it ticks mg_timer_poll(&head, 100); ASSERT(v1 == 1); mg_timer_poll(&head, 101); ASSERT(v1 == 1); mg_timer_poll(&head, 102); ASSERT(v1 == 1); mg_timer_poll(&head, 103); ASSERT(v1 == 1); mg_timer_poll(&head, 104); ASSERT(v1 == 1); mg_timer_poll(&head, 105); ASSERT(v1 == 2); // Shift a long time back, make sure it ticks mg_timer_poll(&head, 50); ASSERT(v1 == 2); mg_timer_poll(&head, 60); ASSERT(v1 == 3); mg_timer_free(&head, &t1); ASSERT(head == NULL); // Test proper timer deallocation, see #1539 { struct mg_mgr mgr; mg_mgr_init(&mgr); mg_timer_add(&mgr, 1, MG_TIMER_REPEAT, f1, NULL); ASSERT(mgr.timers != NULL); mg_mgr_free(&mgr); ASSERT(mgr.timers == NULL); ASSERT(mgr.conns == NULL); } // Test that non-repeating called timers are deleted, see #2768 { struct mg_mgr mgr; int arg = 0; mg_mgr_init(&mgr); mg_timer_add(&mgr, 0, MG_TIMER_ONCE, f1, &arg); ASSERT(mgr.timers != NULL); mg_mgr_poll(&mgr, 10); ASSERT(arg == 1); ASSERT(mgr.timers == NULL); mg_mgr_free(&mgr); ASSERT(mgr.timers == NULL); ASSERT(mgr.conns == NULL); } } static bool sn(const char *fmt, ...) { char buf[100], tmp[1] = {0}, buf2[sizeof(buf)]; size_t n, n2, n1; va_list ap; bool result; va_start(ap, fmt); n = (size_t) vsnprintf(buf2, sizeof(buf2), fmt, ap); va_end(ap); va_start(ap, fmt); n1 = mg_vsnprintf(buf, sizeof(buf), fmt, &ap); va_end(ap); va_start(ap, fmt); n2 = mg_vsnprintf(tmp, 0, fmt, &ap); va_end(ap); result = n1 == n2 && n1 == n && strcmp(buf, buf2) == 0; if (!result) MG_ERROR(("[%s] -> [%s] != [%s] %d %d %d\n", fmt, buf, buf2, (int) n1, (int) n2, (int) n)); return result; } static bool sccmp(const char *s1, const char *s2, int expected) { int n1 = mg_casecmp(s1, s2); // MG_INFO(("[%s] [%s] %d %d", s1, s2, n1, expected)); return n1 == expected; } static size_t pf1(void (*out)(char, void *), void *ptr, va_list *ap) { int a = va_arg(*ap, int); int b = va_arg(*ap, int); return mg_xprintf(out, ptr, "%d", a + b); } static size_t pf2(void (*out)(char, void *), void *ptr, va_list *ap) { int cnt = va_arg(*ap, int); size_t n = 0; while (cnt-- > 0) n += mg_xprintf(out, ptr, "%d", cnt); return n; } static bool chkdbl(struct mg_str s, double val) { double d, tolerance = 1e-14; return mg_json_get_num(s, "$", &d) && fabs(val - d) < tolerance; } static void test_str(void) { { struct mg_str s = mg_strdup(mg_str("a")); ASSERT(mg_strcmp(s, mg_str("a")) == 0); mg_free((void *) s.buf); } { const char *s; struct mg_str a = mg_str("hello"), b = mg_str("a"), c = mg_str(NULL); ASSERT((s = mgstrstr(a, b)) == NULL); ASSERT((s = mgstrstr(a, c)) != NULL); ASSERT(s == a.buf); } ASSERT(mg_strcmp(mg_str(""), mg_str(NULL)) == 0); ASSERT(mg_strcmp(mg_str("a"), mg_str("b")) < 0); ASSERT(mg_strcmp(mg_str("b"), mg_str("a")) > 0); ASSERT(mg_strcmp(mg_str("hi"), strstrip(mg_str(" \thi\r\n"))) == 0); ASSERT(sccmp("", "", 0)); ASSERT(sccmp("", "1", -49)); ASSERT(sccmp("a", "A", 0)); ASSERT(sccmp("a1", "A", 49)); ASSERT(sccmp("a", "A1", -49)); ASSERT(mg_strcasecmp(mg_str(""), mg_str(NULL)) == 0); ASSERT(mg_strcasecmp(mg_str("a"), mg_str("B")) < 0); ASSERT(mg_strcasecmp(mg_str("b"), mg_str("A")) > 0); ASSERT(mg_strcasecmp(mg_str("hi"), mg_str("HI")) == 0); { ASSERT(chkdbl(mg_str_n("1.23", 3), 1.2)); ASSERT(chkdbl(mg_str("1.23 "), 1.23)); ASSERT(chkdbl(mg_str("-0.01 "), -0.01)); ASSERT(chkdbl(mg_str("-0.5e2"), -50.0)); ASSERT(chkdbl(mg_str("123e-3"), 0.123)); } #if defined(NO_ZU_TEST) || (defined(_MSC_VER) && _MSC_VER < 1700) // VC98 does not have '%zu', some old compilers may also not support it #else ASSERT(sn("%zu", 0)); ASSERT(sn("%zu", -1)); ASSERT(sn("%zu", -2)); ASSERT(sn("%zd", 123)); #if !defined(_MSC_VER) ASSERT(sn("%zd", -3)); // MSVC casts to unsigned #endif ASSERT(sn("%zu", 0x1234)); #endif ASSERT(sn("%d", 0)); ASSERT(sn("%d", 1)); ASSERT(sn("%d", -1)); ASSERT(sn("%.*s", 0, "ab")); ASSERT(sn("%.*s", 1, "ab")); ASSERT(sn("%.1s", "ab")); ASSERT(sn("%.99s", "a")); ASSERT(sn("%11s", "a")); ASSERT(sn("%s", "a\0b")); ASSERT(sn("%2s", "a")); ASSERT(sn("%.*s", 3, "a\0b")); ASSERT(sn("%d", 7)); ASSERT(sn("%d", 123)); #if MG_ARCH == MG_ARCH_UNIX ASSERT(sn("%lld", (uint64_t) 0xffffffffff)); ASSERT(sn("%lld", (uint64_t) -1)); ASSERT(sn("%llu", (uint64_t) -1)); ASSERT(sn("%llx", (uint64_t) 0xffffffffff)); ASSERT(sn("%p", (void *) (size_t) 7)); #endif ASSERT(sn("%lx", (unsigned long) 0x6204d754)); ASSERT(sn("ab")); ASSERT(sn("%dx", 1)); ASSERT(sn("%sx", "a")); ASSERT(sn("%cx", 32)); ASSERT(sn("%x", 15)); ASSERT(sn("%2x", 15)); ASSERT(sn("%02x", 15)); ASSERT(sn("%hx:%hhx", (short) 1, (char) 2)); ASSERT(sn("%hx:%hhx", (short) 1, (char) 2)); ASSERT(sn("%%")); ASSERT(sn("%x", 15)); ASSERT(sn("%#x", 15)); ASSERT(sn("%#6x", 15)); ASSERT(sn("%#06x", 15)); ASSERT(sn("%#-6x", 15)); ASSERT(sn("%-2s!", "a")); ASSERT(sn("%s %s", "a", "b")); ASSERT(sn("%s %s", "a", "b")); ASSERT(sn("ab%dc", 123)); ASSERT(sn("%s ", "a")); ASSERT(sn("%s %s", "a", "b")); ASSERT(sn("%2s %s", "a", "b")); { // mg_queue_printf() struct mg_queue q; char buf[128]; uint32_t p = 0xffffffff; size_t len; mg_queue_init(&q, buf, sizeof(buf)); len = mg_queue_printf(&q, "A%p%p%pB", p, p, p); ASSERT(len == 32); ASSERT(memcmp(buf + 4, "A0xffffffff0xffffffff0xffffffffB", 32) == 0); } // Non-standard formatting { char buf[100], *p = NULL; struct mg_iobuf io = {0, 0, 0, 16}; const char *expected; expected = "\"\""; mg_snprintf(buf, sizeof(buf), "%m", mg_print_esc, 0, ""); ASSERT(strcmp(buf, expected) == 0); expected = ""; mg_snprintf(buf, 1, "%s", "abc"); ASSERT(strcmp(buf, expected) == 0); expected = "a"; mg_snprintf(buf, 2, "%s", "abc"); ASSERT(strcmp(buf, expected) == 0); expected = "\"hi, \\\"\""; mg_snprintf(buf, sizeof(buf), "\"hi, %M\"", mg_print_esc, 0, "\""); MG_INFO(("[%s] [%s]", buf, expected)); ASSERT(strcmp(buf, expected) == 0); expected = "\"a'b\""; mg_snprintf(buf, sizeof(buf), "%m", mg_print_esc, 0, "a'b"); ASSERT(strcmp(buf, expected) == 0); expected = "\"a\\b\\n\\f\\r\\t\\\"\""; mg_snprintf(buf, sizeof(buf), "%m", mg_print_esc, 0, "a\b\n\f\r\t\""); ASSERT(strcmp(buf, expected) == 0); expected = "\"abc\""; mg_snprintf(buf, sizeof(buf), "%m", mg_print_esc, 3, "abcdef"); ASSERT(strcmp(buf, expected) == 0); p = mg_mprintf("[%s,%M,%s]", "null", pf1, 2, 3, "hi"); ASSERT(strcmp(p, "[null,5,hi]") == 0); mg_free(p); p = mg_mprintf("[%M,%d]", pf2, 10, 7); ASSERT(strcmp(p, "[9876543210,7]") == 0); mg_free(p); mg_xprintf(mg_pfn_iobuf, &io, "[%M", pf2, 10); mg_xprintf(mg_pfn_iobuf, &io, ","); mg_xprintf(mg_pfn_iobuf, &io, "%d]", 7); ASSERT(strcmp((char *) io.buf, "[9876543210,7]") == 0); mg_iobuf_free(&io); } { #if MG_ARCH == MG_ARCH_WIN32 bool is_windows = true; #else bool is_windows = false; #endif #define DBLWIDTH(a, b) a, b #define TESTDOUBLE(fmt_, num_, res_) \ do { \ char t1[40] = "", t2[40] = ""; \ const char *N = #num_; \ mg_snprintf(t1, sizeof(t1), fmt_, num_); \ snprintf(t2, sizeof(t2), fmt_, num_); \ printf("[%s,%s] : [%s] [%s] [%s]\n", fmt_, N, res_, t2, t1); \ ASSERT(strcmp(t1, res_) == 0); \ if (!is_windows) ASSERT(strcmp(t1, t2) == 0); \ } while (0) #define TESTDOUBLE_NOHOSTCHECK(fmt_, num_, res_) \ do { \ char t1[40] = "", t2[40] = ""; \ const char *N = #num_; \ mg_snprintf(t1, sizeof(t1), fmt_, num_); \ snprintf(t2, sizeof(t2), fmt_, num_); \ printf("[%s,%s] : [%s] [%s] [%s]\n", fmt_, N, res_, t2, t1); \ ASSERT(strcmp(t1, res_) == 0); \ } while (0) TESTDOUBLE("%g", 0.0, "0"); TESTDOUBLE("%g", 0.123, "0.123"); TESTDOUBLE("%g", 0.00123, "0.00123"); TESTDOUBLE("%g", 0.123456333, "0.123456"); TESTDOUBLE("%g", 123.0, "123"); TESTDOUBLE("%g", 11.5454, "11.5454"); TESTDOUBLE("%g", 11.0001, "11.0001"); TESTDOUBLE("%g", 0.999, "0.999"); TESTDOUBLE("%g", 0.999999, "0.999999"); TESTDOUBLE("%g", 0.9999999, "1"); TESTDOUBLE("%g", 10.9, "10.9"); TESTDOUBLE("%g", 10.01, "10.01"); TESTDOUBLE("%g", 1.0, "1"); TESTDOUBLE("%g", 10.0, "10"); TESTDOUBLE("%g", 100.0, "100"); TESTDOUBLE("%g", 1000.0, "1000"); TESTDOUBLE("%g", 10000.0, "10000"); TESTDOUBLE("%g", 100000.0, "100000"); TESTDOUBLE("%g", 1000000.0, "1e+06"); TESTDOUBLE("%f", 1000000.0, "1000000.000000"); TESTDOUBLE("%g", 10000000.0, "1e+07"); TESTDOUBLE("%f", 10000000.0, "10000000.000000"); TESTDOUBLE("%g", 100000001.0, "1e+08"); TESTDOUBLE("%g", 0.1, "0.1"); TESTDOUBLE("%g", 0.01, "0.01"); TESTDOUBLE("%g", 0.001, "0.001"); TESTDOUBLE("%g", 0.0001, "0.0001"); TESTDOUBLE_NOHOSTCHECK("%g", 0.00001, "0.00001"); // "1e-05" TESTDOUBLE("%g", 0.000001, "1e-06"); TESTDOUBLE("%g", -0.0001, "-0.0001"); TESTDOUBLE_NOHOSTCHECK("%g", -0.00001, "-0.00001"); // "-1e-05" TESTDOUBLE("%g", 10.5454, "10.5454"); TESTDOUBLE("%g", 999999.0, "999999"); TESTDOUBLE("%g", 9999999.0, "1e+07"); TESTDOUBLE("%g", 44556677.0, "4.45567e+07"); TESTDOUBLE("%g", 1234567.2, "1.23457e+06"); TESTDOUBLE("%g", -987.65432, "-987.654"); TESTDOUBLE("%g", 0.0000000001, "1e-10"); TESTDOUBLE("%g", 2.34567e-57, "2.34567e-57"); TESTDOUBLE("%g", -2.34567e-57, "-2.34567e-57"); TESTDOUBLE("%g", 123.456222, "123.456"); TESTDOUBLE("%g", 600.1234, "600.123"); TESTDOUBLE("%g", -600.1234, "-600.123"); TESTDOUBLE("%g", 599.1234, "599.123"); TESTDOUBLE("%g", -599.1234, "-599.123"); TESTDOUBLE("%g", 0.14, "0.14"); TESTDOUBLE("%.*g", DBLWIDTH(7, 9999999.0), "9999999"); TESTDOUBLE("%.*g", DBLWIDTH(10, 0.123456333), "0.123456333"); TESTDOUBLE("%.*g", DBLWIDTH(10, 123.456222), "123.456222"); TESTDOUBLE("%.*g", DBLWIDTH(10, 1e11), "1e+11"); // e > width TESTDOUBLE("%.*g", DBLWIDTH(10, -1e11), "-1e+11"); // -e < -width { struct mg_iobuf io = {0, 0, 0, 16}; mg_xprintf(mg_pfn_iobuf, &io, "%.*g", 88, -1e-88); // > sizeof(tmp) mg_iobuf_free(&io); } TESTDOUBLE("%f", 0.14, "0.140000"); TESTDOUBLE("%.*f", DBLWIDTH(4, 0.14), "0.1400"); TESTDOUBLE("%.*f", DBLWIDTH(3, 0.14), "0.140"); TESTDOUBLE("%.*f", DBLWIDTH(2, 0.14), "0.14"); TESTDOUBLE("%.*f", DBLWIDTH(2, 25.14), "25.14"); TESTDOUBLE("%.*f", DBLWIDTH(1, 0.14), "0.1"); TESTDOUBLE("%.*f", DBLWIDTH(1, 0.19), "0.2"); TESTDOUBLE("%.*f", DBLWIDTH(1, 0.16), "0.2"); TESTDOUBLE_NOHOSTCHECK("%.1f", 0.15, "0.2"); TESTDOUBLE("%.3f", 123.123456789, "123.123"); TESTDOUBLE("%.4f", 123.123456789, "123.1235"); TESTDOUBLE("%.5f", 123.1234567, "123.12346"); TESTDOUBLE_NOHOSTCHECK("%.*f", DBLWIDTH(1, 0.15), "0.2"); TESTDOUBLE("%.5f", 123.12345, "123.12345"); TESTDOUBLE("%.4f", 789.01234, "789.0123"); TESTDOUBLE("%2.3f", 1.23, "1.230"); TESTDOUBLE("%.1f", 1.5, "1.5"); TESTDOUBLE("%.3f", 500.0, "500.000"); TESTDOUBLE("%.1f", 1.15, "1.1"); TESTDOUBLE("%.1f", 0.155, "0.2"); TESTDOUBLE("%.1f", 1.155, "1.2"); TESTDOUBLE("%.1f", 0.155, "0.2"); TESTDOUBLE("%.2f", 0.015, "0.01"); TESTDOUBLE("%.2f", 0.0015, "0.00"); TESTDOUBLE("%.1f", 0.155, "0.2"); TESTDOUBLE("%.3f", 13.12505, "13.125"); #if MG_ARCH == MG_ARCH_WIN32 && defined(_MSC_VER) && _MSC_VER < 1700 // TODO(): for some reason we round down in VC98; skip #else TESTDOUBLE("%.3f", 15.1255, "15.126"); TESTDOUBLE("%.3f", 19.1255, "19.125"); #endif TESTDOUBLE("%.4f", 100.15, "100.1500"); TESTDOUBLE("%.2f", 5.55, "5.55"); #ifndef _WIN32 TESTDOUBLE("%g", (double) INFINITY, "inf"); TESTDOUBLE("%g", (double) -INFINITY, "-inf"); TESTDOUBLE("%g", (double) NAN, "nan"); #else TESTDOUBLE("%g", HUGE_VAL, "inf"); TESTDOUBLE("%g", -HUGE_VAL, "-inf"); #endif } { const char *expected = "[\"MA==\",\"MAo=\",\"MAr+\",\"MAr+Zw==\"]"; char tmp[100], s[] = "0\n\xfeg"; ASSERT(mg_snprintf(tmp, sizeof(tmp), "[%m,%m,%m,%m]", mg_print_base64, 1, s, mg_print_base64, 2, s, mg_print_base64, 3, s, mg_print_base64, 4, s) == 33); ASSERT(strcmp(tmp, expected) == 0); } { const char *expected = "\"002001200220616263\""; char tmp[100], s[] = "\x00 \x01 \x02 abc"; ASSERT(mg_snprintf(tmp, sizeof(tmp), "%m", mg_print_hex, 9, s) == 20); ASSERT(strcmp(tmp, expected) == 0); } { char tmp[3]; ASSERT(mg_snprintf(tmp, sizeof(tmp), "%s", "0123456789") == 10); ASSERT(strcmp(tmp, "01") == 0); ASSERT(tmp[2] == '\0'); } { char buf[100]; struct mg_addr a; uint32_t addr = mg_htonl(0x2000001); memcpy(a.addr.ip, &addr, sizeof(uint32_t)); a.port = mg_htons(3); a.is_ip6 = false; ASSERT(mg_snprintf(buf, sizeof(buf), "%M %d", mg_print_ip, &a, 7) == 9); ASSERT(strcmp(buf, "2.0.0.1 7") == 0); ASSERT(mg_snprintf(buf, sizeof(buf), "%M %d", mg_print_ip_port, &a, 7) == 11); ASSERT(strcmp(buf, "2.0.0.1:3 7") == 0); memset(a.addr.ip, 0, sizeof(a.addr.ip)); a.addr.ip[0] = 1, a.addr.ip[1] = 100, a.addr.ip[2] = 33; a.is_ip6 = true; ASSERT(mg_snprintf(buf, sizeof(buf), "%M %d", mg_print_ip, &a, 7) == 24); ASSERT(strcmp(buf, "[164:2100:0:0:0:0:0:0] 7") == 0); ASSERT(mg_snprintf(buf, sizeof(buf), "%M %d", mg_print_ip_port, &a, 7) == 26); ASSERT(strcmp(buf, "[164:2100:0:0:0:0:0:0]:3 7") == 0); } { char s[1] = {'.'}; ASSERT(mg_path_is_sane(mg_str_n(s, 1)) == true); } { char s[2] = {'/', '.'}; // "/." ASSERT(mg_path_is_sane(mg_str_n(s, 2)) == true); } { char s[3] = {'a', '/', '.'}; // "a/." ASSERT(mg_path_is_sane(mg_str_n(s, 3)) == true); } ASSERT(mg_path_is_sane(mg_str("")) == true); ASSERT(mg_path_is_sane(mg_str("a.b")) == true); ASSERT(mg_path_is_sane(mg_str("a..b")) == true); ASSERT(mg_path_is_sane(mg_str(".a")) == true); ASSERT(mg_path_is_sane(mg_str(".a.")) == true); ASSERT(mg_path_is_sane(mg_str("./")) == true); ASSERT(mg_path_is_sane(mg_str("a..")) == true); ASSERT(mg_path_is_sane(mg_str("././a..")) == true); ASSERT(mg_path_is_sane(mg_str("..")) == false); ASSERT(mg_path_is_sane(mg_str("../")) == false); ASSERT(mg_path_is_sane(mg_str("./..")) == false); ASSERT(mg_path_is_sane(mg_str("a/../")) == false); ASSERT(mg_path_is_sane(mg_str("a/../b")) == false); ASSERT(mg_path_is_sane(mg_str_n("a/..", 2)) == true); ASSERT(mg_path_is_sane(mg_str_n("a/../b", 2)) == true); ASSERT(mg_path_is_sane(mg_str_n("a\0/../b", 7)) == false); } static void fn1(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_ERROR) { mg_free(*(char **) c->fn_data); // See #2263 *(char **) c->fn_data = mg_mprintf("%s", (char *) ev_data); } (void) c; } static void test_dns_error(const char *dns_server_url, const char *errstr) { // Test timeout struct mg_mgr mgr; char *buf = NULL; int i; mg_mgr_init(&mgr); mgr.dns4.url = dns_server_url; mgr.dnstimeout = 10; MG_DEBUG(("opening dummy DNS listener @ [%s]...", dns_server_url)); mg_listen(&mgr, mgr.dns4.url, NULL, NULL); // Just discard our queries mg_http_connect(&mgr, "http://google.com", fn1, &buf); for (i = 0; i < 50 && buf == NULL; i++) mg_mgr_poll(&mgr, 1); mg_mgr_free(&mgr); // MG_DEBUG(("buf: [%s] [%s]", buf, errstr)); ASSERT(buf != NULL && strcmp(buf, errstr) == 0); mg_free(buf); } static void test_dns(void) { struct mg_dns_message dm; // txid flags numQ numA numAP numOP // 0000 00 01 81 80 00 01 00 01 00 00 00 00 07 63 65 73 .............ces // 0010 61 6e 74 61 03 63 6f 6d 00 00 01 00 01 c0 0c 00 anta.com........ // 0020 01 00 01 00 00 02 57 00 04 94 fb 36 ec ......W....6. uint8_t data[] = {0, 1, 0x81, 0x80, 0, 1, 0, 1, 0, 0, 0, 0, 7, 0x63, 0x65, 0x73, 0x61, 0x6e, 0x74, 0x61, 0x03, 0x63, 0x6f, 0x6d, 0, 0, 1, 0, 1, 0xc0, 0x0c, 0, 1, 0, 1, 0, 0, 2, 0x57, 0, 4, 0x94, 0xfb, 0x36, 0xec}; ASSERT(mg_dns_parse(NULL, 0, &dm) == 0); ASSERT(mg_dns_parse(data, sizeof(data), &dm) == 1); ASSERT(strcmp(dm.name, "cesanta.com") == 0); data[30] = 29; // Point a pointer to itself memset(&dm, 0, sizeof(dm)); ASSERT(mg_dns_parse(data, sizeof(data), &dm) == 1); ASSERT(strcmp(dm.name, "") == 0); { // 0000 00 01 81 80 00 01 00 04 00 00 00 00 05 79 61 68 .............yah // 0010 6f 6f 05 63 31 31 32 36 03 63 6f 6d 00 00 01 00 oo.c1126.com.... // 0020 01 c0 0c 00 05 00 01 00 00 0d 34 00 0c 03 77 77 ..........4...ww // 0030 77 05 79 61 68 6f 6f c0 18 c0 2d 00 05 00 01 00 w.yahoo...-..... // 0040 00 00 01 00 14 0b 6e 65 77 2d 66 70 2d 73 68 65 ......new-fp-she // 0050 64 03 77 67 31 01 62 c0 31 c0 45 00 01 00 01 00 d.wg1.b.1.E..... // 0060 00 00 0a 00 04 57 f8 64 d8 c0 45 00 01 00 01 00 .....W.d..E..... // 0070 00 00 0a 00 04 57 f8 64 d7 .....W.d. uint8_t d[] = { 0x00, 0x01, 0x81, 0x80, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x05, 0x79, 0x61, 0x68, 0x6f, 0x6f, 0x05, 0x63, 0x31, 0x31, 0x32, 0x36, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x0d, 0x34, 0x00, 0x0c, 0x03, 0x77, 0x77, 0x77, 0x05, 0x79, 0x61, 0x68, 0x6f, 0x6f, 0xc0, 0x18, 0xc0, 0x2d, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x14, 0x0b, 0x6e, 0x65, 0x77, 0x2d, 0x66, 0x70, 0x2d, 0x73, 0x68, 0x65, 0x64, 0x03, 0x77, 0x67, 0x31, 0x01, 0x62, 0xc0, 0x31, 0xc0, 0x45, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x04, 0x57, 0xf8, 0x64, 0xd8, 0xc0, 0x45, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x04, 0x57, 0xf8, 0x64, 0xd7, }; ASSERT(mg_dns_parse(d, sizeof(d), &dm) == 1); // MG_INFO(("[%s]", dm.name)); ASSERT(strcmp(dm.name, "new-fp-shed.wg1.b.yahoo.com") == 0); } { // DNS Query for domain abc.local // 0000 00 00 00 00 00 01 00 00 00 00 00 00 03 61 62 63 .............abc // 0010 05 6c 6f 63 61 6c 00 00 01 00 01 .local..... uint8_t d[] = { 0x00, 0x00, // txid: 0 0x00, 0x00, // flags: 0 (Query flag = 0) 0x00, 0x01, // numQuestions: 1 0x00, 0x00, // numAnswers: 1 0x00, 0x00, 0x00, 0x00, // additional RRs 0x03, 0x61, 0x62, 0x63, // "abc" 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // "local" 0x00, 0x00, 0x01, 0x00, 0x01 // domain end, type, class }; memset(&dm, 0, sizeof(dm)); ASSERT(mg_dns_parse(d, sizeof(d), &dm) == 1); ASSERT(dm.resolved == false); ASSERT(strcmp(dm.name, "abc.local") == 0); } test_dns_error("udp://127.0.0.1:12345", "DNS timeout"); test_dns_error("", "resolver"); test_dns_error("tcp://0.0.0.0:0", "DNS error"); } static void test_util(void) { const char *e; char buf[100], *s; struct mg_addr a; uint64_t ipv3; uint8_t d64[8] = {0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef}; uint32_t ipv4; uint16_t port; struct mg_str data; memset(&a, 0xa5, sizeof(a)); ASSERT(mg_file_printf(&mg_fs_posix, "data.txt", "%s", "hi") == true); // if (system("ls -l") != 0) (void) 0; data = mg_file_read(&mg_fs_posix, "data.txt"); ASSERT(data.buf != NULL); ASSERT(strcmp(data.buf, "hi") == 0); mg_free((void *) data.buf); remove("data.txt"); ASSERT(mg_aton(mg_str("0"), &a) == false); ASSERT(mg_aton(mg_str("0.0.0."), &a) == false); ASSERT(mg_aton(mg_str("0.0.0.256"), &a) == false); ASSERT(mg_aton(mg_str("0.0.0.-1"), &a) == false); ASSERT(mg_aton(mg_str("127.0.0.1"), &a) == true); ASSERT(a.is_ip6 == false); memcpy(&ipv4, &a.addr.ip4, sizeof(ipv4)); ASSERT(ipv4 == mg_htonl(0x7f000001)); ASSERT(mg_ntohl(ipv4) == 0x7f000001); MG_STORE_BE32(&ipv4, 0x5678abcd); ASSERT(((uint8_t *) &ipv4)[0] == 0x56 && ((uint8_t *) &ipv4)[1] == 0x78 && ((uint8_t *) &ipv4)[2] == 0xab && ((uint8_t *) &ipv4)[3] == 0xcd); ASSERT(MG_LOAD_BE32(&ipv4) == 0x5678abcd); MG_STORE_BE16(&port, 0x1234); ASSERT(((uint8_t *) &port)[0] == 0x12 && ((uint8_t *) &port)[1] == 0x34); ASSERT(MG_LOAD_BE16(&port) == 0x1234); ASSERT(port == mg_htons(0x1234)); ASSERT(mg_ntohs(port) == 0x1234); MG_STORE_BE24(&ipv4, 0xef2345); ASSERT(((uint8_t *) &ipv4)[0] == 0xef && ((uint8_t *) &ipv4)[1] == 0x23 && ((uint8_t *) &ipv4)[2] == 0x45); ASSERT(MG_LOAD_BE24(&ipv4) == 0xef2345); ASSERT(mg_timegm(2023, 1, 1, 0, 0, 0) == 1672531200); ASSERT(mg_timegm(2025, 12, 31, 23, 59, 59) == 1767225599); ASSERT(mg_timegm(2024, 2, 1, 0, 0, 0) == 1706745600); ASSERT(mg_timegm(2000, 4, 1, 0, 0, 0) == 954547200); memcpy(&ipv3, d64, sizeof(ipv3)); #if defined(_MSC_VER) && _MSC_VER < 1700 // VC98 doesn't suppport LL suffix #else ASSERT(ipv3 == mg_htonll(0x1234567890abcdefLL)); ASSERT(mg_ntohll(ipv3) == 0x1234567890abcdefLL); #endif MG_STORE_BE64(&ipv3, 0x5678abcd12349ef0); ASSERT(((uint8_t *) &ipv3)[0] == 0x56 && ((uint8_t *) &ipv3)[1] == 0x78 && ((uint8_t *) &ipv3)[2] == 0xab && ((uint8_t *) &ipv3)[3] == 0xcd && ((uint8_t *) &ipv3)[4] == 0x12 && ((uint8_t *) &ipv3)[5] == 0x34 && ((uint8_t *) &ipv3)[6] == 0x9e && ((uint8_t *) &ipv3)[7] == 0xf0); ASSERT(MG_LOAD_BE64(&ipv3) == 0x5678abcd12349ef0); memset(a.addr.ip, 0xa5, sizeof(a.addr.ip)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7:8"), &a) == true); ASSERT(a.is_ip6 == true); e = "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08"; ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0); memset(a.addr.ip, 0xa5, sizeof(a.addr.ip)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("1:2::3"), &a) == true); ASSERT(a.is_ip6 == true); e = "\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03"; ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0); memset(a.addr.ip, 0xaa, sizeof(a.addr.ip)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("1::1"), &a) == true); ASSERT(a.is_ip6 == true); e = "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"; ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0); memset(a.addr.ip, 0xaa, sizeof(a.addr.ip)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("::fFff:1.2.3.4"), &a) == true); ASSERT(a.is_ip6 == true); e = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04"; ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0); memset(a.addr.ip, 0xaa, sizeof(a.addr.ip)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("::1"), &a) == true); ASSERT(a.is_ip6 == true); ASSERT(a.scope_id == 0); e = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"; ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0); memset(a.addr.ip, 0xaa, sizeof(a.addr.ip)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("1::"), &a) == true); ASSERT(a.is_ip6 == true); e = "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0); memset(a.addr.ip, 0xaa, sizeof(a.addr.ip)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("2001:4860:4860::8888"), &a) == true); ASSERT(a.is_ip6 == true); e = "\x20\x01\x48\x60\x48\x60\x00\x00\x00\x00\x00\x00\x00\x00\x88\x88"; ASSERT(memcmp(&a.addr.ip6, e, sizeof(a.addr.ip6)) == 0); ASSERT(mg_aton(mg_str("12345::1"), &a) == false); ASSERT(mg_aton(mg_str("1:::2"), &a) == false); ASSERT(mg_aton(mg_str("1::2::3"), &a) == false); ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7"), &a) == false); ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7:8:9"), &a) == false); ASSERT(mg_aton(mg_str("1:2:3:4:5:6:7:g"), &a) == false); ASSERT(mg_url_decode("a=%", 3, buf, sizeof(buf), 0) < 0); ASSERT(mg_url_decode("&&&a=%", 6, buf, sizeof(buf), 0) < 0); ASSERT(mg_url_decode("a=%1", 4, buf, sizeof(buf), 0) < 0); ASSERT(mg_url_decode("a=%12", 5, buf, sizeof(buf), 0) == 3 && buf[2] == 0x12); ASSERT(mg_url_decode("a=%123", 6, buf, sizeof(buf), 0) == 4 && buf[2] == 0x12 && buf[3] == '3'); memset(&a, 0, sizeof(a)); ASSERT(mg_aton(mg_str("::1%1"), &a) == true); ASSERT(a.is_ip6 == true); ASSERT(a.scope_id == 1); memset(&a, 0, sizeof(a)); ASSERT(mg_aton(mg_str("abcd::aabb:ccdd%17"), &a) == true); ASSERT(a.is_ip6 == true); ASSERT(a.scope_id == 17); memset(&a, 0xaa, sizeof(a)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("::1%17"), &a) == true); ASSERT(a.is_ip6 == true); ASSERT(a.scope_id == 17); memset(&a, 0xaa, sizeof(a)), a.is_ip6 = false; ASSERT(mg_aton(mg_str("::1%255"), &a) == true); ASSERT(a.is_ip6 == true); ASSERT(a.scope_id == 255); { size_t n; ASSERT((n = mg_url_encode("", 0, buf, sizeof(buf))) == 0); ASSERT((n = mg_url_encode("a", 1, buf, 0)) == 0); ASSERT((n = mg_url_encode("a", 1, buf, sizeof(buf))) == 1); ASSERT(strncmp(buf, "a", n) == 0); ASSERT((n = mg_url_encode("._-~", 4, buf, sizeof(buf))) == 4); ASSERT(strncmp(buf, "._-~", n) == 0); ASSERT((n = mg_url_encode("a@%>", 4, buf, sizeof(buf))) == 10); ASSERT(strncmp(buf, "a%40%25%3e", n) == 0); ASSERT((n = mg_url_encode("a@b.c", 5, buf, sizeof(buf))) == 7); ASSERT(strncmp(buf, "a%40b.c", n) == 0); } { s = mg_mprintf("%3d", 123); ASSERT(strcmp(s, "123") == 0); mg_free(s); } { extern bool mg_to_size_t(struct mg_str, size_t *); size_t val, max = (size_t) -1; ASSERT(mg_to_size_t(mg_str("0"), &val) && val == 0); ASSERT(mg_to_size_t(mg_str("123"), &val) && val == 123); ASSERT(mg_to_size_t(mg_str(" 123 \t"), &val) && val == 123); ASSERT(mg_to_size_t(mg_str(""), &val) == false); ASSERT(mg_to_size_t(mg_str(" 123x"), &val) == false); ASSERT(mg_to_size_t(mg_str("-"), &val) == false); mg_snprintf(buf, sizeof(buf), sizeof(max) == 8 ? "%llu" : "%lu", max); ASSERT(mg_to_size_t(mg_str(buf), &val) && val == max); } { uint64_t val, max = (uint64_t) -1; ASSERT(mg_str_to_num(mg_str("0"), 10, &val, sizeof(uint64_t)) && val == 0); ASSERT(mg_str_to_num(mg_str("123"), 10, &val, sizeof(uint64_t)) && val == 123); ASSERT(mg_str_to_num(mg_str(" 123"), 10, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str("123 "), 10, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str(""), 10, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str(" 123x"), 10, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str("-"), 10, &val, sizeof(uint64_t)) == false); mg_snprintf(buf, sizeof(buf), "%llu", max); ASSERT(mg_str_to_num(mg_str(buf), 10, &val, sizeof(uint64_t)) && val == max); ASSERT(mg_str_to_num(mg_str("0"), 2, &val, sizeof(uint64_t)) && val == 0); ASSERT(mg_str_to_num(mg_str("1"), 2, &val, sizeof(uint64_t)) && val == 1); ASSERT(mg_str_to_num(mg_str("0123"), 2, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str("123"), 2, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str("01111011"), 2, &val, sizeof(uint64_t)) && val == 123); ASSERT(mg_str_to_num(mg_str("1111111111111111111111111111111111111111111111" "111111111111111111"), 2, &val, sizeof(uint64_t)) && val == max); ASSERT(mg_str_to_num(mg_str("0"), 16, &val, sizeof(uint64_t)) && val == 0); ASSERT(mg_str_to_num(mg_str("123"), 16, &val, sizeof(uint64_t)) && val == 0x123); ASSERT(mg_str_to_num(mg_str("def"), 16, &val, sizeof(uint64_t)) && val == 0xdef); ASSERT(mg_str_to_num(mg_str("defg"), 16, &val, sizeof(uint64_t)) == false); mg_snprintf(buf, sizeof(buf), "%llx", max); ASSERT(mg_str_to_num(mg_str(buf), 16, &val, sizeof(uint64_t)) && val == max); ASSERT(mg_str_to_num(mg_str("0x123"), 0, &val, sizeof(uint64_t)) && val == 0x123); ASSERT(mg_str_to_num(mg_str("0b123"), 0, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str("0c123"), 0, &val, sizeof(uint64_t)) == false); ASSERT(mg_str_to_num(mg_str("0b101"), 0, &val, sizeof(uint64_t)) && val == 5); ASSERT(mg_str_to_num(mg_str("0123"), 0, &val, sizeof(uint64_t)) && val == 123); } { uint32_t val, max = (uint32_t) -1; ASSERT(mg_str_to_num(mg_str("123"), 10, &val, sizeof(uint32_t)) && val == 123); mg_snprintf(buf, sizeof(buf), "%lu", (unsigned long) max); ASSERT(strcmp(buf, "4294967295") == 0); ASSERT(mg_str_to_num(mg_str(buf), 10, &val, sizeof(uint32_t)) && val == max); ASSERT(mg_str_to_num(mg_str("01111011"), 2, &val, sizeof(uint32_t)) && val == 123); ASSERT(mg_str_to_num(mg_str("11111111111111111111111111111111"), 2, &val, sizeof(uint32_t)) && val == max); ASSERT(mg_str_to_num(mg_str("0"), 16, &val, sizeof(uint32_t)) && val == 0); ASSERT(mg_str_to_num(mg_str("123"), 16, &val, sizeof(uint32_t)) && val == 0x123); mg_snprintf(buf, sizeof(buf), "%lx", max); ASSERT(mg_str_to_num(mg_str(buf), 16, &val, sizeof(uint32_t)) && val == max); } { uint16_t val, max = (uint16_t) -1; ASSERT(mg_str_to_num(mg_str("123"), 10, &val, sizeof(uint16_t)) && val == 123); mg_snprintf(buf, sizeof(buf), "%u", max); ASSERT(mg_str_to_num(mg_str(buf), 10, &val, sizeof(uint16_t)) && val == max); ASSERT(mg_str_to_num(mg_str("01111011"), 2, &val, sizeof(uint16_t)) && val == 123); ASSERT( mg_str_to_num(mg_str("1111111111111111"), 2, &val, sizeof(uint16_t)) && val == max); ASSERT(mg_str_to_num(mg_str("123"), 16, &val, sizeof(uint16_t)) && val == 0x123); mg_snprintf(buf, sizeof(buf), "%x", max); ASSERT(mg_str_to_num(mg_str(buf), 16, &val, sizeof(uint16_t)) && val == max); } { uint8_t val, max = (uint8_t) -1; ASSERT(mg_str_to_num(mg_str("123"), 10, &val, sizeof(uint8_t)) && val == 123); mg_snprintf(buf, sizeof(buf), "%u", max); ASSERT(mg_str_to_num(mg_str(buf), 10, &val, sizeof(uint8_t)) && val == max); ASSERT(mg_str_to_num(mg_str("01111011"), 2, &val, sizeof(uint8_t)) && val == 123); ASSERT(mg_str_to_num(mg_str("11111111"), 2, &val, sizeof(uint8_t)) && val == max); ASSERT(mg_str_to_num(mg_str("12"), 16, &val, sizeof(uint8_t)) && val == 0x12); mg_snprintf(buf, sizeof(buf), "%x", max); ASSERT(mg_str_to_num(mg_str(buf), 16, &val, sizeof(uint8_t)) && val == max); } { size_t i; memset(buf, ' ', sizeof(buf)); mg_random_str(buf, 0); ASSERT(buf[0] == ' '); mg_random_str(buf, 1); ASSERT(buf[0] == '\0'); ASSERT(buf[1] == ' '); mg_random_str(buf, sizeof(buf)); ASSERT(buf[sizeof(buf) - 1] == '\0'); for (i = 0; i < sizeof(buf) - 1; i++) ASSERT(isalnum((uint8_t) buf[i])); } } static void test_crc32(void) { // echo -n aaa | cksum -o3 ASSERT(mg_crc32(0, 0, 0) == 0); ASSERT(mg_crc32(0, "a", 1) == 3904355907U); ASSERT(mg_crc32(0, "abc", 3) == 891568578); ASSERT(mg_crc32(mg_crc32(0, "ab", 2), "c", 1) == 891568578); } static void test_crc16(void) { ASSERT(mg_crc16(0, 0, 0) == 0); ASSERT(mg_crc16(0, "a", 1) == 0x82f7); ASSERT(mg_crc16(0, "abc", 3) == 0x9e25); ASSERT(mg_crc16(mg_crc16(0, "ab", 2), "c", 1) == 0x9e25); } static void us(struct mg_connection *c, int ev, void *ev_data) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; if (ev == MG_EV_HTTP_MSG && mg_match(hm->uri, mg_str("/upload"), NULL)) { MG_DEBUG(("Got all %lu bytes!", (unsigned long) hm->body.len)); MG_DEBUG(("Query string: [%.*s]", (int) hm->query.len, hm->query.buf)); // MG_DEBUG(("Body:\n%.*s", (int) hm->body.len, hm->body.buf)); mg_http_reply(c, 200, "", "ok (%d %.*s)\n", (int) hm->body.len, (int) hm->body.len, hm->body.buf); } else if (ev == MG_EV_HTTP_MSG) { mg_http_reply(c, 200, "", "ok\n"); } } static void uc(struct mg_connection *c, int ev, void *ev_data) { const char **s = (const char **) c->fn_data; if (ev == MG_EV_OPEN) { // c->is_hexdumping = 1; } else if (ev == MG_EV_CONNECT) { mg_printf(c, "POST /upload HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n\r\n"); mg_http_printf_chunk(c, "%s", "foo\n"); mg_http_printf_chunk(c, "%s", "bar\n"); mg_http_printf_chunk(c, ""); } else if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; // MG_INFO(("---> [%s] [%.*s]", *s, hm->body.len, hm->body.buf)); ASSERT(mg_strcmp(hm->body, mg_str(*s)) == 0); *s = NULL; } } static int s_su_done; // 1: upload ended, -1: upload rejected static int s_su_end; // Number of end calls // mg_http_stream_body() callback, saves the body to a file named as the URI static bool su_cb(struct mg_http_message *hm, struct mg_str *data, void **p) { if (hm != NULL) { // Start char path[100]; struct mg_str name = mg_str_n(hm->uri.buf + 1, hm->uri.len - 1); *p = NULL; if (!mg_path_is_sane(name)) { s_su_done = -1; return false; } mg_snprintf(path, sizeof(path), "./%.*s", (int) name.len, name.buf); *p = mg_fs_open(&mg_fs_posix, path, MG_FS_WRITE); return *p != NULL; } else if (data != NULL) { // Next chunk struct mg_fd *fd = (struct mg_fd *) *p; return fd != NULL && fd->fs->wr(fd->fd, data->buf, data->len) == data->len; } s_su_end++; // End, also called when the connection drops if (*p != NULL) mg_fs_close((struct mg_fd *) *p); *p = NULL; if (s_su_done == 0) s_su_done = 1; return true; } static void su(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_HDRS) { mg_http_stream_body(c, (struct mg_http_message *) ev_data, su_cb, NULL); } else if (ev == MG_EV_HTTP_MSG) { // Only for requests that were not streamed mg_http_reply(c, 400, "", "not streamed\n"); } } // Client that announces a large body, sends a few bytes and stays silent static void cd(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_CONNECT) { mg_printf(c, "POST /su_drop.txt HTTP/1.0\r\nContent-Length: 100000\r\n\r\nfoo\n"); } (void) ev_data; } static char s_cr_buf[4096]; // Response body streamed by the client static size_t s_cr_len; static int s_cr_end; static bool cr_cb(struct mg_http_message *hm, struct mg_str *data, void **p) { (void) p; if (hm != NULL) { s_cr_len = 0; } else if (data != NULL) { ASSERT(s_cr_len + data->len <= sizeof(s_cr_buf)); memcpy(s_cr_buf + s_cr_len, data->buf, data->len); s_cr_len += data->len; } else { s_cr_end++; } return true; } // Client that streams the response body with mg_http_stream_body() static void cr(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_CONNECT) { mg_printf(c, "GET / HTTP/1.0\r\n\r\n"); } else if (ev == MG_EV_HTTP_HDRS) { ASSERT(mg_http_stream_body(c, (struct mg_http_message *) ev_data, cr_cb, NULL)); } } static void cs(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { char body[3000]; memset(body, 'x', sizeof(body)); mg_http_reply(c, 200, "", "%.*s", (int) sizeof(body), body); } (void) ev_data; } static void test_http_upload(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12352"; int i; const char *s = "ok (8 foo\nbar\n)\n"; mg_mgr_init(&mgr); mg_http_listen(&mgr, url, us, NULL); mg_http_connect(&mgr, url, uc, (void *) &s); for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 5); ASSERT(s == NULL); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); // mg_http_stream_body: successful upload, file content verified { char buf[FETCH_BUF_SIZE]; struct mg_str fc; remove("su_ok.txt"); s_su_done = s_su_end = 0; mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://127.0.0.1:12355", su, NULL); ASSERT(fetch(&mgr, buf, "http://127.0.0.1:12355", "POST /su_ok.txt HTTP/1.0\r\nContent-Length: 8\r\n\r\nfoo\nbar\n") == 200); ASSERT(s_su_done == 1 && s_su_end == 1); fc = mg_file_read(&mg_fs_posix, "su_ok.txt"); ASSERT(mg_strcmp(fc, mg_str("foo\nbar\n")) == 0); mg_free((void *) fc.buf); remove("su_ok.txt"); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } // mg_http_stream_body: path traversal rejected by the callback, "500" sent { char buf[FETCH_BUF_SIZE]; s_su_done = s_su_end = 0; mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://127.0.0.1:12356", su, NULL); ASSERT(fetch(&mgr, buf, "http://127.0.0.1:12356", "POST /../evil.txt HTTP/1.0\r\nContent-Length: 8\r\n\r\nfoo\nbar\n") == 500); ASSERT(s_su_done == -1 && s_su_end == 1); ASSERT(fopen("../evil.txt", "r") == NULL); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } // mg_http_stream_body: chunked request is not streamed, caller replies "400" { char buf[FETCH_BUF_SIZE]; s_su_done = s_su_end = 0; remove("su_chunked.txt"); mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://127.0.0.1:12393", su, NULL); ASSERT(fetch(&mgr, buf, "http://127.0.0.1:12393", "POST /su_chunked.txt HTTP/1.1\r\n" "Transfer-Encoding: chunked\r\n\r\n" "3\r\nfoo\r\n0\r\n\r\n") == 400); ASSERT(s_su_done == 0 && s_su_end == 0); // The callback was never called ASSERT(fopen("su_chunked.txt", "r") == NULL); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } // mg_http_stream_body: connection drops mid-body, the end call still happens { struct mg_connection *cc; s_su_done = s_su_end = 0; remove("su_drop.txt"); mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://127.0.0.1:12391", su, NULL); cc = mg_http_connect(&mgr, "http://127.0.0.1:12391", cd, NULL); ASSERT(cc != NULL); for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 5); ASSERT(s_su_end == 0); // Still waiting for the rest of the body cc->is_closing = 1; for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 5); ASSERT(s_su_end == 1); remove("su_drop.txt"); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } // mg_http_stream_body on a client connection: response body is streamed { s_cr_end = 0; s_cr_len = 0; mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://127.0.0.1:12392", cs, NULL); mg_http_connect(&mgr, "http://127.0.0.1:12392", cr, NULL); for (i = 0; i < 50 && s_cr_end == 0; i++) mg_mgr_poll(&mgr, 5); ASSERT(s_cr_end == 1); ASSERT(s_cr_len == 3000); ASSERT(s_cr_buf[0] == 'x' && s_cr_buf[2999] == 'x'); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } } #define LONG_CHUNK "chunk with length taking up more than two hex digits" static void eX(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { mg_printf(c, "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); c->data[0] = 1; // c->is_hexdumping = 1; } else if (ev == MG_EV_POLL && c->data[0] != 0) { c->data[0]++; if (c->data[0] == 10) mg_http_printf_chunk(c, "a"); if (c->data[0] == 20) { mg_http_printf_chunk(c, "b"); mg_http_printf_chunk(c, "c"); } if (c->data[0] == 30) { mg_http_printf_chunk(c, "d"); mg_http_printf_chunk(c, ""); c->data[0] = 0; } } (void) ev_data; } static void eY(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { mg_printf(c, "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n"); c->data[0] = 1; } else if (ev == MG_EV_POLL && c->data[0] != 0) { c->data[0]++; if (c->data[0] == 10) mg_send(c, "a", 1); if (c->data[0] == 12) mg_send(c, "bc", 2); if (c->data[0] == 30) mg_send(c, "d", 1), c->pfn_data = NULL, c->data[0] = 0; } (void) ev_data; } static void eZ(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { mg_http_reply(c, 200, "", "abcd"); } (void) ev_data; } static void eS(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { ASSERT(mg_send(c, "NADA", 0)); mg_http_reply(c, 200, "", "abcd"); } (void) ev_data; } // Do not delete chunks as they arrive static void eh4(struct mg_connection *c, int ev, void *ev_data) { uint32_t *crc = (uint32_t *) c->fn_data; if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; *crc = mg_crc32(*crc, hm->body.buf, hm->body.len); MG_INFO(("%lu M [%.*s]", c->id, (int) hm->body.len, hm->body.buf)); } } static void test_http_chunked_case(mg_event_handler_t s, mg_event_handler_t c, int req_count, const char *expected) { char url[100]; struct mg_mgr mgr; uint32_t i, crc = 0, expected_crc = mg_crc32(0, expected, strlen(expected)); struct mg_connection *conn; static uint16_t port = 32344; // To prevent bind errors on Windows mg_snprintf(url, sizeof(url), "http://127.0.0.1:%d", port++); mg_mgr_init(&mgr); mg_http_listen(&mgr, url, s, NULL); conn = mg_http_connect(&mgr, url, c, &crc); while (conn != NULL && req_count-- > 0) { mg_printf(conn, "GET / HTTP/1.0\n\n"); } for (i = 0; i < 100 && crc != expected_crc; i++) { mg_mgr_poll(&mgr, 1); } // MG_INFO(("-------- %d [%s]", i, expected)); ASSERT(i < 100); ASSERT(crc == expected_crc); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_http_chunked(void) { // test mg_send allows calls with 0 length test_http_chunked_case(eS, eh4, 1, "abcd"); // Non-chunked encoding test_http_chunked_case(eY, eh4, 1, "abcd"); // Chunks not deleted test_http_chunked_case(eY, eh4, 2, "abcdabcd"); test_http_chunked_case(eZ, eh4, 1, "abcd"); // Not deleted test_http_chunked_case(eZ, eh4, 2, "abcdabcd"); // Chunked encoding test_http_chunked_case(eX, eh4, 1, "abcd"); // Chunks not deleted test_http_chunked_case(eX, eh4, 2, "abcdabcd"); } static void test_invalid_listen_addr(void) { struct mg_mgr mgr; struct mg_connection *c; mg_mgr_init(&mgr); c = mg_http_listen(&mgr, "invalid:31:14", eh1, NULL); ASSERT(c == NULL); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } struct stream_status { uint32_t polls; size_t sent; size_t received; uint32_t send_crc; uint32_t recv_crc; }; // Consume recv buffer after letting it reach MG_MAX_RECV_SIZE static void eh8(struct mg_connection *c, int ev, void *ev_data) { struct stream_status *status = (struct stream_status *) c->fn_data; if (c->is_listening) return; ASSERT(c->recv.len <= MG_MAX_RECV_SIZE); if (ev == MG_EV_ACCEPT) { // Optimize recv buffer size near max to speed up test mg_iobuf_resize(&c->recv, MG_MAX_RECV_SIZE - MG_IO_SIZE); status->received = 0; status->recv_crc = 0; } if (ev == MG_EV_CLOSE) { ASSERT(status->received == status->sent); } // Let buffer fill up and start consuming after 10 full buffer poll events if (status->polls >= 10 && ev == MG_EV_POLL) { // consume at most a third of MG_MAX_RECV_SIZE on each poll size_t consume; if (MG_MAX_RECV_SIZE / 3 >= c->recv.len) consume = c->recv.len; else consume = MG_MAX_RECV_SIZE / 3; status->received += consume; status->recv_crc = mg_crc32(status->recv_crc, (const char *) c->recv.buf, consume); mg_iobuf_del(&c->recv, 0, consume); } // count polls with full buffer to ensure c->is_full prevents reads if (ev == MG_EV_POLL && c->recv.len == MG_MAX_RECV_SIZE) status->polls += 1; (void) ev_data; } // Toggle c->is_full to prevent max_recv_buf_size reached read errors static void eh10(struct mg_connection *c, int ev, void *ev_data) { if (c->recv.len >= MG_MAX_RECV_SIZE && ev == MG_EV_READ) c->is_full = true; eh8(c, ev, ev_data); if (c->recv.len < MG_MAX_RECV_SIZE && ev == MG_EV_POLL) c->is_full = false; } // Send buffer larger than MG_MAX_RECV_SIZE to server static void eh11(struct mg_connection *c, int ev, void *ev_data) { struct stream_status *status = (struct stream_status *) c->fn_data; if (ev == MG_EV_CONNECT) { size_t len = MG_MAX_RECV_SIZE * 2; struct mg_iobuf buf = {NULL, 0, 0, 0}; mg_iobuf_init(&buf, len, 0); mg_random(buf.buf, buf.size); buf.len = buf.size; mg_send(c, buf.buf, buf.len); status->sent = buf.len; status->send_crc = mg_crc32(0, (const char *) buf.buf, buf.len); mg_iobuf_free(&buf); } (void) ev_data; } static void test_http_stream_buffer(void) { struct mg_mgr mgr; const char *url = "tcp://127.0.0.1:12344"; uint32_t i; struct stream_status status; mg_mgr_init(&mgr); mg_listen(&mgr, url, eh10, &status); status.polls = 0; mg_connect(&mgr, url, eh11, &status); for (i = 0; i < (MG_MAX_RECV_SIZE / MG_IO_SIZE) * 50; i++) { mg_mgr_poll(&mgr, 1); if (status.polls >= 10 && status.sent == status.received) break; } ASSERT(status.sent == status.received); ASSERT(status.send_crc == status.recv_crc); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_multipart(void) { struct mg_http_part part; size_t ofs; const char *s = "--xyz\r\n" "Content-Disposition: form-data; name=\"val\"\r\n" "\r\n" "abc\r\ndef\r\n" "--xyz\r\n" "Content-Disposition: form-data; name=\"foo\"; filename=\"a b.txt\"\r\n" "Content-Type: text/plain\r\n" "\r\n" "hello world\r\n" "\r\n" "--xyz--\r\n"; const char *bad_multipart = "--xyz\r\n" "Content-Disposition: form-data; name=\"foo\"; filename=\"safe\rname.txt\"\r\n" "Content-Type: text/plain\r\n" "\r\n" "hello\r\n" "--xyz--\r\n"; ASSERT(mg_http_next_multipart(mg_str(""), 0, NULL) == 0); ASSERT(mg_http_next_multipart(mg_str(bad_multipart), 0, &part) > 0); ASSERT(mg_strcmp(part.name, mg_str("foo")) == 0); ASSERT(mg_strcmp(part.filename, mg_str("safe\rname.txt")) == 0); ASSERT(mg_strcmp(part.body, mg_str("hello")) == 0); ASSERT((ofs = mg_http_next_multipart(mg_str(s), 0, &part)) > 0); ASSERT(mg_strcmp(part.name, mg_str("val")) == 0); // MG_INFO(("--> [%.*s]", (int) part.body.len, part.body.buf)); ASSERT(mg_strcmp(part.body, mg_str("abc\r\ndef")) == 0); ASSERT(part.filename.len == 0); ASSERT((ofs = mg_http_next_multipart(mg_str(s), ofs, &part)) > 0); ASSERT(mg_strcmp(part.name, mg_str("foo")) == 0); // MG_INFO(("--> [%.*s]", (int) part.filename.len, part.filename.buf)); ASSERT(mg_strcmp(part.filename, mg_str("a b.txt")) == 0); ASSERT(mg_strcmp(part.body, mg_str("hello world\r\n")) == 0); ASSERT(mg_http_next_multipart(mg_str(s), ofs, &part) == 0); } static void eh7(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); sopts.root_dir = "/"; sopts.fs = &mg_fs_packed; sopts.enable_dir_listing = true; mg_mem_files = mg_packed_files; mg_http_serve_dir(c, hm, &sopts); } } static void test_packed(void) { struct mg_mgr mgr; const char *url = "http://127.0.0.1:12351"; char buf[FETCH_BUF_SIZE]; struct mg_str data = mg_file_read(&mg_fs_posix, "Makefile"); mg_mgr_init(&mgr); mg_http_listen(&mgr, url, eh7, NULL); // Load top level file directly // fetch(&mgr, buf, url, "GET /Makefile HTTP/1.0\n\n"); // printf("---> %s\n", buf); ASSERT(fetch(&mgr, buf, url, "GET /Makefile HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, data.buf) == 0); mg_free((void *) data.buf); // Load file deeper in the FS tree directly data = mg_file_read(&mg_fs_posix, "data/ssi.h"); ASSERT(fetch(&mgr, buf, url, "GET /data/ssi.h HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, data.buf) == 0); mg_free((void *) data.buf); // List root dir ASSERT(fetch(&mgr, buf, url, "GET / HTTP/1.0\n\n") == 200); // printf("--------\n%s\n", buf); // List nested dir ASSERT(fetch(&mgr, buf, url, "GET /data HTTP/1.0\n\n") == 301); ASSERT(fetch(&mgr, buf, url, "GET /data/ HTTP/1.0\n\n") == 200); // printf("--------\n%s\n", buf); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } #if (MG_ENABLE_SOCKET == 0) int send(int sock, const void *buf, size_t len, int flags); int send(int sock, const void *buf, size_t len, int flags) { (void) sock, (void) buf, (void) len, (void) flags; return -1; } #endif static void u1(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_CONNECT) { ((int *) c->fn_data)[0] += 1; mg_send(c, "hi", 2); } else if (ev == MG_EV_WRITE) { ((int *) c->fn_data)[0] += 100; } else if (ev == MG_EV_READ) { ((int *) c->fn_data)[0] += 10; mg_iobuf_free(&c->recv); } (void) ev_data; } static void test_udp(void) { struct mg_mgr mgr; const char *url = "udp://127.0.0.1:12353"; int i, done = 0; mg_mgr_init(&mgr); mg_listen(&mgr, url, u1, (void *) &done); mg_connect(&mgr, url, u1, (void *) &done); for (i = 0; i < 5; i++) mg_mgr_poll(&mgr, 1); // MG_INFO(("%d", done)); ASSERT(done == 111); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_check_ip_acl(void) { struct mg_addr ip = {{{1, 2, 3, 4}}, 0, 0, false}; // 1.2.3.4 ASSERT(mg_check_ip_acl(mg_str(NULL), &ip) == 1); ASSERT(mg_check_ip_acl(mg_str(""), &ip) == 1); ASSERT(mg_check_ip_acl(mg_str("invalid"), &ip) == -1); ASSERT(mg_check_ip_acl(mg_str("+hi"), &ip) == -2); ASSERT(mg_check_ip_acl(mg_str("+//"), &ip) == -2); ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) == 0); ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/8"), &ip) == 1); ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.2.3.4"), &ip) == 1); ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/16"), &ip) == 0); ip.is_ip6 = true; ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) == -1); // not yet supported } static void w3(struct mg_connection *c, int ev, void *ev_data) { // MG_INFO(("ev %d", ev)); if (ev == MG_EV_WS_OPEN) { char buf[8192]; memset(buf, 'A', sizeof(buf)); mg_ws_send(c, "hi there!", 9, WEBSOCKET_OP_TEXT); mg_ws_printf(c, WEBSOCKET_OP_TEXT, "%s", "hi there2!"); mg_printf(c, "%s", "boo"); mg_ws_wrap(c, 3, WEBSOCKET_OP_TEXT); mg_ws_send(c, buf, sizeof(buf), WEBSOCKET_OP_TEXT); } else if (ev == MG_EV_WS_MSG) { struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; ASSERT(mg_strcmp(wm->data, mg_str("lebowski")) == 0); ((int *) c->fn_data)[0]++; } else if (ev == MG_EV_CLOSE) { ((int *) c->fn_data)[0] += 10; } } static void w2(struct mg_connection *c, int ev, void *ev_data) { struct mg_str msg = mg_str_n("lebowski", 8); if (ev == MG_EV_HTTP_MSG) { mg_ws_upgrade(c, (struct mg_http_message *) ev_data, NULL); } else if (ev == MG_EV_WS_OPEN) { mg_ws_send(c, "x", 1, WEBSOCKET_OP_PONG); } else if (ev == MG_EV_POLL && c->is_websocket) { size_t ofs, n = (size_t) c->fn_data; if (n < msg.len) { // Send "msg" char by char using fragmented frames // mg_ws_send() sets the FIN flag in the WS header. Clean it // to send fragmented packet. Insert PONG messages between frames uint8_t op = n == 0 ? WEBSOCKET_OP_TEXT : WEBSOCKET_OP_CONTINUE; mg_ws_send(c, ":->", 3, WEBSOCKET_OP_PING); ofs = c->send.len; mg_ws_send(c, &msg.buf[n], 1, op); if (n < msg.len - 1) c->send.buf[ofs] = op; // Clear FIN flag c->fn_data = (void *) (n + 1); // Point to the next char } else { mg_ws_send(c, "", 0, WEBSOCKET_OP_CLOSE); } } else if (ev == MG_EV_WS_MSG) { struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; MG_INFO(("Got WS, %lu", wm->data.len)); // mg_hexdump(wm->data.buf, wm->data.len); if (wm->data.len == 9) { ASSERT(mg_strcmp(wm->data, mg_str("hi there!")) == 0); } else if (wm->data.len == 10) { ASSERT(mg_strcmp(wm->data, mg_str("hi there2!")) == 0); } else if (wm->data.len == 3) { ASSERT(mg_strcmp(wm->data, mg_str("boo")) == 0); } else { MG_INFO(("%lu", wm->data.len)); ASSERT(wm->data.len == 8192); } } } static void test_ws_fragmentation(void) { const char *url = "ws://localhost:12357/ws"; struct mg_mgr mgr; int i, done = 0; mg_mgr_init(&mgr); ASSERT(mg_http_listen(&mgr, url, w2, NULL) != NULL); mg_ws_connect(&mgr, url, w3, &done, "%s", "Sec-WebSocket-Protocol: echo\r\n"); for (i = 0; i < 25; i++) mg_mgr_poll(&mgr, 1); // MG_INFO(("--> %d", done)); ASSERT(done == 11); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void h7(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_HTTP_MSG) { struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_serve_opts opts; memset(&opts, 0, sizeof(opts)); opts.root_dir = "./data,/foo=./dirtest"; opts.enable_dir_listing = true; mg_http_serve_dir(c, hm, &opts); } } static void test_rewrites(void) { char buf[FETCH_BUF_SIZE]; const char *url = "http://LOCALHOST:12358"; const char *expected = "#define MG_VERSION \"" MG_VERSION "\"\n"; struct mg_mgr mgr; mg_mgr_init(&mgr); ASSERT(mg_http_listen(&mgr, url, h7, NULL) != NULL); ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hello\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /foo/version.h HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, expected) == 0); ASSERT(fetch(&mgr, buf, url, "GET /foo HTTP/1.0\n\n") == 301); ASSERT(fetch(&mgr, buf, url, "GET /foo/ HTTP/1.0\n\n") == 200); // printf("-->[%s]\n", buf); mg_mgr_free(&mgr); ASSERT(mgr.conns == NULL); } static void test_get_header_var(void) { struct mg_str empty = mg_str(""), bar = mg_str("bar"), baz = mg_str("baz"); struct mg_str header = mg_str("Digest foo=\"bar\", blah,boo=baz, x=\"yy\""); struct mg_str bad_header = mg_str("Digest foo=\"bar"); struct mg_str yy = mg_str("yy"); // struct mg_str x = mg_http_get_header_var(header, mg_str("x")); // MG_INFO(("--> [%d] [%d]", (int) x.len, yy.len)); struct mg_str bad_value = mg_http_get_header_var(bad_header, mg_str("foo")); ASSERT(bad_value.len == 4); ASSERT(mg_strcmp(mg_str("\"bar"), bad_value) == 0); ASSERT(mg_strcmp(empty, mg_http_get_header_var(empty, empty)) == 0); ASSERT(mg_strcmp(empty, mg_http_get_header_var(header, empty)) == 0); ASSERT(mg_strcmp(empty, mg_http_get_header_var(header, mg_str("fooo"))) == 0); ASSERT(mg_strcmp(empty, mg_http_get_header_var(header, mg_str("fo"))) == 0); ASSERT(mg_strcmp(empty, mg_http_get_header_var(header, mg_str("blah"))) == 0); ASSERT(mg_strcmp(bar, mg_http_get_header_var(header, mg_str("foo"))) == 0); ASSERT(mg_strcmp(baz, mg_http_get_header_var(header, mg_str("boo"))) == 0); ASSERT(mg_strcmp(yy, mg_http_get_header_var(header, mg_str("x"))) == 0); } static void json_scan(struct mg_str json, int depth) { int i, n = 0, o = mg_json_get(json, "$", &n); for (i = 0; i < depth; i++) printf(" "); printf("%.*s\n", n, json.buf + o); if (json.buf[o] == '{' || json.buf[o] == '[') { // Iterate over elems struct mg_str key, val, sub = mg_str_n(json.buf + o, (size_t) n); size_t ofs = 0; while ((ofs = mg_json_next(sub, ofs, &key, &val)) > 0) { for (i = 0; i < depth; i++) printf(" "); printf("KEY: %.*s VAL: %.*s\n", (int) key.len, key.buf, (int) val.len, val.buf); if (*val.buf == '[' || *val.buf == '{') json_scan(val, depth + 1); } } } static void test_json(void) { const char *s1 = "{\"a\":{},\"b\":7,\"c\":[[],2]}"; const char *s2 = "{\"a\":{\"b1\":{}},\"c\":7,\"d\":{\"b2\":{}}}"; int n; struct mg_str json; ASSERT(mg_json_get(mg_str_n(" true ", 6), "", &n) == MG_JSON_INVALID); ASSERT(mg_json_get(mg_str_n(" true ", 6), "$", &n) == 1 && n == 4); ASSERT(mg_json_get(mg_str_n("null ", 5), "$", &n) == 0 && n == 4); json = mg_str(" \"hi\\nthere\""); ASSERT(mg_json_get(json, "$", &n) == 2 && n == 11); ASSERT(mg_json_get(mg_str_n(" { } ", 5), "$", &n) == 1); ASSERT(mg_json_get(mg_str_n(" [[]]", 5), "$", &n) == 1); ASSERT(mg_json_get(mg_str_n(" [ ] ", 5), "$", &n) == 1); ASSERT(mg_json_get(mg_str_n("[1,2]", 5), "$", &n) == 0 && n == 5); ASSERT(mg_json_get(mg_str_n("[1,2]", 5), "$[0]", &n) == 1 && n == 1); ASSERT(mg_json_get(mg_str_n("[1,2]", 5), "$[1]", &n) == 3 && n == 1); ASSERT(mg_json_get(mg_str_n("[1,2]", 5), "$[3]", &n) == MG_JSON_NOT_FOUND); json = mg_str("{\"a\":[]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 2); json = mg_str("{\"a\":[1,2]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 5); json = mg_str("{\"a\":[1,[1]]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 7); json = mg_str("{\"a\":[[]]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 4); json = mg_str("{\"a\":[[1,2]]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 7); json = mg_str("{\"a\":{}}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 2); json = mg_str("{\"a\":{\"a\":{}}}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 8); json = mg_str("{\"a\":{\"a\":[]}}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 8); json = mg_str("[[1,[2,3]],4]"); ASSERT(mg_json_get(json, "$", &n) == 0 && n == 13); ASSERT(mg_json_get(json, "$[0]", &n) == 1 && n == 9); ASSERT(mg_json_get(json, "$[1]", &n) == 11); ASSERT(mg_json_get(json, "$[1]", &n) == 11 && n == 1); ASSERT(mg_json_get(json, "$[2]", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$[0][0]", &n) == 2 && n == 1); ASSERT(mg_json_get(json, "$[0][1]", &n) == 4 && n == 5); ASSERT(mg_json_get(json, "$[0][2]", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$[0][1][0]", &n) == 5 && n == 1); ASSERT(mg_json_get(json, "$[0][1][1]", &n) == 7 && n == 1); json = mg_str("[[1,2],3]"); ASSERT(mg_json_get(json, "$", &n) == 0 && n == 9); ASSERT(mg_json_get(json, "$[0][0]", &n) == 2 && n == 1); ASSERT(mg_json_get(json, "$[0][1]", &n) == 4 && n == 1); ASSERT(mg_json_get(json, "$[0][2]", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$[1][0]", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$[1]", &n) == 7 && n == 1); ASSERT(mg_json_get(json, "$[1][0]", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$", &n) == 0 && n == 9); ASSERT(mg_json_get(json, "$[1][0]", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$[0][1]", &n) == 4 && n == 1); json = mg_str(s1); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 2); ASSERT(mg_json_get(json, "$.b", &n) == 12 && n == 1); ASSERT(mg_json_get(json, "$.c", &n) == 18 && n == 6); ASSERT(mg_json_get(json, "$.c[0]", &n) == 19 && n == 2); ASSERT(mg_json_get(json, "$.c[1]", &n) == 22 && n == 1); ASSERT(mg_json_get(json, "$.c[3]", &n) == MG_JSON_NOT_FOUND); json = mg_str(s2); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 9); ASSERT(mg_json_get(json, "$.a.b1", &n) == 11 && n == 2); ASSERT(mg_json_get(json, "$.a.b2", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$.a.b", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$.a1", &n) == MG_JSON_NOT_FOUND); ASSERT(mg_json_get(json, "$.c", &n) == 19 && n == 1); { char to[4], expect[4] = {0, 0, 0, 0}; memset(to, 0, sizeof(to)); json = mg_str("\"\\u0000\""); ASSERT(mg_json_unescape(json, "$", to, 4) && memcmp(to, expect, 4) == 0); to[0] = 0; expect[0] = (char) 0xff; json = mg_str("\"\\u00ff\""); ASSERT(mg_json_unescape(json, "$", to, 4) && memcmp(to, expect, 4) == 0); json = mg_str("\"\\u0100\""); ASSERT(mg_json_unescape(json, "$", to, 4) == 0); json = mg_str("\"\\u1000\""); ASSERT(mg_json_unescape(json, "$", to, 4) == 0); json = mg_str("{\"a\":\"\"}"); mg_snprintf(to, sizeof(to), "hi"); ASSERT(mg_json_unescape(json, "$.a", to, sizeof(to)) == 0); ASSERT(to[0] == '\0'); json = mg_str("{\"a\":\"b\"}"); ASSERT(mg_json_unescape(json, "$.a", to, sizeof(to)) == 1); ASSERT(strcmp(to, "b") == 0); } { double d = 0; bool b = false; int len; char *str = NULL; json = mg_str("{\"a\":\"b\", \"c\":\"\"}"); str = mg_json_get_str(json, "$.a"); ASSERT(str != NULL); // printf("---> [%s]\n", str); ASSERT(strcmp(str, "b") == 0); mg_free(str); // Non existent string str = mg_json_get_str(json, "$.d"); ASSERT(str == NULL); // Empty string str = mg_json_get_str(json, "$.c"); ASSERT(str != NULL); ASSERT(strcmp(str, "") == 0); mg_free(str); json = mg_str("{\"a\": \"hi\\nthere\",\"b\": [12345, true]}"); str = mg_json_get_str(json, "$.a"); ASSERT(str != NULL); ASSERT(strcmp(str, "hi\nthere") == 0); mg_free(str); ASSERT(mg_json_get_long(json, "$.foo", -42) == -42); ASSERT(mg_json_get_long(json, "$.b[0]", -42) == 12345); ASSERT(mg_json_get_num(json, "$.a", &d) == false); ASSERT(mg_json_get_num(json, "$.c", &d) == false); ASSERT(mg_json_get_num(json, "$.b[0]", &d) == true); ASSERT(d == 12345); ASSERT(mg_json_get_bool(json, "$.b", &b) == false); ASSERT(mg_json_get_bool(json, "$.b[0]", &b) == false); ASSERT(mg_json_get_bool(json, "$.b[1]", &b) == true); ASSERT(b == true); ASSERT(mg_json_get(json, "$.b[2]", &len) < 0); json = mg_str("[\"YWJj\", \"0100026869\", \"\", \"abc\", \"0g\"]"); ASSERT((str = mg_json_get_b64(json, "$[0]", &len)) != NULL); ASSERT(len == 3 && memcmp(str, "abc", (size_t) len) == 0); mg_free(str); ASSERT((str = mg_json_get_hex(json, "$[1]", &len)) != NULL); ASSERT(len == 5 && memcmp(str, "\x01\x00\x02hi", (size_t) len) == 0); mg_free(str); ASSERT((str = mg_json_get_hex(json, "$[2]", &len)) != NULL); ASSERT(len == 0 && str[0] == '\0'); mg_free(str); len = 42; str = mg_json_get_hex(json, "$[3]", &len); ASSERT(str == NULL && len == 42); mg_free(str); str = mg_json_get_hex(json, "$[4]", &len); ASSERT(str == NULL && len == 42); mg_free(str); json = mg_str("{\"a\":[1,2,3], \"ab\": 2}"); ASSERT(mg_json_get_long(json, "$.a[0]", -42) == 1); ASSERT(mg_json_get_long(json, "$.ab", -42) == 2); ASSERT(mg_json_get_long(json, "$.ac", -42) == -42); json = mg_str("{\"a\":[],\"b\":[1,2]}"); ASSERT(mg_json_get_long(json, "$.a[0]", -42) == -42); ASSERT(mg_json_get_long(json, "$.b[0]", -42) == 1); ASSERT(mg_json_get_long(json, "$.b[1]", -42) == 2); ASSERT(mg_json_get_long(json, "$.b[2]", -42) == -42); json = mg_str("[{\"a\":1,\"b\":2},{\"a\":3, \"b\":4}]"); ASSERT(mg_json_get_long(json, "$[0].a", -42) == 1); ASSERT(mg_json_get_long(json, "$[0].b", -42) == 2); ASSERT(mg_json_get_long(json, "$[1].a", -42) == 3); ASSERT(mg_json_get_long(json, "$[1].b", -42) == 4); ASSERT(mg_json_get_long(json, "$[2].a", -42) == -42); json = mg_str("{\"a\":[1],\"b\":[2,3]}"); ASSERT(mg_json_get_long(json, "$.a[0]", -42) == 1); ASSERT(mg_json_get_long(json, "$.a[1]", -42) == -42); json = mg_str("{\"a\":[1,[2,3], 4]}"); ASSERT(mg_json_get_long(json, "$.a[0]", -42) == 1); ASSERT(mg_json_get_long(json, "$.a[1][0]", -42) == 2); ASSERT(mg_json_get_long(json, "$.a[1][1]", -42) == 3); ASSERT(mg_json_get_long(json, "$.a[1][2]", -42) == -42); ASSERT(mg_json_get_long(json, "$.a[2]", -42) == 4); ASSERT(mg_json_get_long(json, "$.a[3]", -42) == -42); } json = mg_str("{\"a\":[],\"b\":[1,2]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 2); ASSERT(mg_json_get(json, "$.a[0]", &n) < 0 && n == 0); json = mg_str("{\"a\":{},\"b\":[1,2]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 2); ASSERT(mg_json_get(json, "$.a[0]", &n) < 0 && n == 0); json = mg_str("{\"a\":true,\"b\":[1,2]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 4); ASSERT(mg_json_get(json, "$.a[0]", &n) < 0 && n == 0); json = mg_str("{\"a\":1,\"b\":[1,2]}"); ASSERT(mg_json_get(json, "$.a", &n) == 5 && n == 1); ASSERT(mg_json_get(json, "$.a[0]", &n) < 0 && n == 0); ASSERT(mg_json_get_long(mg_str("[0, 42]"), "$[1]", 0) == 42); ASSERT(mg_json_get_long(mg_str("[[], 42]"), "$[1]", 0) == 42); ASSERT(mg_json_get_long(mg_str("[{}, 42]"), "$[1]", 0) == 42); json = mg_str("[{\"a\":1},{\"a\":2}]"); ASSERT(mg_json_get_long(json, "$[0]", -1) == -1); ASSERT(mg_json_get_long(json, "$[0].a", -1) == 1); ASSERT(mg_json_get_long(json, "$[1].a", -1) == 2); ASSERT(mg_json_get_long(json, "$[2].a", -1) == -1); json = mg_str("[{\"a1\":1},{\"a\":2}]"); ASSERT(mg_json_get_long(json, "$[0]", -1) == -1); ASSERT(mg_json_get_long(json, "$[0].a", -1) == -1); ASSERT(mg_json_get_long(json, "$[1].a", -1) == 2); ASSERT(mg_json_get_long(json, "$[2].a", -1) == -1); // mg_json_next() json = mg_str("[1,true,{\"a\":[3],\"b\":42}]"); json_scan(json, 0); { struct mg_str k, v, sub = mg_str_n(json.buf + 8, json.len - 8); const char *a = "\"a\"", *b = "\"b\""; ASSERT(mg_json_next(sub, 0, &k, &v) == 9); ASSERT(vcmp(k, a)); ASSERT(vcmp(v, "[3]")); ASSERT(mg_json_next(sub, 9, &k, &v) == 15); ASSERT(vcmp(k, b)); ASSERT(vcmp(v, "42")); ASSERT(mg_json_next(sub, 15, &k, &v) == 0); } { struct mg_str expected = mg_str("\"b:c\""), val; json = mg_str("{\"a\":\"b:c\"}"); val = mg_json_get_tok(json, "$.a"); ASSERT(mg_strcmp(val, expected) == 0); } // mg_json_get_num: parsing exponential { double d = 0.0, tolerance = 1e-12; json = mg_str( "{" "\"i\":1e3," "\"n\":-2.5e2," "\"p\":3E+4," "\"m\":4.25E-1," "\"z\":0e0," "\"s\":-0.0e+0," "\"a\":[6.123456789e3,-1e-9]," "\"bad\":\"1e3\"" "}"); ASSERT(mg_json_get_num(json, "$.i", &d) == true); ASSERT(fabs(d - 1000.0) < tolerance); ASSERT(mg_json_get_num(json, "$.n", &d) == true); ASSERT(fabs(d - -250.0) < tolerance); ASSERT(mg_json_get_num(json, "$.p", &d) == true); ASSERT(fabs(d - 30000.0) < tolerance); ASSERT(mg_json_get_num(json, "$.m", &d) == true); ASSERT(fabs(d - 0.425) < tolerance); ASSERT(mg_json_get_num(json, "$.z", &d) == true); ASSERT(fabs(d - 0.0) < tolerance); ASSERT(mg_json_get_num(json, "$.s", &d) == true); ASSERT(fabs(d - 0.0) < tolerance); ASSERT(mg_json_get_num(json, "$.a[0]", &d) == true); ASSERT(fabs(d - 6123.456789) < tolerance); ASSERT(mg_json_get_num(json, "$.a[1]", &d) == true); ASSERT(fabs(d - -1e-9) < tolerance); ASSERT(mg_json_get_num(json, "$.bad", &d) == false); } } static void resp_rpc(struct mg_rpc_req *r) { int len = 0, off = mg_json_get(r->frame, "$.result", &len); mg_xprintf(r->pfn, r->pfn_data, "%.*s", len, &r->frame.buf[off]); } static void test_rpc(void) { struct mg_rpc *head = NULL; struct mg_iobuf io = {0, 0, 0, 256}; struct mg_rpc_req req = {&head, 0, mg_pfn_iobuf, &io, 0, {0, 0}}; mg_rpc_add(&head, mg_str("rpc.list"), mg_rpc_list, NULL); { req.frame = mg_str("{\"method\":\"rpc.list\"}"); mg_rpc_process(&req); ASSERT(io.buf == NULL); } { const char *resp = "{\"id\":1,\"result\":[\"rpc.list\"]}"; req.frame = mg_str("{\"id\": 1,\"method\":\"rpc.list\"}"); mg_rpc_process(&req); ASSERT(strcmp((char *) io.buf, resp) == 0); mg_iobuf_free(&io); } { const char *resp = "{\"id\":true,\"error\":{\"code\":-32601,\"message\":\"foo not " "found\"}}"; req.frame = mg_str("{\"id\": true,\"method\":\"foo\"}"); mg_rpc_process(&req); // MG_INFO(("-> %s", io.buf)); ASSERT(strcmp((char *) io.buf, resp) == 0); mg_iobuf_free(&io); } { const char *resp = "{\"id\":true,\"error\":{\"code\":-32601,\"message\":\"foo not " "found\"}}"; req.frame = mg_str("{\"id\": true,\"method\":\"foo\"}"); req.head = NULL; mg_rpc_process(&req); // MG_INFO(("-> %s", io.buf)); ASSERT(strcmp((char *) io.buf, resp) == 0); mg_iobuf_free(&io); req.head = &head; } { const char *resp = "{\"error\":{\"code\":-32700,\"message\":\"haha\"}}"; req.frame = mg_str("haha"); mg_rpc_process(&req); // MG_INFO(("-> %s", io.buf)); ASSERT(strcmp((char *) io.buf, resp) == 0); mg_iobuf_free(&io); } { const char *resp = "{\"id\":1,\"error\":{\"code\":-32601,\"message\":\" not found\"}}"; req.frame = mg_str("{\"id\":1,\"result\":123}"); mg_rpc_process(&req); // MG_INFO(("-> %s", io.buf)); ASSERT(strcmp((char *) io.buf, resp) == 0); mg_iobuf_free(&io); } { req.frame = mg_str("{\"id\":1,\"result\":123}"); mg_rpc_add(&head, mg_str(""), resp_rpc, NULL); mg_rpc_process(&req); MG_INFO(("-> %s", io.buf)); ASSERT(strcmp((char *) io.buf, "123") == 0); mg_iobuf_free(&io); } mg_rpc_del(&head, NULL); ASSERT(head == NULL); } static void ph(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_POLL) ++(*(int *) c->fn_data); (void) c, (void) ev_data; } static void test_poll(void) { int count = 0, i; struct mg_mgr mgr; mg_mgr_init(&mgr); mg_http_listen(&mgr, "http://127.0.0.1:12340", ph, &count); for (i = 0; i < 10; i++) mg_mgr_poll(&mgr, 0); ASSERT(count == 10); mg_mgr_free(&mgr); } #define NMESSAGES 99999 static uint32_t s_qcrc = 0; static int s_out, s_in; static void producer(void *param) { struct mg_queue *q = (struct mg_queue *) param; char tmp[64 * 1024], *buf; size_t len, ofs = sizeof(tmp); for (s_out = 0; s_out < NMESSAGES; s_out++) { if (ofs >= sizeof(tmp)) mg_random(tmp, sizeof(tmp)), ofs = 0; len = ((uint8_t *) tmp)[ofs] % 55U + 1U; if (ofs + len > sizeof(tmp)) len = sizeof(tmp) - ofs; while ((mg_queue_book(q, &buf, len)) < len) (void) 0; memcpy(buf, &tmp[ofs], len); s_qcrc = mg_crc32(s_qcrc, buf, len); ofs += len; #if 0 fprintf(stderr, "-->prod %3d %8x %-3lu %zu/%zu/%lu\n", s_out, s_qcrc, len, q->tail, q->head, buf - q->buf); #endif mg_queue_add(q, len); } } static uint32_t consumer(struct mg_queue *q) { uint32_t crc = 0; for (s_in = 0; s_in < NMESSAGES; s_in++) { char *buf; size_t len; while ((len = mg_queue_next(q, &buf)) == 0) (void) 0; crc = mg_crc32(crc, buf, len); #if 0 fprintf(stderr, "-->cons %3u %8x %-3lu %zu/%zu/%lu\n", s_in, crc, len, q->tail, q->head, buf - q->buf); #endif mg_queue_del(q, len); } return crc; } #if MG_ARCH == MG_ARCH_WIN32 static void start_thread(void (*f)(void *), void *p) { _beginthread((void(__cdecl *)(void *)) f, 0, p); } #elif MG_ARCH == MG_ARCH_UNIX #include static void start_thread(void (*f)(void *), void *p) { union { void (*f1)(void *); void *(*f2)(void *); } u = {f}; pthread_t thread_id = (pthread_t) 0; pthread_attr_t attr; (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&thread_id, &attr, u.f2, p); pthread_attr_destroy(&attr); } #else static void start_thread(void (*f)(void *), void *p) { (void) f, (void) p; } #endif static void test_queue(void) { char buf[512]; struct mg_queue queue; uint32_t crc; memset(buf, 0x55, sizeof(buf)); mg_queue_init(&queue, buf, sizeof(buf)); start_thread(producer, &queue); // Start producer in a separate thread crc = consumer(&queue); // Consumer eats data in this thread MG_INFO(("CRC1 %8x", s_qcrc)); // Show CRCs MG_INFO(("CRC2 %8x", crc)); ASSERT(s_qcrc == crc); } static void test_md5_str(const char *string, const unsigned char *expected_hash) { mg_md5_ctx ctx; unsigned char digest[16]; mg_md5_init(&ctx); mg_md5_update(&ctx, (unsigned char *) string, strlen(string)); mg_md5_final(&ctx, digest); ASSERT((memcmp(digest, expected_hash, 16) == 0)); } static void test_md5(void) { const unsigned char expected_hash_1[] = {0xe5, 0x45, 0x14, 0x96, 0xe1, 0x1d, 0x7d, 0xa9, 0x62, 0x9f, 0xe0, 0x64, 0xcb, 0x3d, 0x2b, 0x54}; const unsigned char expected_hash_2[] = {0x99, 0x33, 0xf6, 0x4d, 0x7a, 0xb5, 0x0b, 0x0f, 0xf4, 0x35, 0xdc, 0x61, 0x1d, 0xef, 0x20, 0xff}; const unsigned char expected_hash_3[] = {0xf7, 0x94, 0xc3, 0xa4, 0x56, 0x6d, 0xc1, 0x10, 0x95, 0xfc, 0x56, 0x87, 0xf8, 0xb1, 0x69, 0xf2}; test_md5_str("#&*%$DHFH(0x12345)^&*(^!@$%^^&&*", expected_hash_1); test_md5_str("1298**&^%DHKSHFLS)(*)&^^%$#!!!!", expected_hash_2); test_md5_str(")_)+_)!&^*%$#>>>{}}}{{{][[[[]]]", expected_hash_3); } static void test_sha1_str(const char *string, const unsigned char *expected_hash) { mg_sha1_ctx ctx; unsigned char digest[20]; mg_sha1_init(&ctx); mg_sha1_update(&ctx, (unsigned char *) string, strlen(string)); mg_sha1_final(digest, &ctx); ASSERT((memcmp(digest, expected_hash, 20) == 0)); } static void test_sha1(void) { const unsigned char expected_hash_1[] = { 0x02, 0xaf, 0x27, 0x00, 0xf7, 0xba, 0xb5, 0xf5, 0xf3, 0x69, 0xd8, 0x80, 0x01, 0x0d, 0x6a, 0x28, 0x31, 0x63, 0x1f, 0x92}; const unsigned char expected_hash_2[] = { 0xaa, 0xe4, 0x39, 0xe8, 0xb4, 0x72, 0x47, 0xe5, 0x1a, 0x6d, 0x82, 0x25, 0x5e, 0x9f, 0x32, 0xd9, 0x93, 0x0a, 0x5f, 0xfb}; const unsigned char expected_hash_3[] = { 0xa0, 0xdd, 0xd2, 0xa1, 0x52, 0xdf, 0xa9, 0xb8, 0x7e, 0x73, 0x32, 0x6a, 0x31, 0x28, 0xe9, 0x6d, 0x3a, 0x90, 0x82, 0x58}; test_sha1_str("#&*%$DHFH(0x12345)^&*(^!@$%^^&&*", expected_hash_1); test_sha1_str("1298**&^%DHKSHFLS)(*)&^^%$#!!!!", expected_hash_2); test_sha1_str(")_)+_)!&^*%$#>>>{}}}{{{][[[[]]]", expected_hash_3); } static void test_sha256_str(const char *string, const unsigned char *expected_hash) { unsigned char digest[32]; mg_sha256(digest, (unsigned char *) string, strlen(string)); ASSERT((memcmp(digest, expected_hash, 32) == 0)); } static void test_sha256(void) { const unsigned char expected_hash_1[] = { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}; const unsigned char expected_hash_2[] = { 0xbc, 0x07, 0x32, 0x21, 0x17, 0x8e, 0x81, 0xbd, 0x2f, 0x67, 0x13, 0x3a, 0xca, 0xb4, 0x07, 0xad, 0x5b, 0x61, 0x8b, 0x33, 0xd2, 0x95, 0x9e, 0x94, 0x45, 0x45, 0xdc, 0x24, 0x99, 0x0a, 0xff, 0x92}; const unsigned char expected_hash_3[] = { 0x1b, 0x65, 0x3e, 0xda, 0x9a, 0x2a, 0x24, 0x55, 0xa3, 0x56, 0x38, 0x08, 0xf4, 0xf7, 0xc5, 0xa6, 0xc5, 0x2d, 0x2c, 0xb1, 0x71, 0xe5, 0x90, 0x4c, 0x83, 0x9c, 0x77, 0x92, 0x51, 0xa2, 0x84, 0x4a}; test_sha256_str("", expected_hash_1); test_sha256_str( "#&*%$DHFH(0x12345)^&*(^!@$%^^&&*1298**&^%DHKSHFLS)(*)&^^%$#!!!!", expected_hash_2); test_sha256_str(")_)+_)!&^*%$#>>>{}}}{{{][[[[]]]", expected_hash_3); } static void test_sha384_str(const char *string, const unsigned char *expected_hash) { unsigned char digest[48]; mg_sha384(digest, (unsigned char *) string, strlen(string)); ASSERT((memcmp(digest, expected_hash, 48) == 0)); } static void test_sha384(void) { const unsigned char expected_hash_1[] = { 0x38, 0xb0, 0x60, 0xa7, 0x51, 0xac, 0x96, 0x38, 0x4c, 0xd9, 0x32, 0x7e, 0xb1, 0xb1, 0xe3, 0x6a, 0x21, 0xfd, 0xb7, 0x11, 0x14, 0xbe, 0x07, 0x43, 0x4c, 0x0c, 0xc7, 0xbf, 0x63, 0xf6, 0xe1, 0xda, 0x27, 0x4e, 0xde, 0xbf, 0xe7, 0x6f, 0x65, 0xfb, 0xd5, 0x1a, 0xd2, 0xf1, 0x48, 0x98, 0xb9, 0x5b}; const unsigned char expected_hash_2[] = { 0x77, 0xe7, 0x0a, 0x31, 0xe5, 0xcd, 0x68, 0xa4, 0xc5, 0xb3, 0x70, 0x55, 0x38, 0xd0, 0x90, 0xb0, 0xcd, 0xb6, 0xf4, 0x1c, 0x2e, 0xe6, 0xf4, 0xdd, 0xf6, 0xb4, 0xfc, 0x97, 0x01, 0x79, 0x3c, 0x89, 0x82, 0x3b, 0x13, 0xa2, 0x48, 0xa7, 0xfe, 0xd2, 0xd0, 0xc4, 0xbf, 0xed, 0x85, 0xb6, 0x20, 0xc7}; const unsigned char expected_hash_3[] = { 0x45, 0xa1, 0xc6, 0x4d, 0x99, 0x29, 0x42, 0x87, 0x49, 0x46, 0x73, 0x3c, 0x3b, 0xc8, 0xbc, 0x9c, 0x43, 0x10, 0x75, 0x23, 0x89, 0x22, 0x04, 0x41, 0xcd, 0xa3, 0x34, 0xeb, 0x97, 0x9f, 0x2a, 0xbf, 0x17, 0x94, 0x38, 0x72, 0x6b, 0xd8, 0x8e, 0xcc, 0xb5, 0x50, 0xc6, 0x5b, 0x35, 0x1f, 0x91, 0x90}; test_sha384_str("", expected_hash_1); test_sha384_str( "#&*%$DHFH(0x12345)^&*(^!@$%^^&&*1298**&^%DHKSHFLS)(*)&^^%$#!!!!", expected_hash_2); test_sha384_str(")_)+_)!&^*%$#>>>{}}}{{{][[[[]]]", expected_hash_3); } static void test_split(void) { struct mg_str a, b, s; s = mg_str(""); ASSERT(mg_span(s, &a, &s, '.') == false); ASSERT(mg_span(s, &a, NULL, '.') == false); ASSERT(mg_span(s, NULL, &b, '.') == false); ASSERT(mg_span(s, NULL, NULL, '.') == false); ASSERT(mg_span(s, &a, &b, '.') == false); s = mg_str("aa.bb.cc"), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, &a, &b, '.') == true); ASSERT(mg_strcmp(a, mg_str("aa")) == 0); ASSERT(mg_strcmp(b, mg_str("bb.cc")) == 0); ASSERT(mg_strcmp(s, mg_str("aa.bb.cc")) == 0); s = mg_str("aa.bb.cc"), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, &a, NULL, '.') == true); ASSERT(mg_strcmp(a, mg_str("aa")) == 0); ASSERT(mg_strcmp(s, mg_str("aa.bb.cc")) == 0); s = mg_str("aa.bb.cc"), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, NULL, &b, '.') == true); ASSERT(mg_strcmp(b, mg_str("bb.cc")) == 0); ASSERT(mg_strcmp(s, mg_str("aa.bb.cc")) == 0); s = mg_str("aa.bb.cc"), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, NULL, NULL, '.') == true); ASSERT(mg_strcmp(s, mg_str("aa.bb.cc")) == 0); s = mg_str("aa.bb.cc"), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, &a, &s, '.') == true); ASSERT(mg_strcmp(a, mg_str("aa")) == 0); ASSERT(mg_strcmp(s, mg_str("bb.cc")) == 0); s = mg_str(".aa"), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, &a, &b, '.') == true); ASSERT(mg_strcmp(a, mg_str("")) == 0); ASSERT(mg_strcmp(b, mg_str("aa")) == 0); s = mg_str("aa."), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, &a, &b, '.') == true); ASSERT(mg_strcmp(a, mg_str("aa")) == 0); ASSERT(mg_strcmp(b, mg_str("")) == 0); s = mg_str("aa"), a = mg_str_n(NULL, 0), b = mg_str_n(NULL, 0); ASSERT(mg_span(s, &a, &b, '.') == true); ASSERT(mg_strcmp(a, mg_str("aa")) == 0); ASSERT(mg_strcmp(b, mg_str("")) == 0); } static void test_x25519(void) { #if MG_TLS == MG_TLS_BUILTIN uint8_t key[X25519_BYTES]; uint8_t buf[X25519_BYTES]; char tmp[100]; size_t i; for (i = 0; i < sizeof(key); i++) key[i] = (uint8_t) i; for (i = 0; i < sizeof(buf); i++) buf[i] = 0; mg_tls_x25519(buf, key, X25519_BASE_POINT, 1); mg_snprintf(tmp, sizeof(tmp), "%M", mg_print_hex, sizeof(buf), buf); MG_INFO(("%s", tmp)); ASSERT(mg_strcmp(mg_str("8f40c5adb6"), mg_str_n(tmp, 10)) == 0); #endif } static void test_rsa(void) { #if MG_TLS == MG_TLS_BUILTIN static const uint8_t tv_n[256] = { 0xe5, 0xd5, 0x5c, 0xed, 0xa7, 0xeb, 0xdd, 0x7f, 0x2a, 0x23, 0xd3, 0x2b, 0xd1, 0x01, 0x4e, 0xd3, 0x06, 0x51, 0x8a, 0x7f, 0x49, 0xaa, 0x1d, 0xa0, 0x6b, 0xa4, 0x75, 0x2d, 0x88, 0x99, 0x12, 0x20, 0x56, 0x43, 0x4a, 0x32, 0x52, 0xa4, 0x92, 0x4f, 0x9e, 0xae, 0x73, 0x7e, 0x22, 0x78, 0x8e, 0xec, 0x64, 0x0a, 0xff, 0xeb, 0x02, 0x9e, 0xfe, 0x0c, 0xbf, 0x37, 0x4e, 0xf9, 0xb3, 0x71, 0x23, 0x29, 0xae, 0x22, 0xc9, 0x9e, 0xa3, 0xc9, 0x63, 0xa8, 0x89, 0x39, 0x89, 0xf0, 0x37, 0x27, 0x1a, 0xbf, 0x9b, 0x70, 0x35, 0xf2, 0x7c, 0x0f, 0x34, 0xf2, 0x80, 0x6b, 0x9b, 0x80, 0x98, 0x16, 0x64, 0xba, 0x7e, 0x51, 0x22, 0xe1, 0xca, 0x39, 0x8c, 0x6c, 0x0b, 0xc6, 0x6b, 0xc8, 0x74, 0x50, 0x84, 0x9b, 0xe3, 0xf1, 0xdb, 0xf5, 0xff, 0x7e, 0x49, 0xe8, 0xdc, 0x41, 0xb9, 0x25, 0x3e, 0x2d, 0xbc, 0x48, 0x8f, 0xc8, 0x6f, 0x1b, 0x6b, 0x8a, 0xeb, 0xdb, 0x68, 0xaa, 0x15, 0xd9, 0x5e, 0xd8, 0x11, 0x07, 0x03, 0xbd, 0xd2, 0xa9, 0x6f, 0xce, 0x58, 0xb1, 0xb1, 0x86, 0xff, 0x86, 0x6e, 0x4a, 0x81, 0x64, 0xa0, 0x6c, 0x83, 0xca, 0xfc, 0x3f, 0xfe, 0x7d, 0x95, 0xd6, 0x40, 0x29, 0x21, 0x5a, 0x3b, 0x5d, 0xc8, 0x93, 0xa0, 0x1d, 0x2c, 0x6e, 0xb6, 0xc0, 0x65, 0x15, 0x69, 0x8b, 0x67, 0x71, 0x03, 0xde, 0xe7, 0xcc, 0x65, 0x83, 0x0e, 0x5a, 0x9d, 0xc9, 0x0e, 0xc1, 0xc7, 0xc4, 0xf3, 0x47, 0x1e, 0x9d, 0xce, 0x9d, 0xaf, 0x8b, 0x5f, 0xaa, 0x35, 0xfe, 0x15, 0x59, 0xd4, 0xc1, 0xd5, 0xaa, 0x3b, 0x3a, 0x0d, 0x9a, 0x98, 0x88, 0x1b, 0x5e, 0xf8, 0x5b, 0x07, 0xbf, 0xdb, 0x5e, 0x88, 0x46, 0x4a, 0xde, 0x9a, 0x63, 0x30, 0x7b, 0x4f, 0x3f, 0xc6, 0x9b, 0x88, 0x98, 0x34, 0xbe, 0xfd, 0xbb, 0xf5, 0xd1}; static const uint8_t tv_e[3] = {0x01, 0x00, 0x01}; static const uint8_t tv_sig[256] = { 0x8f, 0xc5, 0x67, 0x8f, 0x44, 0xae, 0x2c, 0x03, 0x2b, 0xb4, 0xfb, 0xd5, 0x1a, 0xba, 0xc7, 0xd2, 0x7a, 0x9a, 0xaa, 0x2d, 0x31, 0x49, 0x4c, 0x73, 0x46, 0x38, 0x1e, 0xd6, 0xb2, 0xc0, 0x8f, 0x2f, 0x8c, 0xf6, 0xba, 0x7c, 0x81, 0xe4, 0xbe, 0x37, 0x09, 0xb3, 0x9a, 0xae, 0x7e, 0x06, 0xcd, 0x3a, 0x48, 0xc3, 0x6a, 0x7b, 0x06, 0x28, 0x70, 0x06, 0x4c, 0xd9, 0x38, 0xa8, 0x25, 0x7e, 0x6f, 0xdc, 0x65, 0xc0, 0x17, 0x2c, 0x7b, 0x97, 0x1e, 0x61, 0x00, 0xb9, 0xdf, 0xd0, 0x2b, 0x54, 0x3a, 0xff, 0x18, 0x32, 0x8e, 0x69, 0x5e, 0x64, 0x47, 0x68, 0xd8, 0x3a, 0x78, 0xf0, 0x91, 0x7d, 0x6b, 0xe0, 0xc1, 0x6b, 0x5f, 0xd8, 0x7b, 0x22, 0xd6, 0x5d, 0x3b, 0x73, 0xf2, 0x1c, 0x9c, 0x73, 0xb7, 0x29, 0x22, 0xaf, 0x27, 0x0c, 0xce, 0x29, 0xa1, 0x46, 0x09, 0x5f, 0x9a, 0x9f, 0xa5, 0x6f, 0x88, 0x65, 0x23, 0x68, 0xaf, 0x1d, 0x56, 0x32, 0x69, 0x6f, 0x9c, 0x2d, 0x93, 0x0d, 0x99, 0x8a, 0x39, 0x53, 0x82, 0x0a, 0xae, 0xe2, 0xe3, 0xf1, 0x03, 0x7a, 0xb3, 0x5b, 0x05, 0x6d, 0xdd, 0xbe, 0xb5, 0x0d, 0x53, 0x81, 0x93, 0x9b, 0xdb, 0xd6, 0x39, 0x61, 0x97, 0x0c, 0x23, 0xd3, 0x98, 0x51, 0xb1, 0xd9, 0x42, 0x1d, 0x5d, 0x29, 0x2b, 0x64, 0xda, 0xa9, 0x37, 0x70, 0x30, 0x77, 0xa0, 0x99, 0x8d, 0x13, 0x67, 0x5d, 0x68, 0x80, 0x9f, 0x68, 0x25, 0x30, 0x50, 0x31, 0xe3, 0xed, 0xd2, 0xa2, 0xa0, 0xfc, 0xf7, 0xb4, 0x85, 0xbf, 0x68, 0xdc, 0x14, 0xbc, 0xeb, 0xd7, 0x9f, 0x7a, 0x6a, 0xb1, 0x9c, 0x8b, 0xf5, 0xff, 0xc1, 0x5a, 0xf7, 0xaf, 0x52, 0x88, 0x0e, 0xf2, 0x5c, 0x10, 0x02, 0x35, 0xe2, 0xcc, 0xd9, 0x2b, 0x20, 0x80, 0xc2, 0xb9, 0xfa, 0x5f, 0xbd, 0xc6, 0xd2, 0xd7, 0xc1, 0xe3, 0xcd, 0x59}; static const uint8_t tv_em[256] = { 0x17, 0x08, 0xf4, 0xe4, 0x67, 0x2b, 0xa9, 0x3b, 0x34, 0x13, 0x96, 0xeb, 0xd1, 0x14, 0xf1, 0x90, 0x4c, 0x6f, 0xc9, 0xec, 0xa0, 0x85, 0xa8, 0xa6, 0x89, 0xe4, 0xd4, 0x48, 0x41, 0x5b, 0x6b, 0x1c, 0x79, 0xce, 0x87, 0xdb, 0x12, 0x45, 0x97, 0x3b, 0x37, 0xd9, 0xd6, 0xe9, 0x7e, 0x6e, 0xcf, 0xb2, 0x84, 0x47, 0xe4, 0xda, 0x6c, 0x53, 0xfd, 0xe3, 0x18, 0x0c, 0xa7, 0xd4, 0x70, 0xc6, 0xd5, 0xd9, 0xc2, 0xbb, 0x08, 0xfd, 0xf4, 0x56, 0x66, 0x21, 0x68, 0xb5, 0xb5, 0x4c, 0x9b, 0x1d, 0xdb, 0xac, 0xa0, 0xc4, 0x64, 0x1e, 0xee, 0x1b, 0xe0, 0xc8, 0x84, 0xc5, 0xa9, 0xd0, 0x50, 0xd8, 0xb6, 0xd2, 0xb5, 0x1f, 0xbc, 0xf7, 0x01, 0xd2, 0x53, 0x44, 0xc9, 0x1c, 0xae, 0x45, 0x28, 0xbd, 0xbe, 0x28, 0x6e, 0xb9, 0x06, 0xd6, 0xc9, 0xcd, 0x5a, 0xdd, 0x31, 0x99, 0x56, 0x22, 0xf0, 0xd7, 0xa4, 0xb3, 0x38, 0x04, 0xcc, 0x7f, 0x45, 0xba, 0x05, 0x26, 0xf9, 0x34, 0x50, 0xb4, 0xcf, 0xf3, 0x81, 0xb7, 0xf9, 0xf1, 0x2a, 0xbc, 0x2e, 0xe1, 0x51, 0x12, 0x23, 0x5a, 0xec, 0xe8, 0x59, 0x1b, 0xb2, 0x58, 0x6e, 0x17, 0x3c, 0x9e, 0x3a, 0x24, 0xf2, 0x7d, 0xd8, 0xfa, 0x82, 0xf5, 0x30, 0x13, 0x53, 0xf5, 0x6e, 0x08, 0xdd, 0x0d, 0x92, 0x24, 0x84, 0x02, 0x7b, 0x64, 0x55, 0x1c, 0xda, 0xf4, 0xb7, 0xc1, 0x35, 0x87, 0xd2, 0x79, 0xf4, 0x34, 0xc3, 0xb7, 0x58, 0xdb, 0x8b, 0x82, 0x71, 0x49, 0xc1, 0x85, 0x7f, 0x56, 0x8a, 0xf7, 0xaf, 0xbb, 0xd6, 0x38, 0x38, 0x34, 0x4b, 0x93, 0xe9, 0x77, 0x37, 0xd0, 0x9c, 0xd2, 0xe0, 0x76, 0x6f, 0xa2, 0x20, 0x2e, 0x0a, 0x2e, 0x48, 0x5e, 0xa2, 0x83, 0xd2, 0xfa, 0xc2, 0xc8, 0xd9, 0xa7, 0xcd, 0x7b, 0xb3, 0x78, 0x30, 0x46, 0x7e, 0x83, 0xbc, 0x11, 0x40, 0xbc}; static const uint8_t tv_mhash[32] = { 0x21, 0x83, 0x9d, 0xb5, 0x7b, 0xe2, 0x84, 0xc2, 0x31, 0xf7, 0xb6, 0xa1, 0x90, 0x9b, 0x53, 0x99, 0x5f, 0x09, 0x1b, 0x84, 0xf7, 0x35, 0x57, 0xbb, 0xbb, 0xef, 0x7f, 0x7b, 0x93, 0xe8, 0xef, 0x16}; ASSERT(mg_rsa_verify(tv_em, 256, tv_mhash)); { uint8_t em[256]; int r = mg_rsa_mod_pow(tv_n, sizeof(tv_n), tv_e, sizeof(tv_e), tv_sig, sizeof(tv_sig), em, sizeof(em)); ASSERT(r == 0); ASSERT(memcmp(em, tv_em, 256) == 0); ASSERT(mg_rsa_verify(em, 256, tv_mhash)); } { uint8_t mhash[32]; uint8_t em[256]; memcpy(mhash, tv_mhash, 32); memcpy(em, tv_em, 256); mhash[0] ^= 0x01; // wrong mhash ASSERT(!mg_rsa_verify(tv_em, 256, mhash)); mhash[0] = tv_mhash[0]; em[255] ^= 0x01; // bad trailer ASSERT(!mg_rsa_verify(em, 256, tv_mhash)); em[255] = tv_em[255]; em[0] |= 0x80; // top bit set ASSERT(!mg_rsa_verify(em, 256, tv_mhash)); em[0] = tv_em[0]; em[223] ^= 0xFF; // corrupt h field ASSERT(!mg_rsa_verify(em, 256, tv_mhash)); em[223] = tv_em[223]; em[50] ^= 0x01; // corrupt zero padding ASSERT(!mg_rsa_verify(em, 256, tv_mhash)); em[50] = tv_em[50]; } { static const uint8_t sha256_di[] = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; static const uint8_t sha384_di[] = { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}; uint8_t hash[48], em[256]; size_t i, pslen; for (i = 0; i < sizeof(hash); i++) hash[i] = (uint8_t) i; memset(em, 0xff, sizeof(em)); em[0] = 0, em[1] = 1; pslen = sizeof(em) - 3 - sizeof(sha256_di) - 32; em[2 + pslen] = 0; memcpy(em + 3 + pslen, sha256_di, sizeof(sha256_di)); memcpy(em + 3 + pslen + sizeof(sha256_di), hash, 32); ASSERT(mg_rsa_pkcs_verify(em, sizeof(em), hash, 32)); em[1] = 2; // bad block type ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32)); em[1] = 1; em[10] = 0; // bad padding ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32)); em[10] = 0xff; em[3 + pslen + 15] ^= 1; // bad DigestInfo ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32)); em[3 + pslen + 15] ^= 1; em[sizeof(em) - 1] ^= 1; // bad hash ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32)); memset(em, 0, sizeof(em)); // former suffix-only bypass memcpy(em + sizeof(em) - 32, hash, 32); ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 32)); memset(em, 0xff, sizeof(em)); em[0] = 0, em[1] = 1; pslen = sizeof(em) - 3 - sizeof(sha384_di) - 48; em[2 + pslen] = 0; memcpy(em + 3 + pslen, sha384_di, sizeof(sha384_di)); memcpy(em + 3 + pslen + sizeof(sha384_di), hash, 48); ASSERT(mg_rsa_pkcs_verify(em, sizeof(em), hash, 48)); ASSERT(!mg_rsa_pkcs_verify(em, sizeof(em), hash, 20)); } #endif } static void test_jwt(void) { char jwt[512], claims[128], got[128], hdr[128], extra[64], tampered[512]; struct mg_jwt_opts opts, bad; const char *dot; char *str; size_t n, m; mg_snprintf(claims, sizeof(claims), "{%m:%m,%m:%d}", MG_ESC("sub"), MG_ESC("admin"), MG_ESC("iat"), 123); mg_snprintf(extra, sizeof(extra), "%m:%m", MG_ESC("cty"), MG_ESC("JWT")); memset(&opts, 0, sizeof(opts)); opts.claims = mg_str(claims); opts.header = mg_str(extra); opts.kid = mg_str("hmac-key-1"); opts.secret = mg_str("secret"); n = mg_jwt_sign_hs256(&opts, jwt, sizeof(jwt)); ASSERT(n > 0 && n + 1 < sizeof(jwt)); jwt[n] = '\0'; dot = strchr(jwt, '.'); ASSERT(dot != NULL); ASSERT(mg_base64url_decode(jwt, (size_t) (dot - jwt), hdr, sizeof(hdr)) > 0); str = mg_json_get_str(mg_str(hdr), "$.kid"); ASSERT(str != NULL && strcmp(str, "hmac-key-1") == 0); mg_free(str); str = mg_json_get_str(mg_str(hdr), "$.cty"); ASSERT(str != NULL && strcmp(str, "JWT") == 0); mg_free(str); m = mg_jwt_verify_hs256(mg_str_n(jwt, n), &opts, got, sizeof(got)); ASSERT(m == strlen(claims)); ASSERT(strcmp(got, claims) == 0); bad = opts; bad.secret = mg_str("bad-secret"); ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &bad, got, sizeof(got)) == 0); memcpy(tampered, jwt, n + 1); tampered[n - 1] = tampered[n - 1] == 'A' ? 'B' : 'A'; ASSERT(mg_jwt_verify_hs256(mg_str_n(tampered, n), &opts, got, sizeof(got)) == 0); ASSERT(mg_jwt_sign_hs256(&opts, got, 10) == 0); ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &opts, got, 4) == 0); #if MG_TLS == MG_TLS_BUILTIN { const char *key_pem = "-----BEGIN EC PRIVATE KEY-----\n" "MHcCAQEEIAVdo8UAScxG7jiuNY2UZESNX/KPH8qJ0u0gOMMsAzYWoAoGCCqGSM49\n" "AwEHoUQDQgAEqN6BIhvgbk7ecmUcn8Da9Avkj/uDNERtqWJG9r/or26X4u9jR5Jl\n" "4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n" "-----END EC PRIVATE KEY-----\n"; uint8_t key[32], pub[64]; memset(&opts, 0, sizeof(opts)); opts.claims = mg_str(claims); opts.kid = mg_str("ec-key-1"); opts.private_key = key; opts.public_key = pub; ASSERT(mg_uecc_parse_private_key(mg_str(key_pem), key, sizeof(key)) == 32); ASSERT(mg_uecc_compute_public_key(key, pub, mg_uecc_secp256r1()) == 1); n = mg_jwt_sign_es256(&opts, jwt, sizeof(jwt)); ASSERT(n > 0 && n + 1 < sizeof(jwt)); jwt[n] = '\0'; m = mg_jwt_verify_es256(mg_str_n(jwt, n), &opts, got, sizeof(got)); ASSERT(m == strlen(claims)); ASSERT(strcmp(got, claims) == 0); ASSERT(mg_jwt_verify_hs256(mg_str_n(jwt, n), &bad, got, sizeof(got)) == 0); memcpy(tampered, jwt, n + 1); tampered[n - 1] = tampered[n - 1] == 'A' ? 'B' : 'A'; ASSERT(mg_jwt_verify_es256(mg_str_n(tampered, n), &opts, got, sizeof(got)) == 0); } #endif } static void test_crypto(void) { test_md5(); test_sha1(); test_sha256(); test_sha384(); test_x25519(); test_rsa(); test_jwt(); } static void dash_custom(struct mg_connection *c, int ev, void *ev_data) { (void) c, (void) ev, (void) ev_data; } static int dash_authenticate(char *user, size_t userlen, const char *pass) { int level = 0; if (strcmp(pass, "admin") == 0) { level = 7; } else if (strcmp(pass, "user") == 0) { level = 3; } (void) user, (void) userlen; return level; } struct dash_ws_test { int opened; int done; int saw_set3; }; struct dash_arr_ws_test { int opened; int done; int saw_arr_index; // index seen in "arr/INDEX" change key, -1 if not seen int saw_arr_val; // "val" seen in the change params, -1 if not seen int saw_arr_size; // size seen in a plain "arr":N change, -1 if not seen }; static void dash_arr_ws_cb(struct mg_connection *c, int ev, void *ev_data) { struct dash_arr_ws_test *d = (struct dash_arr_ws_test *) c->fn_data; if (ev == MG_EV_WS_OPEN) { d->opened = 1; } else if (ev == MG_EV_WS_MSG) { struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; struct mg_str method = mg_json_get_tok(wm->data, "$.method"); if (mg_strcmp(method, mg_str("\"change\"")) == 0) { // Check for "arr/INDEX" (element change) or "arr":N (size change) const char *p = wm->data.buf; const char *end = p + wm->data.len; while (p < end - 6) { if (strncmp(p, "\"arr/", 5) == 0) { const char *v; p += 5; d->saw_arr_index = (int) strtol(p, NULL, 10); v = strstr(p, "\"val\":"); if (v != NULL) d->saw_arr_val = (int) strtol(v + 6, NULL, 10); d->done = 1; c->is_closing = 1; break; } if (strncmp(p, "\"arr\":", 6) == 0) { d->saw_arr_size = (int) strtol(p + 6, NULL, 10); d->done = 1; c->is_closing = 1; break; } p++; } } } else if (ev == MG_EV_CLOSE && d->done == 0) { d->done = -1; } } static void dash_ws_cb(struct mg_connection *c, int ev, void *ev_data) { struct dash_ws_test *d = (struct dash_ws_test *) c->fn_data; if (ev == MG_EV_WS_OPEN) { d->opened = 1; } else if (ev == MG_EV_WS_MSG) { struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; struct mg_str method = mg_json_get_tok(wm->data, "$.method"); struct mg_str set3 = mg_json_get_tok(wm->data, "$.params.set3"); if (mg_strcmp(method, mg_str("\"change\"")) == 0 && set3.buf != NULL) { d->saw_set3 = 1; d->done = 1; c->is_closing = 1; } } else if (ev == MG_EV_CLOSE && d->done == 0) { d->done = -1; } } char three[10] = "hi"; int one = 1; #define TEST_ARR_SIZE 17 #define TEST_ARR_CAP (TEST_ARR_SIZE + 2) // Cap for MG_DASH_ADD tests #define TEST_ARR_RO_INDEX 3 // This element is read-only: WRITE is always denied static int s_arr_index; static int s_arr_val; static int s_arr_store[TEST_ARR_CAP]; // Backing store, init to squares static int s_arr_count = TEST_ARR_SIZE; // Logical size, grows via MG_DASH_ADD static bool set1_fn(enum mg_dash_op op, struct mg_dash_user *u) { if (u->level < 3) return false; if (op == MG_DASH_READ) { mg_snprintf(three, sizeof(three), "t: %d", one); return true; } return u->level >= 7; } static bool set2_fn(enum mg_dash_op op, struct mg_dash_user *u) { (void) u; return op == MG_DASH_READ; } static bool set3_fn(enum mg_dash_op op, struct mg_dash_user *u) { if (u->level < 7) return false; return op == MG_DASH_READ || op == MG_DASH_WRITE; } static bool test_upload_dir(const struct mg_dash_user *u, char *buf, size_t len) { (void) u; #if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32 mkdir("mongoose_dash_test", 0755); #endif mg_snprintf(buf, len, "%s", "mongoose_dash_test"); return true; } struct test_file_entry { char name[64]; uint64_t size; }; static struct test_file_entry s_test_file; static int s_test_file_index; static struct mg_field test_files_fields[] = { {"name", MG_VAL_STR, s_test_file.name, sizeof(s_test_file.name)}, {"size", MG_VAL_UINT64, &s_test_file.size, 0}, {NULL, MG_VAL_INT, NULL, 0}, }; static struct mg_field_set test_files_set = { "files", test_files_fields, NULL, &s_test_file_index, NULL}; static bool arr_fn(enum mg_dash_op op, struct mg_dash_user *u) { (void) u; if (op == MG_DASH_READ) { if (s_arr_index < 0) { s_arr_index = s_arr_count; return true; } if (s_arr_index >= s_arr_count) { s_arr_index = -1; return true; } s_arr_val = s_arr_store[s_arr_index]; return true; } if (op == MG_DASH_WRITE) { if (s_arr_index < 0 || s_arr_index >= s_arr_count) return false; if (s_arr_index == TEST_ARR_RO_INDEX) return false; // Deny: read-only s_arr_store[s_arr_index] = s_arr_val; return true; } if (op == MG_DASH_ADD) { if (s_arr_count >= TEST_ARR_CAP) return false; // Deny: cap reached s_arr_store[s_arr_count++] = s_arr_val; return true; } if (op == MG_DASH_DELETE) return s_arr_index < s_arr_count; return false; } static void test_dash(void) { char buf[FETCH_BUF_SIZE]; const char *url = "http://localhost:26352"; const char *ws_url = "ws://localhost:26352/api/websocket"; struct mg_mgr mgr; struct mg_dash dash; struct dash_ws_test ws_admin, ws_user_notify; bool two = false; int admin = 7; int i; struct mg_field fields1[] = { {"one", MG_VAL_INT, &one, sizeof(one)}, {"three", MG_VAL_STR, three, sizeof(three)}, {NULL, MG_VAL_INT, NULL, 0}, }; struct mg_field fields2[] = { {"two", MG_VAL_BOOL, &two, 0}, // Size set to 0 - readonly {NULL, MG_VAL_INT, NULL, 0}, }; struct mg_field fields3[] = { {"admin", MG_VAL_INT, &admin, sizeof(admin)}, {NULL, MG_VAL_INT, NULL, 0}, }; struct mg_field_set set1 = {"set1", fields1, set1_fn, NULL, NULL}; struct mg_field_set set2 = {"set2", fields2, set2_fn, NULL, NULL}; struct mg_field_set set3 = {"set3", fields3, set3_fn, NULL, NULL}; struct mg_field arr_fields[] = { {"val", MG_VAL_INT, &s_arr_val, sizeof(s_arr_val)}, {NULL, MG_VAL_INT, NULL, 0}, }; struct mg_field_set arr_set = {"arr", arr_fields, arr_fn, &s_arr_index, NULL}; const char *get_all_expected = "{\"arr\":17,\"set3\":{\"admin\":7}," "\"set2\":{\"two\":false}," "\"set1\":{\"one\":1,\"three\":\"t: 1\"}}\n"; const char *set1_req = "POST /api/set HTTP/1.0\nContent-Length: 18\n\n" "{\"set1\":{\"one\":2}}"; const char *set1_set3_req = "POST /api/set HTTP/1.0\n" "Authorization: Basic dXNlcjp1c2Vy\n" "Content-Length: 18\n\n" "{\"set1\":{\"one\":3}}"; const char *set1_admin_set3_req = "POST /api/set HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 18\n\n" "{\"set1\":{\"one\":3}}"; const char *set3_user_req = "POST /api/set HTTP/1.0\n" "Authorization: Basic dXNlcjp1c2Vy\n" "Content-Length: 20\n\n" "{\"set3\":{\"admin\":8}}"; const char *set3_admin_req = "POST /api/set HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 20\n\n" "{\"set3\":{\"admin\":8}}"; const char *set3_admin_req9 = "POST /api/set HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 20\n\n" "{\"set3\":{\"admin\":9}}"; const char *set1_admin_set4_req = "POST /api/set HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 18\n\n" "{\"set1\":{\"one\":4}}"; const char *set1_expected = "{\"one\":2,\"three\":\"t: 2\"}\n"; const char *set1_expected3 = "{\"one\":3,\"three\":\"t: 3\"}\n"; const char *set1_expected4 = "{\"one\":4,\"three\":\"t: 4\"}\n"; const char *set3_expected = "{\"admin\":7}\n"; const char *set3_expected8 = "{\"admin\":8}\n"; const char *login_user_expected = "{\"user\":\"user\",\"level\":3}\n"; const char *login_admin_expected = "{\"user\":\"admin\",\"level\":7}\n"; const char *set2_req = "POST /api/set HTTP/1.0\nContent-Length: 21\n\n" "{\"set2\":{\"two\":true}}"; const char *set2_expected = "{\"two\":false}\n"; const char *arr7_expected = "[{\"val\":49}]\n"; const char *arr0_2_expected = "[{\"val\":0},{\"val\":1},{\"val\":4}]\n"; const char *arr5_mod_req = "POST /api/get/arr/5 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 10\n\n" "{\"val\":99}"; const char *arr100_mod_req = "POST /api/get/arr/100 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 10\n\n" "{\"val\":99}"; const char *arr3_mod_req = "POST /api/get/arr/3 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 11\n\n" "{\"val\":777}"; const char *arr_add555_req = "POST /api/add/arr HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 11\n\n" "{\"val\":555}"; const char *arr_add556_req = "POST /api/add/arr HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 11\n\n" "{\"val\":556}"; const char *arr_add557_req = "POST /api/add/arr HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n" "Content-Length: 11\n\n" "{\"val\":557}"; const char *arr5_mod_expected = "[{\"val\":99}]\n"; const char *arr3_expected = "[{\"val\":9}]\n"; const char *arr17_expected = "[{\"val\":555}]\n"; const char *files_probe = "\"files\""; const char *test_bin_probe = "\"test.bin\""; char marker = 0; memset(&dash, 0, sizeof(dash)); dash.session_auto_expiration_seconds = 1; remove("mongoose_dash_test/test.bin"); MG_DASH_ADD_FIELD_SET(&dash, &test_files_set); MG_DASH_ADD_FIELD_SET(&dash, &set1); ASSERT(dash.sets == &set1); ASSERT(set1.next == &test_files_set); MG_DASH_ADD_FIELD_SET(&dash, &set2); ASSERT(dash.sets == &set2); ASSERT(set2.next == &set1); MG_DASH_ADD_FIELD_SET(&dash, &set3); ASSERT(dash.sets == &set3); ASSERT(set3.next == &set2); MG_DASH_REGISTER_CUSTOM_HANDLER(&dash, "/api/custom", dash_custom, &marker); ASSERT(dash.custom_handlers != NULL); ASSERT(mg_strcmp(dash.custom_handlers->uri_pattern, mg_str("/api/custom")) == 0); ASSERT(dash.custom_handlers->handler == dash_custom); ASSERT(dash.custom_handlers->handler_data == &marker); for (i = 0; i < TEST_ARR_SIZE; i++) s_arr_store[i] = i * i; MG_DASH_ADD_FIELD_SET(&dash, &arr_set); mg_mgr_init(&mgr); mg_mem_files = mg_packed_files; ASSERT(mg_http_listen(&mgr, url, mg_dash_ev_handler, &dash) != NULL); // Test general get / set functionality ASSERT(fetch(&mgr, buf, url, "GET /api/get HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, get_all_expected) == 0); ASSERT(fetch(&mgr, buf, url, set1_req) == 200); ASSERT(cmpbody(buf, "1\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set1 HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, set1_expected) == 0); // Setting a variable with zero size should fail ASSERT(fetch(&mgr, buf, url, set2_req) == 200); ASSERT(cmpbody(buf, "0\n") == 0); ASSERT(two == false); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set2 HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, set2_expected) == 0); // Install auth function with two users - user, level 3 and admin, level 7 dash.authenticate = dash_authenticate; ASSERT(fetch(&mgr, buf, url, "GET /api/get HTTP/1.0\n\n") == 403); ASSERT(cmpbody(buf, "Not Authorised\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/login HTTP/1.0\n" "Authorization: Basic dXNlcjpiYWQ=\n\n") == 403); ASSERT(fetch(&mgr, buf, url, "GET /api/login HTTP/1.0\n" "Authorization: Basic dXNlcjp1c2Vy\n\n") == 200); ASSERT(cmpbody(buf, login_user_expected) == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/login HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, login_admin_expected) == 0); // set3 is admin-only ASSERT(fetch(&mgr, buf, url, "GET /api/get/set3 HTTP/1.0\n" "Authorization: Basic dXNlcjp1c2Vy\n\n") == 200); ASSERT(cmpbody(buf, "null\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set3 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, set3_expected) == 0); ASSERT(fetch(&mgr, buf, url, set3_user_req) == 200); ASSERT(cmpbody(buf, "0\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set3 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, set3_expected) == 0); ASSERT(fetch(&mgr, buf, url, set3_admin_req) == 200); ASSERT(cmpbody(buf, "1\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set3 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, set3_expected8) == 0); // set1 read level should be 3, write level 7 ASSERT(fetch(&mgr, buf, url, "GET /api/get/set1 HTTP/1.0\n" "Authorization: Basic dXNlcjp1c2Vy\n\n") == 200); ASSERT(cmpbody(buf, set1_expected) == 0); ASSERT(fetch(&mgr, buf, url, set1_set3_req) == 200); ASSERT(cmpbody(buf, "0\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set1 HTTP/1.0\n" "Authorization: Basic dXNlcjp1c2Vy\n\n") == 200); ASSERT(cmpbody(buf, set1_expected) == 0); ASSERT(fetch(&mgr, buf, url, set1_admin_set3_req) == 200); ASSERT(cmpbody(buf, "1\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set1 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, set1_expected3) == 0); ws_user_notify.opened = 0; ws_user_notify.done = 0; ws_user_notify.saw_set3 = 0; mg_ws_connect(&mgr, ws_url, dash_ws_cb, &ws_user_notify, "%s", "Authorization: Basic dXNlcjp1c2Vy\r\n"); for (i = 0; i < 200 && ws_user_notify.opened == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_user_notify.opened == 1); ASSERT(fetch(&mgr, buf, url, set3_admin_req9) == 200); ASSERT(cmpbody(buf, "1\n") == 0); for (i = 0; i < 200 && ws_user_notify.done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_user_notify.done == 0); ASSERT(ws_user_notify.saw_set3 == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set1 HTTP/1.0\n" "Authorization: Basic dXNlcjp1c2Vy\n\n") == 200); ASSERT(cmpbody(buf, set1_expected3) == 0); ws_admin.opened = 0; ws_admin.done = 0; ws_admin.saw_set3 = 0; mg_ws_connect(&mgr, ws_url, dash_ws_cb, &ws_admin, "%s", "Authorization: Basic YWRtaW46YWRtaW4=\r\n"); for (i = 0; i < 200 && ws_admin.opened == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_admin.opened == 1); ASSERT(fetch(&mgr, buf, url, set3_admin_req) == 200); ASSERT(cmpbody(buf, "1\n") == 0); for (i = 0; i < 200 && ws_admin.done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_admin.done == 1); ASSERT(ws_admin.saw_set3 == 1); ASSERT(fetch(&mgr, buf, url, set1_admin_set4_req) == 200); ASSERT(cmpbody(buf, "1\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/set1 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, set1_expected4) == 0); mg_delayms(1100); ASSERT(fetch(&mgr, buf, url, "GET /api/hi HTTP/1.0\n\n") == 200); ASSERT(cmpbody(buf, "hi\n") == 0); // Array tests with auth (17 squares) ASSERT(fetch(&mgr, buf, url, "GET /api/get/arr HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, "17\n") == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/arr/7 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, arr7_expected) == 0); ASSERT(fetch(&mgr, buf, url, "GET /api/get/arr/0/2 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, arr0_2_expected) == 0); // Array deletion: single element ASSERT(fetch(&mgr, buf, url, "GET /api/del/arr/3 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, "1\n") == 0); // Array deletion: range ASSERT(fetch(&mgr, buf, url, "GET /api/del/arr/1/3 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, "3\n") == 0); // Array deletion: out-of-range returns 403 ASSERT(fetch(&mgr, buf, url, "GET /api/del/arr/100 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 403); // Non-array set returns 404 ASSERT(fetch(&mgr, buf, url, "GET /api/del/set1/0 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 404); // Test that array change notification uses "arr/INDEX" key format { struct dash_arr_ws_test ws_arr; memset(&ws_arr, 0, sizeof(ws_arr)); ws_arr.saw_arr_index = -1; ws_arr.saw_arr_val = -1; ws_arr.saw_arr_size = -1; mg_ws_connect(&mgr, ws_url, dash_arr_ws_cb, &ws_arr, "%s", "Authorization: Basic YWRtaW46YWRtaW4=\r\n"); for (i = 0; i < 200 && ws_arr.opened == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_arr.opened == 1); s_arr_index = 5; mg_dash_send_change(&mgr, &arr_set); for (i = 0; i < 200 && ws_arr.done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_arr.done == 1); ASSERT(ws_arr.saw_arr_index == 5); } // Array modification: POST /api/get// updates one element // and broadcasts a "/" WS change notification with new value { struct dash_arr_ws_test ws_mod; memset(&ws_mod, 0, sizeof(ws_mod)); ws_mod.saw_arr_index = -1; ws_mod.saw_arr_val = -1; ws_mod.saw_arr_size = -1; mg_ws_connect(&mgr, ws_url, dash_arr_ws_cb, &ws_mod, "%s", "Authorization: Basic YWRtaW46YWRtaW4=\r\n"); for (i = 0; i < 200 && ws_mod.opened == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_mod.opened == 1); ASSERT(fetch(&mgr, buf, url, arr5_mod_req) == 200); ASSERT(cmpbody(buf, "1\n") == 0); for (i = 0; i < 200 && ws_mod.done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_mod.done == 1); ASSERT(ws_mod.saw_arr_index == 5); ASSERT(ws_mod.saw_arr_val == 99); // The new value got persisted - a fresh read returns it ASSERT(fetch(&mgr, buf, url, "GET /api/get/arr/5 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, arr5_mod_expected) == 0); // Out-of-range index: 404 ASSERT(fetch(&mgr, buf, url, arr100_mod_req) == 404); // Read-only element: fn denies MG_DASH_WRITE -> 403, value unchanged ASSERT(fetch(&mgr, buf, url, arr3_mod_req) == 403); ASSERT(fetch(&mgr, buf, url, "GET /api/get/arr/3 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, arr3_expected) == 0); } // Array addition: POST /api/add/ appends an element and broadcasts // a plain "":N WS change notification with the new size { struct dash_arr_ws_test ws_add; memset(&ws_add, 0, sizeof(ws_add)); ws_add.saw_arr_index = -1; ws_add.saw_arr_val = -1; ws_add.saw_arr_size = -1; mg_ws_connect(&mgr, ws_url, dash_arr_ws_cb, &ws_add, "%s", "Authorization: Basic YWRtaW46YWRtaW4=\r\n"); for (i = 0; i < 200 && ws_add.opened == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_add.opened == 1); ASSERT(fetch(&mgr, buf, url, arr_add555_req) == 200); ASSERT(cmpbody(buf, "true\n") == 0); for (i = 0; i < 200 && ws_add.done == 0; i++) mg_mgr_poll(&mgr, 1); ASSERT(ws_add.done == 1); ASSERT(ws_add.saw_arr_size == TEST_ARR_SIZE + 1); // The new element landed at the end of the array ASSERT(fetch(&mgr, buf, url, "GET /api/get/arr/17 HTTP/1.0\n" "Authorization: Basic YWRtaW46YWRtaW4=\n\n") == 200); ASSERT(cmpbody(buf, arr17_expected) == 0); // One more addition reaches the cap, then fn denies further ones ASSERT(fetch(&mgr, buf, url, arr_add556_req) == 200); ASSERT(fetch(&mgr, buf, url, arr_add557_req) == 403); } mg_mgr_free(&mgr); // Test no files fieldset: /fs/ returns 404 { const char *url2 = "http://localhost:26353"; struct mg_mgr mgr2; struct mg_dash dash2; memset(&dash2, 0, sizeof(dash2)); MG_DASH_ADD_FIELD_SET(&dash2, &set1); MG_DASH_ADD_FIELD_SET(&dash2, &set2); MG_DASH_ADD_FIELD_SET(&dash2, &set3); mg_mgr_init(&mgr2); mg_mem_files = mg_packed_files; ASSERT(mg_http_listen(&mgr2, url2, mg_dash_ev_handler, &dash2) != NULL); ASSERT(fetch(&mgr2, buf, url2, "GET /api/get HTTP/1.0\n\n") == 200); ASSERT(strstr(buf, files_probe) == NULL); // no files fieldset ASSERT(fetch(&mgr2, buf, url2, "GET /fs/test.txt HTTP/1.0\n\n") == 404); ASSERT(fetch(&mgr2, buf, url2, "POST /fs/test.txt HTTP/1.0\nContent-Length: 5\n\nhello") == 404); mg_mgr_free(&mgr2); } // Test the built-in file manager (struct mg_dash::files_dir): /fs/ active { const char *url3 = "http://localhost:26354"; struct mg_mgr mgr3; struct mg_dash dash3; struct mg_str uploaded; memset(&dash3, 0, sizeof(dash3)); dash3.files_dir = test_upload_dir; mg_mgr_init(&mgr3); mg_mem_files = mg_packed_files; ASSERT(mg_http_listen(&mgr3, url3, mg_dash_ev_handler, &dash3) != NULL); ASSERT(fetch(&mgr3, buf, url3, "GET /api/get HTTP/1.0\n\n") == 200); ASSERT(strstr(buf, files_probe) == NULL); // files manager is not a fieldset ASSERT(fetch(&mgr3, buf, url3, "DELETE /fs/nofile.txt HTTP/1.0\n\n") == 404); ASSERT(fetch(&mgr3, buf, url3, "DELETE /fs/..%2Fevil.txt HTTP/1.0\n\n") == 400); ASSERT(fetch(&mgr3, buf, url3, "PUT /fs/test.bin HTTP/1.0\nContent-Length: 4\n\ndata") == 200); uploaded = mg_file_read(&mg_fs_posix, "mongoose_dash_test/test.bin"); ASSERT(mg_strcmp(uploaded, mg_str("data")) == 0); mg_free((void *) uploaded.buf); ASSERT(fetch(&mgr3, buf, url3, "PUT /fs/..%2Fevil.bin HTTP/1.0\nContent-Length: " "4\n\ndata") == 400); ASSERT(fetch(&mgr3, buf, url3, "GET /fs/ HTTP/1.0\n\n") == 200); ASSERT(strstr(buf, test_bin_probe) != NULL); mg_mgr_free(&mgr3); } remove("mongoose_dash_test/test.bin"); #if MG_ARCH == MG_ARCH_UNIX || MG_ARCH == MG_ARCH_WIN32 rmdir("mongoose_dash_test"); #endif } #define DASHBOARD(x) \ printf("HEALTH_DASHBOARD\t\"%s\": %s,\n", x, s_error ? "false" : "true"); static uint8_t coils_database[25] = { 0x4D, 0x9C, 0xAA, 0x55, 0xCC, 0xA3, 0x66, 0xAF, 0x60, 0xBC, 0xCC, 0x6C, 0x53, 0xFF, 0x00, 0x55, 0x3C, 0x0F, 0xF0, 0x8F, 0x54, 0x99, 0xF8, 0x0D, 0x2A, }; static bool modbus_read_coil(uint16_t i) { uint16_t start_byte = i / 8; int bit_pos = i % 8; uint8_t data = ((coils_database[start_byte] >> bit_pos) & 0x01); return data; } static void modbus_srv(struct mg_connection *c, int ev, void *ev_data) { struct mg_modbus_req *mr = (struct mg_modbus_req *) ev_data; uint16_t i, r; size_t tc = *(size_t *) c->fn_data + 1; static bool coil_1[] = {1}; static bool coil_7[] = {1, 0, 1, 0, 1, 0, 1}; static uint16_t reg_1[] = {0x0001}; static uint16_t reg_7[] = {0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007}; if (ev == MG_EV_READ) { *(size_t *) c->fn_data += 1; } else if (ev == MG_EV_MODBUS_REQ) { if (mr->addr == 0xFFFF) { mr->error = MG_MODBUS_ERR_ILLEGAL_ADDRESS; return; } switch (mr->func) { case MG_MODBUS_FUNC_READ_COILS: case MG_MODBUS_FUNC_READ_DISCRETE_INPUTS: if (tc != 0x1D) { for (i = 0; i < mr->len; i++) { mr->u.bits[i] = (mr->len == 1 ? coil_1[i] : coil_7[i]); } } else { for (i = 0; i < mr->len; i++) { mr->u.bits[i] = modbus_read_coil(i); } } break; case MG_MODBUS_FUNC_READ_HOLDING_REGISTERS: case MG_MODBUS_FUNC_READ_INPUT_REGISTERS: if (tc == 0x1c) { mr->error = MG_MODBUS_ERR_DEVICE_FAILURE; break; } if (mr->len == 1) { mr->u.regs[0] = reg_1[0]; } else if (mr->len == 7) { for (i = 0; i < 7; i++) mr->u.regs[i] = reg_7[i]; } else { mr->error = MG_MODBUS_ERR_ILLEGAL_ADDRESS; } break; case MG_MODBUS_FUNC_WRITE_SINGLE_COIL: switch (tc) { case 0x05: ASSERT(mr->len == 1); ASSERT(mr->addr == 0x14); ASSERT(mr->u.bits != NULL); ASSERT(mr->u.bits[0] == true); break; default: ASSERT(false); return; } break; case MG_MODBUS_FUNC_WRITE_SINGLE_REGISTER: switch (tc) { case 0x06: ASSERT(mr->len == 1); ASSERT(mr->addr == 0x15); ASSERT(mr->u.regs != NULL); ASSERT(mr->u.regs[0] == 1); break; default: ASSERT(false); return; } break; case MG_MODBUS_FUNC_WRITE_MULTIPLE_COILS: switch (tc) { case 0x14: ASSERT(mr->addr == 0x23); ASSERT(mr->len == 1); ASSERT(mr->u.bits != NULL); ASSERT(mr->u.bits[0] == true); break; case 0x15: ASSERT(mr->addr == 0x24); ASSERT(mr->len == 7); ASSERT(mr->u.bits != NULL); for (i = 0; i < 7; i++) { ASSERT(mr->u.bits[i] == coil_7[i]); } break; default: ASSERT(false); return; } break; case MG_MODBUS_FUNC_WRITE_MULTIPLE_REGISTERS: switch (tc) { case 0x17: r = 0; ASSERT(mr->addr == 0x26); ASSERT(mr->len == 1); ASSERT(mr->u.regs != NULL); r = mr->u.regs[0]; ASSERT(r == 1); break; case 0x18: ASSERT(mr->addr == 0x27); ASSERT(mr->len == 7); ASSERT(mr->u.regs != NULL); for (i = 0; i < 7; i++) { r = mr->u.regs[i]; ASSERT(r == reg_7[i]); } break; default: ASSERT(false); return; } break; default: mr->error = MG_MODBUS_ERR_ILLEGAL_FUNCTION; break; } } (void) c, (void) ev_data; } static void test_modbus(void) { struct modbus_test { const char *name; uint8_t req[256]; size_t req_len; uint8_t resp[256]; size_t resp_len; } tests[] = { {"read coils - 1 @ 0x0010", {0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x10, 0x00, 0x01}, 12, {0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01}, 10}, {"read discrete inputs - 1 @ 0x0011", {0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x11, 0x00, 0x01}, 12, {0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01}, 10}, {"read holding registers - 1 @ 0x0012", {0x00, 0x03, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x12, 0x00, 0x01}, 12, {0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x00, 0x01}, 11}, {"read input registers - 1 @ 0x0013", {0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x13, 0x00, 0x01}, 12, {0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x00, 0x01}, 11}, {"write single coil @ 0x0014 = 1", {0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x14, 0xFF, 0x00}, 12, {0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x14, 0xFF, 0x00}, 12}, {"write single register @ 0x0015 = 1", {0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x15, 0x00, 0x01}, 12, {0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x01, 0x06, 0x00, 0x15, 0x00, 0x01}, 12}, /* --- Multi-value reads: 0, 1, 7 --- */ {"read coils - 0 @ 0x0016", {0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x16, 0x00, 0x00}, 12, {0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x01, 0x81, 0x03}, 9}, {"read coils - 1 @ 0x0017", {0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x17, 0x00, 0x01}, 12, {0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x01}, 10}, {"read coils - 7 @ 0x0018", {0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x18, 0x00, 0x07}, 12, {0x00, 0x09, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x55}, 10}, {"read discrete inputs - 0 @ 0x0019", {0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x19, 0x00, 0x00}, 12, {0x00, 0x0A, 0x00, 0x00, 0x00, 0x03, 0x01, 0x82, 0x03}, 9}, {"read discrete inputs - 1 @ 0x001A", {0x00, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x1A, 0x00, 0x01}, 12, {0x00, 0x0B, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x01}, 10}, {"read discrete inputs - 7 @ 0x001B", {0x00, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x1B, 0x00, 0x07}, 12, {0x00, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x01, 0x55}, 10}, {"read holding registers - 0 @ 0x001C", {0x00, 0x0D, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x1C, 0x00, 0x00}, 12, {0x00, 0x0D, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x03}, 9}, {"read holding registers - 1 @ 0x001D", {0x00, 0x0E, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x1D, 0x00, 0x01}, 12, {0x00, 0x0E, 0x00, 0x00, 0x00, 0x05, 0x01, 0x03, 0x02, 0x00, 0x01}, 11}, {"read holding registers - 7 @ 0x001E", {0x00, 0x0F, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x1E, 0x00, 0x07}, 12, {0x00, 0x0F, 0x00, 0x00, 0x00, 0x11, 0x01, 0x03, 0x0E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07}, 23}, {"read input registers - 0 @ 0x001F", {0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x1F, 0x00, 0x00}, 12, {0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x01, 0x84, 0x03}, 9}, {"read input registers - 1 @ 0x0020", {0x00, 0x11, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x20, 0x00, 0x01}, 12, {0x00, 0x11, 0x00, 0x00, 0x00, 0x05, 0x01, 0x04, 0x02, 0x00, 0x01}, 11}, {"read input registers - 7 @ 0x0021", {0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x21, 0x00, 0x07}, 12, {0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x01, 0x04, 0x0E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07}, 23}, {"write multiple coils - 0 @ 0x0022", {0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x01, 0x0F, 0x00, 0x22, 0x00, 0x00, 0x00}, 13, {0x00, 0x13, 0x00, 0x00, 0x00, 0x03, 0x01, 0x8F, 0x03}, 9}, {"write multiple coils - 1 @ 0x0023", {0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x01, 0x0F, 0x00, 0x23, 0x00, 0x01, 0x01, 0x01}, 14, {0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x23, 0x00, 0x01}, 12}, {"write multiple coils - 7 @ 0x0024", {0x00, 0x15, 0x00, 0x00, 0x00, 0x08, 0x01, 0x0F, 0x00, 0x24, 0x00, 0x07, 0x01, 0x55}, 14, {0x00, 0x15, 0x00, 0x00, 0x00, 0x06, 0x01, 0x0F, 0x00, 0x24, 0x00, 0x07}, 12}, {"write multiple registers - 0 @ 0x0025", {0x00, 0x16, 0x00, 0x00, 0x00, 0x07, 0x01, 0x10, 0x00, 0x25, 0x00, 0x00, 0x00}, 13, {0x00, 0x16, 0x00, 0x00, 0x00, 0x03, 0x01, 0x90, 0x03}, 9}, {"write multiple registers - 1 @ 0x0026", {0x00, 0x17, 0x00, 0x00, 0x00, 0x09, 0x01, 0x10, 0x00, 0x26, 0x00, 0x01, 0x02, 0x00, 0x01}, 15, {0x00, 0x17, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x26, 0x00, 0x01}, 12}, {"write multiple registers - 7 @ 0x0027", {0x00, 0x18, 0x00, 0x00, 0x00, 0x15, 0x01, 0x10, 0x00, 0x27, 0x00, 0x07, 0x0E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07}, 27, {0x00, 0x18, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x00, 0x27, 0x00, 0x07}, 12}, /* --- Standard Modbus exception cases --- */ {"illegal function", {0x00, 0x19, 0x00, 0x00, 0x00, 0x06, 0x01, 0x11, 0x00, 0x28, 0x00, 0x01}, 12, {0x00, 0x19, 0x00, 0x00, 0x00, 0x03, 0x01, 0x91, 0x01}, 9}, {"illegal data address", {0x00, 0x1A, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0xFF, 0xFF, 0x00, 0x01}, 12, {0x00, 0x1A, 0x00, 0x00, 0x00, 0x03, 0x01, 0x83, 0x02}, 9}, {"illegal data value - bad coil value", {0x00, 0x1B, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x29, 0x12, 0x34}, 12, {0x00, 0x1B, 0x00, 0x00, 0x00, 0x03, 0x01, 0x85, 0x03}, 9}, {"server device failure", {0x00, 0x1C, 0x00, 0x00, 0x00, 0x06, 0x01, 0x04, 0x00, 0x2A, 0x00, 0x01}, 12, {0x00, 0x1C, 0x00, 0x00, 0x00, 0x03, 0x01, 0x84, 0x04}, 9}, /* --- Stress Test ---*/ {"read coils - 200 @ 0x0000", {0x00, 0x1D, 0x00, 0x00, 0x00, 0x06, 0x01, 0x01, 0x00, 0x00, 0x00, 0xC8}, 12, {0x00, 0x1D, 0x00, 0x00, 0x00, 0x1C, 0x01, 0x01, 0x19, 0x4D, 0x9C, 0xAA, 0x55, 0xCC, 0xA3, 0x66, 0xAF, 0x60, 0xBC, 0xCC, 0x6C, 0x53, 0xFF, 0x00, 0x55, 0x3C, 0x0F, 0xF0, 0x8F, 0x54, 0x99, 0xF8, 0x0D, 0x2A}, 34}, }; struct mg_mgr mgr; struct mg_connection *c; const char *url = "tcp://localhost:32625"; size_t i, j, total = 0, num_tests = sizeof(tests) / sizeof(tests[0]); mg_mgr_init(&mgr); mg_modbus_listen(&mgr, url, modbus_srv, &total); c = mg_connect(&mgr, url, NULL, NULL); for (i = 0; i < num_tests; i++) { MG_INFO(("TEST %s", tests[i].name)); mg_send(c, tests[i].req, tests[i].req_len); for (j = 0; j < 20; j++) mg_mgr_poll(&mgr, 1); mg_hexdump(tests[i].req, tests[i].req_len); mg_hexdump(tests[i].resp, tests[i].resp_len); mg_hexdump(c->recv.buf, c->recv.len); ASSERT(c->recv.len == tests[i].resp_len); ASSERT(memcmp(c->recv.buf, tests[i].resp, c->recv.len) == 0); c->recv.len = 0; } ASSERT(total == num_tests); mg_mgr_free(&mgr); } struct wudata { struct mg_mgr *mgr; unsigned long conn_id; // Parent connection ID }; static void wuthread(void *param) { struct wudata *p = (struct wudata *) param; mg_wakeup(p->mgr, p->conn_id, "hi!", 3); // Send to parent free(p); // Free all resources that were passed to us } static void hwu(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_WAKEUP) { struct mg_str *data = (struct mg_str *) ev_data; MG_DEBUG(("Woke up, got: %.*s", data->len, data->buf)); memcpy(c->data, data->buf, data->len); c->data[data->len] = '\0'; } } static void test_wakeup(void) { #if MG_ENABLE_SOCKET struct mg_mgr mgr; struct mg_connection *c; struct wudata *data; int i; mg_mgr_init(&mgr); // Initialise event manager mg_log_set(MG_LL_DEBUG); c = mg_http_listen(&mgr, "http://127.0.0.1:12341", hwu, NULL); mg_wakeup_init(&mgr); // Initialise wakeup socket pair data = (struct wudata *) calloc(1, sizeof(*data)); // wuthread owns it data->conn_id = c->id; data->mgr = c->mgr; start_thread(wuthread, data); // Start thread and pass data for (i = 0; i < 10 || c->data[0] == '\0'; i++) mg_mgr_poll(&mgr, 100); ASSERT(mg_strcmp(mg_str(c->data), mg_str_n("hi!", 3)) == 0); mg_mgr_free(&mgr); #else printf("\nNO SOCKET SUPPORT, skipping mg_wakeup() test\n"); (void) hwu; (void) wuthread; #endif } extern uint64_t mg_boot_timestamp_ms; int main(void) { const char *debug_level = getenv("V"); if (debug_level == NULL) debug_level = "3"; mg_log_set(atoi(debug_level)); // make sure there is a time reference for mg_now() regardless of SNTP tests mg_boot_timestamp_ms = (uint64_t) time(NULL) * 1000; s_error = false; test_modbus(); DASHBOARD("modbus"); s_error = false; test_crypto(); DASHBOARD("crypto"); s_error = false; test_split(); test_util(); test_str(); test_match(); test_crc32(); test_crc16(); DASHBOARD("misc"); s_error = false; test_json(); DASHBOARD("json"); s_error = false; test_queue(); DASHBOARD("queue"); s_error = false; test_rpc(); DASHBOARD("rpc"); s_error = false; test_dash(); DASHBOARD("dash"); s_error = false; test_check_ip_acl(); DASHBOARD("ip_acl"); s_error = false; test_udp(); DASHBOARD("udp"); s_error = false; test_wakeup(); DASHBOARD("wakeup"); s_error = false; test_get_header_var(); test_http_get_var(); test_http_parse(); test_rewrites(); test_multipart(); test_invalid_listen_addr(); test_http_chunked(); DASHBOARD("http_support"); s_error = false; test_packed(); DASHBOARD("packed_fs"); s_error = false; test_dns(); DASHBOARD("dns"); s_error = false; test_timer(); DASHBOARD("timers"); s_error = false; test_url(); DASHBOARD("url"); s_error = false; test_iobuf(); DASHBOARD("iobuf"); s_error = false; test_base64(); DASHBOARD("base64"); s_error = false; test_tls(); DASHBOARD("tls"); s_error = false; test_ws(); test_ws_fragmentation(); DASHBOARD("ws"); s_error = false; test_http_upload(); test_http_stream_buffer(); test_http_server(); test_http_404(); test_http_no_content_length(); test_http_pipeline(); test_http_range(); DASHBOARD("http_server"); #ifndef LOCALHOST_ONLY s_error = false; test_sntp(); DASHBOARD("sntp"); s_error = false; test_mqtt(); // sorry, MQTT_LOCALHOST is also skipped DASHBOARD("mqtt"); s_error = false; test_http_client(); DASHBOARD("http_client"); #else printf("\nLOCALHOST_ONLY, skipping SNTP, MQTT and HTTPclient tests\n"); (void) test_sntp, (void) test_mqtt, (void) test_http_client; #endif s_error = false; test_poll(); printf("HEALTH_DASHBOARD\t\"poll\": %s\n", s_error ? "false" : "true"); // last entry with no comma #ifdef NO_ABORT if (s_abort != 0) return EXIT_FAILURE; #endif #if defined(MBEDTLS_VERSION_NUMBER) && MBEDTLS_VERSION_NUMBER >= 0x03000000 && \ defined(MBEDTLS_PSA_CRYPTO_C) // Call mbedtls_psa_crypto_free() here to avoid triggering memory-leak // detectors. We are actually freeing all our resources and leaving mbedtls_psa_crypto_free(); #endif printf("SUCCESS. Total tests: %d\n", s_num_tests); return EXIT_SUCCESS; }