diff --git a/3d_party/mongoose/install.sh b/3d_party/mongoose/install.sh new file mode 100755 index 0000000000000000000000000000000000000000..bdd319838f0f65aa9fa127c9ebb940a8f59ed890 --- /dev/null +++ b/3d_party/mongoose/install.sh @@ -0,0 +1,2 @@ +wget https://raw.githubusercontent.com/cesanta/mongoose/7.4/mongoose.c +wget https://raw.githubusercontent.com/cesanta/mongoose/7.4/mongoose.h \ No newline at end of file diff --git a/3d_party/mongoose/mongoose.c b/3d_party/mongoose/mongoose.c new file mode 100644 index 0000000000000000000000000000000000000000..84978f615ead1964bfdd685348f566a0963de885 --- /dev/null +++ b/3d_party/mongoose/mongoose.c @@ -0,0 +1,4794 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2021 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see <http://www.gnu.org/licenses/>. +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, as set out in <https://www.cesanta.com/license>. + +#include "mongoose.h" + +#ifdef MG_ENABLE_LINES +#line 1 "src/private.h" +#endif +void mg_connect_resolved(struct mg_connection *); + +#ifdef MG_ENABLE_LINES +#line 1 "src/base64.c" +#endif +#include <string.h> + +static int mg_b64idx(int c) { + if (c < 26) { + return c + 'A'; + } else if (c < 52) { + return c - 26 + 'a'; + } else if (c < 62) { + return c - 52 + '0'; + } else { + return c == 62 ? '+' : '/'; + } +} + +static int mg_b64rev(int c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c + 26 - 'a'; + } else if (c >= '0' && c <= '9') { + return c + 52 - '0'; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else if (c == '=') { + return 64; + } else { + return -1; + } +} + +int mg_base64_update(unsigned char ch, char *to, int n) { + int rem = (n & 3) % 3; + if (rem == 0) { + to[n] = (char) mg_b64idx(ch >> 2); + to[++n] = (char) ((ch & 3) << 4); + } else if (rem == 1) { + to[n] = (char) mg_b64idx(to[n] | (ch >> 4)); + to[++n] = (char) ((ch & 15) << 2); + } else { + to[n] = (char) mg_b64idx(to[n] | (ch >> 6)); + to[++n] = (char) mg_b64idx(ch & 63); + n++; + } + return n; +} + +int mg_base64_final(char *to, int n) { + int saved = n; + // printf("---[%.*s]\n", n, to); + if (n & 3) n = mg_base64_update(0, to, n); + if ((saved & 3) == 2) n--; + // printf(" %d[%.*s]\n", n, n, to); + while (n & 3) to[n++] = '='; + to[n] = '\0'; + return n; +} + +int mg_base64_encode(const unsigned char *p, int n, char *to) { + int i, len = 0; + for (i = 0; i < n; i++) len = mg_base64_update(p[i], to, len); + len = mg_base64_final(to, len); + return len; +} + +int mg_base64_decode(const char *src, int n, char *dst) { + const char *end = src + n; + int len = 0; + while (src + 3 < end) { + int a = mg_b64rev(src[0]), b = mg_b64rev(src[1]), c = mg_b64rev(src[2]), + d = mg_b64rev(src[3]); + if (a == 64 || a < 0 || b == 64 || b < 0 || c < 0 || d < 0) return 0; + dst[len++] = (char) ((a << 2) | (b >> 4)); + if (src[2] != '=') { + dst[len++] = (char) ((b << 4) | (c >> 2)); + if (src[3] != '=') dst[len++] = (char) ((c << 6) | d); + } + src += 4; + } + dst[len] = '\0'; + return len; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/dns.c" +#endif + + + + + + + +struct dns_data { + struct dns_data *next; + struct mg_connection *c; + unsigned long expire; + uint16_t txnid; +}; + +static struct dns_data *s_reqs; // Active DNS requests + +static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, + struct mg_dns *, bool); + +static void mg_dns_free(struct dns_data *d) { + LIST_DELETE(struct dns_data, &s_reqs, d); + free(d); +} + +void mg_resolve_cancel(struct mg_connection *c) { + struct dns_data *tmp, *d; + for (d = s_reqs; d != NULL; d = tmp) { + tmp = d->next; + if (d->c == c) mg_dns_free(d); + } +} + +static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, + char *to, size_t tolen, int depth) { + size_t i = 0, j = 0; + if (tolen > 0) to[0] = '\0'; + if (depth > 5) return 0; + while (ofs + i + 1 < len) { + size_t n = s[ofs + i]; + if (n == 0) { + i++; + break; + } + if (n & 0xc0) { + size_t ptr = (((n & 0x3f) << 8) | s[ofs + i + 1]); // 12 is hdr len + if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && + mg_dns_parse_name_depth(s, len, ptr, to, tolen, depth + 1) == 0) + return 0; + i += 2; + break; + } + if (ofs + i + n + 1 >= len) return 0; + if (j > 0) { + if (j < tolen) to[j] = '.'; + j++; + } + if (j + n < tolen) memcpy(&to[j], &s[ofs + i + 1], n); + j += n; + i += n + 1; + if (j < tolen) to[j] = '\0'; // Zero-terminate this chunk + } + if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term + return i; +} + +size_t mg_dns_parse_name(const uint8_t *s, size_t n, size_t ofs, char *dst, + size_t dstlen) { + return mg_dns_parse_name_depth(s, n, ofs, dst, dstlen, 0); +} + +size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr *rr) { + const uint8_t *s = buf + ofs, *e = &buf[len]; + + memset(rr, 0, sizeof(*rr)); + if (len < sizeof(struct mg_dns_header)) return 0; // Too small + if (len > 512) return 0; // Too large, we don't expect that + if (s >= e) return 0; // Overflow + + if ((rr->nlen = (uint16_t) mg_dns_parse_name(buf, len, ofs, NULL, 0)) == 0) + return 0; + s += rr->nlen + 4; + if (s > e) return 0; + rr->atype = (uint16_t)(((uint16_t) s[-4] << 8) | s[-3]); + rr->aclass = (uint16_t)(((uint16_t) s[-2] << 8) | s[-1]); + if (is_question) return (size_t)(rr->nlen + 4); + + s += 6; + if (s > e) return 0; + rr->alen = (uint16_t)(((uint16_t) s[-2] << 8) | s[-1]); + if (s + rr->alen > e) return 0; + return (size_t)(rr->nlen + rr->alen + 10); +} + +bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) { + const struct mg_dns_header *h = (struct mg_dns_header *) buf; + struct mg_dns_rr rr; + size_t i, n, ofs = sizeof(*h); + memset(dm, 0, sizeof(*dm)); + + if (len < sizeof(*h)) return 0; // Too small, headers dont fit + if (mg_ntohs(h->num_questions) > 1) return 0; // Sanity + if (mg_ntohs(h->num_answers) > 10) return 0; // Sanity + dm->txnid = mg_ntohs(h->txnid); + + for (i = 0; i < mg_ntohs(h->num_questions); i++) { + if ((n = mg_dns_parse_rr(buf, len, ofs, true, &rr)) == 0) return false; + // LOG(LL_INFO, ("Q %zu %zu", ofs, n)); + ofs += n; + } + for (i = 0; i < mg_ntohs(h->num_answers); i++) { + // LOG(LL_INFO, ("A -- %zu %zu %s", ofs, n, dm->name)); + if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; + mg_dns_parse_name(buf, len, ofs, dm->name, sizeof(dm->name)); + ofs += n; + + if (rr.alen == 4 && rr.atype == 1 && rr.aclass == 1) { + dm->addr.is_ip6 = false; + memcpy(&dm->addr.ip, &buf[ofs - 4], 4); + dm->resolved = true; + break; // Return success + } else if (rr.alen == 16 && rr.atype == 28 && rr.aclass == 1) { + dm->addr.is_ip6 = true; + memcpy(&dm->addr.ip6, &buf[ofs - 16], 16); + dm->resolved = true; + break; // Return success + } + } + return true; +} + +static void dns_cb(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + struct dns_data *d, *tmp; + if (ev == MG_EV_POLL) { + unsigned long now = *(unsigned long *) ev_data; + for (d = s_reqs; d != NULL; d = tmp) { + tmp = d->next; + // LOG(LL_DEBUG, ("%lu %lu dns poll", d->expire, now)); + if (now > d->expire) mg_error(d->c, "DNS timeout"); + } + } else if (ev == MG_EV_READ) { + struct mg_dns_message dm; + int resolved = 0; + if (mg_dns_parse(c->recv.buf, c->recv.len, &dm) == false) { + char *s = mg_hexdump(c->recv.buf, c->recv.len); + LOG(LL_ERROR, ("Unexpected DNS response:\n%s\n", s)); + free(s); + } else { + LOG(LL_VERBOSE_DEBUG, ("%s %d", dm.name, dm.resolved)); + for (d = s_reqs; d != NULL; d = tmp) { + tmp = d->next; + // LOG(LL_INFO, ("d %p %hu %hu", d, d->txnid, dm.txnid)); + if (dm.txnid != d->txnid) continue; + if (d->c->is_resolving) { + d->c->is_resolving = 0; + if (dm.resolved) { +#if MG_ENABLE_LOG + char buf[100]; +#endif + dm.addr.port = d->c->peer.port; // Save port + d->c->peer = dm.addr; // Copy resolved address + LOG(LL_DEBUG, ("%lu %s resolved to %s", d->c->id, dm.name, + mg_ntoa(&d->c->peer, buf, sizeof(buf)))); + mg_connect_resolved(d->c); +#if MG_ENABLE_IPV6 + } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0') { + struct mg_str x = mg_str(dm.name); + mg_sendnsreq(d->c, &x, c->mgr->dnstimeout, &c->mgr->dns6, true); +#endif + } else { + mg_error(d->c, "%s DNS lookup failed", dm.name); + } + } else { + LOG(LL_ERROR, ("%lu already resolved", d->c->id)); + } + mg_dns_free(d); + resolved = 1; + } + } + if (!resolved) LOG(LL_ERROR, ("stray DNS reply")); + c->recv.len = 0; + } else if (ev == MG_EV_CLOSE) { + for (d = s_reqs; d != NULL; d = tmp) { + tmp = d->next; + mg_dns_free(d); + } + } + (void) fn_data; +} + +void mg_dns_send(struct mg_connection *c, const struct mg_str *name, + uint16_t txnid, bool ipv6) { + struct { + struct mg_dns_header header; + uint8_t data[256]; + } pkt; + size_t i, n; + memset(&pkt, 0, sizeof(pkt)); + pkt.header.txnid = mg_htons(txnid); + pkt.header.flags = mg_htons(0x100); + pkt.header.num_questions = mg_htons(1); + for (i = n = 0; i < sizeof(pkt.data) - 5; i++) { + if (name->ptr[i] == '.' || i >= name->len) { + pkt.data[n] = (uint8_t)(i - n); + memcpy(&pkt.data[n + 1], name->ptr + n, i - n); + n = i + 1; + } + if (i >= name->len) break; + } + memcpy(&pkt.data[n], "\x00\x00\x01\x00\x01", 5); // A query + n += 5; + if (ipv6) pkt.data[n - 3] = 0x1c; // AAAA query + // memcpy(&pkt.data[n], "\xc0\x0c\x00\x1c\x00\x01", 6); // AAAA query + // n += 6; + mg_send(c, &pkt, sizeof(pkt.header) + n); +#if 0 + // Immediately after A query, send AAAA query. Whatever reply comes first, + // we'll use it. Note: we cannot send two queries in a single packet. + // https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query + pkt.data[n - 3] = 0x1c; // AAAA query + mg_send(c, &pkt, sizeof(pkt.header) + n); +#endif +} + +static void mg_sendnsreq(struct mg_connection *c, struct mg_str *name, int ms, + struct mg_dns *dnsc, bool ipv6) { + struct dns_data *d = NULL; + if (dnsc->url == NULL) { + mg_error(c, "DNS server URL is NULL. Call mg_mgr_init()"); + } else if (dnsc->c == NULL) { + dnsc->c = mg_connect(c->mgr, dnsc->url, NULL, NULL); + if (dnsc->c != NULL) { + dnsc->c->pfn = dns_cb; + snprintf(dnsc->c->label, sizeof(dnsc->c->label), "%s", "DNS"); + // dnsc->c->is_hexdumping = 1; + } + } + if (dnsc->c == NULL) { + mg_error(c, "resolver"); + } else if ((d = (struct dns_data *) calloc(1, sizeof(*d))) == NULL) { + mg_error(c, "resolve OOM"); + } else { +#if MG_ENABLE_LOG + char buf[100]; +#endif + d->txnid = s_reqs ? (uint16_t)(s_reqs->txnid + 1) : 1; + d->next = s_reqs; + s_reqs = d; + d->expire = mg_millis() + (unsigned long) ms; + d->c = c; + c->is_resolving = 1; + LOG(LL_VERBOSE_DEBUG, + ("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, + name->ptr, mg_ntoa(&dnsc->c->peer, buf, sizeof(buf)), d->txnid)); + mg_dns_send(dnsc->c, name, d->txnid, ipv6); + } +} + +void mg_resolve(struct mg_connection *c, struct mg_str *name, int ms) { + if (mg_aton(*name, &c->peer)) { + // name is an IP address, do not fire name resolution + mg_connect_resolved(c); + } else { + // name is not an IP, send DNS resolution request + mg_sendnsreq(c, name, ms, &c->mgr->dns4, false); + } +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/event.c" +#endif + + + + + +void mg_call(struct mg_connection *c, int ev, void *ev_data) { + if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data); + if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data); +} + +void mg_error(struct mg_connection *c, const char *fmt, ...) { + char mem[256], *buf = mem; + va_list ap; + va_start(ap, fmt); + mg_vasprintf(&buf, sizeof(mem), fmt, ap); + va_end(ap); + LOG(LL_ERROR, ("%lu %s", c->id, buf)); + mg_call(c, MG_EV_ERROR, buf); + if (buf != mem) free(buf); + c->is_closing = 1; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_packed.c" +#endif + + +struct packed_file { + const char *data; + size_t size; + size_t pos; +}; + +const char *mg_unpack(const char *path, size_t *size, time_t *mtime); +const char *mg_unlist(size_t no); +#if MG_ENABLE_PACKED_FS +#else +const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { + (void) path, (void) size, (void) mtime; + return NULL; +} +const char *mg_unlist(size_t no) { + (void) no; + return NULL; +} +#endif + +static int is_dir_prefix(const char *prefix, size_t n, const char *path) { + return n < strlen(path) && memcmp(prefix, path, n) == 0 && path[n] == '/'; + //(n == 0 || path[n] == MG_DIRSEP); +} + +static int packed_stat(const char *path, size_t *size, time_t *mtime) { + const char *p; + size_t i, n = strlen(path); + if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file + // Scan all files. If `path` is a dir prefix for any of them, it's a dir + for (i = 0; (p = mg_unlist(i)) != NULL; i++) { + if (is_dir_prefix(path, n, p)) return MG_FS_DIR; + } + return 0; +} + +static void packed_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + char buf[256], tmp[sizeof(buf)]; + const char *path, *begin, *end; + size_t i, n = strlen(dir); + tmp[0] = '\0'; // Previously listed entry + for (i = 0; (path = mg_unlist(i)) != NULL; i++) { + if (!is_dir_prefix(dir, n, path)) continue; + begin = &path[n + 1]; + end = strchr(begin, '/'); + if (end == NULL) end = begin + strlen(begin); + snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin); + buf[sizeof(buf) - 1] = '\0'; + // If this entry has been already listed, skip + // NOTE: we're assuming that file list is sorted alphabetically + if (strcmp(buf, tmp) == 0) continue; + fn(buf, userdata); // Not yet listed, call user function + strcpy(tmp, buf); // And save this entry as listed + } +} + +static struct mg_fd *packed_open(const char *path, int flags) { + size_t size = 0; + const char *data = mg_unpack(path, &size, NULL); + struct packed_file *fp = NULL; + struct mg_fd *fd = NULL; + if (data == NULL) return NULL; + if (flags & MG_FS_WRITE) return NULL; + fp = (struct packed_file *) calloc(1, sizeof(*fp)); + fd = (struct mg_fd *) calloc(1, sizeof(*fd)); + fp->size = size; + fp->data = data; + fd->fd = fp; + fd->fs = &mg_fs_packed; + return fd; +} + +static void packed_close(struct mg_fd *fd) { + if (fd) free(fd->fd), free(fd); +} + +static size_t packed_read(void *fd, void *buf, size_t len) { + struct packed_file *fp = (struct packed_file *) fd; + if (fp->pos + len > fp->size) len = fp->size - fp->pos; + memcpy(buf, &fp->data[fp->pos], len); + fp->pos += len; + return len; +} + +static size_t packed_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t packed_seek(void *fd, size_t offset) { + struct packed_file *fp = (struct packed_file *) fd; + fp->pos = offset; + if (fp->pos > fp->size) fp->pos = fp->size; + return fp->pos; +} + +struct mg_fs mg_fs_packed = {packed_stat, packed_list, packed_open, + packed_close, packed_read, packed_write, + packed_seek}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_posix.c" +#endif + + +#if defined(FOPEN_MAX) +static int p_stat(const char *path, size_t *size, time_t *mtime) { +#ifdef _WIN32 + struct _stati64 st; + wchar_t tmp[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); + if (_wstati64(tmp, &st) != 0) return 0; +#else + struct stat st; + if (stat(path, &st) != 0) return 0; +#endif + if (size) *size = (size_t) st.st_size; + if (mtime) *mtime = st.st_mtime; + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); +} + +#ifdef _WIN32 +struct dirent { + char d_name[MAX_PATH]; +}; + +typedef struct win32_dir { + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +} DIR; + +int gettimeofday(struct timeval *tv, void *tz) { + FILETIME ft; + unsigned __int64 tmpres = 0; + + if (tv != NULL) { + GetSystemTimeAsFileTime(&ft); + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + tmpres /= 10; // convert into microseconds + tmpres -= (int64_t) 11644473600000000; + tv->tv_sec = (long) (tmpres / 1000000UL); + tv->tv_usec = (long) (tmpres % 1000000UL); + } + (void) tz; + return 0; +} + +static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + // Trim trailing slashes. Leave backslash for paths like "X:\" + p = buf + strlen(buf) - 1; + while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); + // Convert back to Unicode. If doubly-converted string does not match the + // original, something is fishy, reject. + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), + NULL, NULL); + if (strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + return ret; +} + +DIR *opendir(const char *name) { + DIR *d = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); + attrs = GetFileAttributesW(wpath); + if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + d->handle = FindFirstFileW(wpath, &d->info); + d->result.d_name[0] = '\0'; + } else { + free(d); + d = NULL; + } + } + return d; +} + +int closedir(DIR *d) { + int result = 0; + if (d != NULL) { + if (d->handle != INVALID_HANDLE_VALUE) + result = FindClose(d->handle) ? 0 : -1; + free(d); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} + +struct dirent *readdir(DIR *d) { + struct dirent *result = NULL; + if (d != NULL) { + memset(&d->result, 0, sizeof(d->result)); + if (d->handle != INVALID_HANDLE_VALUE) { + result = &d->result; + WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, + sizeof(result->d_name), NULL, NULL); + if (!FindNextFileW(d->handle, &d->info)) { + FindClose(d->handle); + d->handle = INVALID_HANDLE_VALUE; + } + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + return result; +} +#endif + +static void p_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { +#if MG_ENABLE_DIRLIST + struct dirent *dp; + DIR *dirp; + if ((dirp = (opendir(dir))) == NULL) return; + while ((dp = readdir(dirp)) != NULL) { + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, userdata); + } + closedir(dirp); +#else + (void) dir, (void) fn, (void) userdata; +#endif +} + +static struct mg_fd *p_open(const char *path, int flags) { + const char *mode = flags == (MG_FS_READ | MG_FS_WRITE) ? "r+b" + : flags & MG_FS_READ ? "rb" + : flags & MG_FS_WRITE ? "wb" + : ""; + void *fp = NULL; + struct mg_fd *fd = NULL; +#ifdef _WIN32 + wchar_t b1[PATH_MAX], b2[10]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); + fp = (void *) _wfopen(b1, b2); +#else + fp = (void *) fopen(path, mode); +#endif + if (fp == NULL) return NULL; + fd = (struct mg_fd *) calloc(1, sizeof(*fd)); + fd->fd = fp; + fd->fs = &mg_fs_posix; + return fd; +} + +static void p_close(struct mg_fd *fd) { + if (fd != NULL) fclose((FILE *) fd->fd), free(fd); +} + +static size_t p_read(void *fp, void *buf, size_t len) { + return fread(buf, 1, len, (FILE *) fp); +} + +static size_t p_write(void *fp, const void *buf, size_t len) { + return fwrite(buf, 1, len, (FILE *) fp); +} + +static size_t p_seek(void *fp, size_t offset) { +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ + _XOPEN_SOURCE >= 600 + fseeko((FILE *) fp, (off_t) offset, SEEK_SET); +#else + fseek((FILE *) fp, (long) offset, SEEK_SET); +#endif + return (size_t) ftell((FILE *) fp); +} +#else +static char *p_realpath(const char *path, char *resolved_path) { + (void) path, (void) resolved_path; + return NULL; +} + +static int p_stat(const char *path, size_t *size, time_t *mtime) { + (void) path, (void) size, (void) mtime; + return 0; +} + +static void p_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; +} + +static struct mg_fd *p_open(const char *path, int flags) { + (void) path, (void) flags; + return NULL; +} + +static void p_close(struct mg_fd *fd) { + (void) fd; +} + +static size_t p_read(void *fd, void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t p_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t p_seek(void *fd, size_t offset) { + (void) fd, (void) offset; + return (size_t) ~0; +} +#endif + +struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, + p_read, p_write, p_seek}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/http.c" +#endif + + + + + + + + + + + +// Multipart POST example: +// --xyz +// Content-Disposition: form-data; name="val" +// +// abcdef +// --xyz +// Content-Disposition: form-data; name="foo"; filename="a.txt" +// Content-Type: text/plain +// +// hello world +// +// --xyz-- +size_t mg_http_next_multipart(struct mg_str body, size_t ofs, + struct mg_http_part *part) { + struct mg_str cd = mg_str_n("Content-Disposition", 19); + const char *s = body.ptr; + size_t b = ofs, h1, h2, b1, b2, max = body.len; + + // Init part params + if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); + + // Skip boundary + while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; + if (b <= ofs || b + 2 >= max) return 0; + // LOG(LL_INFO, ("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); + + // Skip headers + h1 = h2 = b + 2; + for (;;) { + while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; + if (h2 == h1) break; + if (h2 + 2 >= max) return 0; + // LOG(LL_INFO, ("Header: [%.*s]", (int) (h2 - h1), &s[h1])); + if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && + mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) { + struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); + part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); + part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); + } + h1 = h2 = h2 + 2; + } + b1 = b2 = h2 + 2; + while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' && + memcmp(&s[b2 + 2], s, b - ofs) == 0)) + b2++; + + if (b2 + 2 >= max) return 0; + if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); + // LOG(LL_INFO, ("Body: [%.*s]", (int) (b2 - b1), &s[b1])); + return b2 + 2; +} + +void mg_http_bauth(struct mg_connection *c, const char *user, + const char *pass) { + struct mg_str u = mg_str(user), p = mg_str(pass); + size_t need = c->send.len + 36 + (u.len + p.len) * 2; + if (c->send.size < need) mg_iobuf_resize(&c->send, need); + if (c->send.size >= need) { + int i, n = 0; + char *buf = (char *) &c->send.buf[c->send.len + 21]; + memcpy(&buf[-21], "Authorization: Basic ", 21); // DON'T use mg_send! + for (i = 0; i < (int) u.len; i++) { + n = mg_base64_update(((unsigned char *) u.ptr)[i], buf, n); + } + if (p.len > 0) { + n = mg_base64_update(':', buf, n); + for (i = 0; i < (int) p.len; i++) { + n = mg_base64_update(((unsigned char *) p.ptr)[i], buf, n); + } + } + n = mg_base64_final(buf, n); + c->send.len += 21 + (size_t) n + 2; + memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2); + } else { + LOG(LL_ERROR, ("%lu %s cannot resize iobuf %d->%d ", c->id, c->label, + (int) c->send.size, (int) need)); + } +} + +int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, + size_t dst_len) { + const char *p, *e, *s; + size_t name_len; + int len; + + if (dst == NULL || dst_len == 0) { + len = -2; // Bad destination + } else if (buf->ptr == NULL || name == NULL || buf->len == 0) { + len = -1; // Bad source + dst[0] = '\0'; + } else { + name_len = strlen(name); + e = buf->ptr + buf->len; + len = -4; // Name does not exist + dst[0] = '\0'; + for (p = buf->ptr; p + name_len < e; p++) { + if ((p == buf->ptr || p[-1] == '&') && p[name_len] == '=' && + !mg_ncasecmp(name, p, name_len)) { + p += name_len + 1; + s = (const char *) memchr(p, '&', (size_t) (e - p)); + if (s == NULL) s = e; + len = mg_url_decode(p, (size_t) (s - p), dst, dst_len, 1); + if (len < 0) len = -3; // Failed to decode + break; + } + } + } + return len; +} + +int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, + int is_form_url_encoded) { + size_t i, j; + for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) { + if (src[i] == '%') { + // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len + if (i + 2 < src_len && isxdigit(*(const unsigned char *) (src + i + 1)) && + isxdigit(*(const unsigned char *) (src + i + 2))) { + mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]); + i += 2; + } else { + return -1; + } + } else if (is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination + return i >= src_len && j < dst_len ? (int) j : -1; +} + +int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { + size_t i; + for (i = 0; i < buf_len; i++) { + if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) + return -1; + if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') || + (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n')) + return (int) i + 1; + } + return 0; +} + +static const char *skip(const char *s, const char *e, const char *d, + struct mg_str *v) { + v->ptr = s; + while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++; + v->len = (size_t) (s - v->ptr); + while (s < e && strchr(d, *s) != NULL) s++; + return s; +} + +struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { + size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); + for (i = 0; i < max && h->headers[i].name.len > 0; i++) { + struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; + if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v; + } + return NULL; +} + +void mg_http_parse_headers(const char *s, const char *end, + struct mg_http_header *h, int max_headers) { + int i; + for (i = 0; i < max_headers; i++) { + struct mg_str k, v, tmp; + const char *he = skip(s, end, "\n", &tmp); + s = skip(s, he, ": \r\n", &k); + s = skip(s, he, "\r\n", &v); + if (k.len == tmp.len) continue; + while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces + if (k.len == 0) break; + // LOG(LL_INFO, ("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr, + //(int) k.len, k.ptr, (int) v.len, v.ptr)); + h[i].name = k; + h[i].value = v; + } +} + +int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { + int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); + const char *end = s + req_len, *qs; + struct mg_str *cl; + + memset(hm, 0, sizeof(*hm)); + if (req_len <= 0) return req_len; + + hm->message.ptr = hm->head.ptr = s; + hm->body.ptr = end; + hm->head.len = (size_t) req_len; + hm->chunk.ptr = end; + hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite + + // Parse request line + s = skip(s, end, " ", &hm->method); + s = skip(s, end, " ", &hm->uri); + s = skip(s, end, "\r\n", &hm->proto); + + // Sanity check. Allow protocol/reason to be empty + if (hm->method.len == 0 || hm->uri.len == 0) return -1; + + // If URI contains '?' character, setup query string + if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) { + hm->query.ptr = qs + 1; + hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1)); + hm->uri.len = (size_t) (qs - hm->uri.ptr); + } + + mg_http_parse_headers(s, end, hm->headers, + sizeof(hm->headers) / sizeof(hm->headers[0])); + if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) { + hm->body.len = (size_t) mg_to64(*cl); + hm->message.len = (size_t) req_len + hm->body.len; + } + + // mg_http_parse() is used to parse both HTTP requests and HTTP + // responses. If HTTP response does not have Content-Length set, then + // body is read until socket is closed, i.e. body.len is infinite (~0). + // + // For HTTP requests though, according to + // http://tools.ietf.org/html/rfc7231#section-8.1.3, + // only POST and PUT methods have defined body semantics. + // Therefore, if Content-Length is not specified and methods are + // not one of PUT or POST, set body length to 0. + // + // So, if it is HTTP request, and Content-Length is not set, + // and method is not (PUT or POST) then reset body length to zero. + is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0; + if (hm->body.len == (size_t) ~0 && !is_response && + mg_vcasecmp(&hm->method, "PUT") != 0 && + mg_vcasecmp(&hm->method, "POST") != 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; + } + + // The 204 (No content) responses also have 0 body length + if (hm->body.len == (size_t) ~0 && is_response && + mg_vcasecmp(&hm->uri, "204") == 0) { + hm->body.len = 0; + hm->message.len = (size_t) req_len; + } + + return req_len; +} + +static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, + va_list ap) { + char mem[256], *buf = mem; + int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + mg_printf(c, "%X\r\n", len); + mg_send(c, buf, len > 0 ? (size_t) len : 0); + mg_send(c, "\r\n", 2); + if (buf != mem) free(buf); +} + +void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + mg_http_vprintf_chunk(c, fmt, ap); + va_end(ap); +} + +void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { + mg_printf(c, "%lX\r\n", (unsigned long) len); + mg_send(c, buf, len); + mg_send(c, "\r\n", 2); +} + +// clang-format off +static const char *mg_http_status_code_str(int status_code) { + switch (status_code) { + case 100: return "Continue"; + case 101: return "Switching Protocols"; + case 102: return "Processing"; + case 200: return "OK"; + case 201: return "Created"; + case 202: return "Accepted"; + case 203: return "Non-authoritative Information"; + case 204: return "No Content"; + case 205: return "Reset Content"; + case 206: return "Partial Content"; + case 207: return "Multi-Status"; + case 208: return "Already Reported"; + case 226: return "IM Used"; + case 300: return "Multiple Choices"; + case 301: return "Moved Permanently"; + case 302: return "Found"; + case 303: return "See Other"; + case 304: return "Not Modified"; + case 305: return "Use Proxy"; + case 307: return "Temporary Redirect"; + case 308: return "Permanent Redirect"; + case 400: return "Bad Request"; + case 401: return "Unauthorized"; + case 402: return "Payment Required"; + case 403: return "Forbidden"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + case 406: return "Not Acceptable"; + case 407: return "Proxy Authentication Required"; + case 408: return "Request Timeout"; + case 409: return "Conflict"; + case 410: return "Gone"; + case 411: return "Length Required"; + case 412: return "Precondition Failed"; + case 413: return "Payload Too Large"; + case 414: return "Request-URI Too Long"; + case 415: return "Unsupported Media Type"; + case 416: return "Requested Range Not Satisfiable"; + case 417: return "Expectation Failed"; + case 418: return "I'm a teapot"; + case 421: return "Misdirected Request"; + case 422: return "Unprocessable Entity"; + case 423: return "Locked"; + case 424: return "Failed Dependency"; + case 426: return "Upgrade Required"; + case 428: return "Precondition Required"; + case 429: return "Too Many Requests"; + case 431: return "Request Header Fields Too Large"; + case 444: return "Connection Closed Without Response"; + case 451: return "Unavailable For Legal Reasons"; + case 499: return "Client Closed Request"; + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP Version Not Supported"; + case 506: return "Variant Also Negotiates"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + case 599: return "Network Connect Timeout Error"; + default: return "OK"; + } +} +// clang-format on + +void mg_http_reply(struct mg_connection *c, int code, const char *headers, + const char *fmt, ...) { + char mem[256], *buf = mem; + va_list ap; + int len; + va_start(ap, fmt); + len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + va_end(ap); + mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: %d\r\n\r\n", code, + mg_http_status_code_str(code), headers == NULL ? "" : headers, len); + mg_send(c, buf, len > 0 ? (size_t) len : 0); + if (buf != mem) free(buf); +} + +static void http_cb(struct mg_connection *, int, void *, void *); +static void restore_http_cb(struct mg_connection *c) { + struct mg_fd *fd = (struct mg_fd *) c->pfn_data; + if (fd != NULL) fd->fs->close(fd); + c->pfn_data = NULL; + c->pfn = http_cb; +} + +char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { + snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) mtime, + (int64_t) size); + return buf; +} + +int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, + const char *dir) { + char offset[40] = "", name[200] = "", path[256]; + mg_http_get_var(&hm->query, "offset", offset, sizeof(offset)); + mg_http_get_var(&hm->query, "name", name, sizeof(name)); + if (name[0] == '\0') { + mg_http_reply(c, 400, "", "%s", "name required"); + return -1; + } else { + FILE *fp; + size_t oft = strtoul(offset, NULL, 0); + snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, name); + LOG(LL_DEBUG, + ("%p %d bytes @ %d [%s]", c->fd, (int) hm->body.len, (int) oft, name)); + if ((fp = fopen(path, oft == 0 ? "wb" : "ab")) == NULL) { + mg_http_reply(c, 400, "", "fopen(%s): %d", name, errno); + return -2; + } else { + fwrite(hm->body.ptr, 1, hm->body.len, fp); + fclose(fp); + mg_http_reply(c, 200, "", ""); + return (int) hm->body.len; + } + } +} + +static void static_cb(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { + struct mg_fd *fd = (struct mg_fd *) fn_data; + // Read to send IO buffer directly, avoid extra on-stack buffer + size_t n, max = 2 * MG_IO_SIZE; + if (c->send.size < max) mg_iobuf_resize(&c->send, max); + if (c->send.len >= c->send.size) return; // Rate limit + n = fd->fs->read(fd->fd, c->send.buf + c->send.len, + c->send.size - c->send.len); + if (n > 0) c->send.len += n; + if (c->send.len < c->send.size) restore_http_cb(c); + } else if (ev == MG_EV_CLOSE) { + restore_http_cb(c); + } + (void) ev_data; +} + +static struct mg_str guess_content_type(struct mg_str path, const char *extra) { + // clang-format off + struct mimeentry { struct mg_str extension, value; }; + #define MIME_ENTRY(a, b) {{a, sizeof(a) - 1 }, { b, sizeof(b) - 1 }} + // clang-format on + const struct mimeentry tab[] = { + MIME_ENTRY("html", "text/html; charset=utf-8"), + MIME_ENTRY("htm", "text/html; charset=utf-8"), + MIME_ENTRY("css", "text/css; charset=utf-8"), + MIME_ENTRY("js", "text/javascript; charset=utf-8"), + MIME_ENTRY("gif", "image/gif"), + MIME_ENTRY("png", "image/png"), + MIME_ENTRY("woff", "font/woff"), + MIME_ENTRY("ttf", "font/ttf"), + MIME_ENTRY("aac", "audio/aac"), + MIME_ENTRY("avi", "video/x-msvideo"), + MIME_ENTRY("azw", "application/vnd.amazon.ebook"), + MIME_ENTRY("bin", "application/octet-stream"), + MIME_ENTRY("bmp", "image/bmp"), + MIME_ENTRY("bz", "application/x-bzip"), + MIME_ENTRY("bz2", "application/x-bzip2"), + MIME_ENTRY("csv", "text/csv"), + MIME_ENTRY("doc", "application/msword"), + MIME_ENTRY("epub", "application/epub+zip"), + MIME_ENTRY("exe", "application/octet-stream"), + MIME_ENTRY("gz", "application/gzip"), + MIME_ENTRY("ico", "image/x-icon"), + MIME_ENTRY("json", "application/json"), + MIME_ENTRY("mid", "audio/mid"), + MIME_ENTRY("mjs", "text/javascript"), + MIME_ENTRY("mov", "video/quicktime"), + MIME_ENTRY("mp3", "audio/mpeg"), + MIME_ENTRY("mp4", "video/mp4"), + MIME_ENTRY("mpeg", "video/mpeg"), + MIME_ENTRY("mpg", "video/mpeg"), + MIME_ENTRY("ogg", "application/ogg"), + MIME_ENTRY("pdf", "application/pdf"), + MIME_ENTRY("rar", "application/rar"), + MIME_ENTRY("rtf", "application/rtf"), + MIME_ENTRY("shtml", "text/html; charset=utf-8"), + MIME_ENTRY("svg", "image/svg+xml"), + MIME_ENTRY("tar", "application/tar"), + MIME_ENTRY("tgz", "application/tar-gz"), + MIME_ENTRY("txt", "text/plain; charset=utf-8"), + MIME_ENTRY("wasm", "application/wasm"), + MIME_ENTRY("wav", "audio/wav"), + MIME_ENTRY("weba", "audio/webm"), + MIME_ENTRY("webm", "video/webm"), + MIME_ENTRY("webp", "image/webp"), + MIME_ENTRY("xls", "application/excel"), + MIME_ENTRY("xml", "application/xml"), + MIME_ENTRY("xsl", "application/xml"), + MIME_ENTRY("zip", "application/zip"), + MIME_ENTRY("3gp", "video/3gpp"), + MIME_ENTRY("7z", "application/x-7z-compressed"), + MIME_ENTRY("7z", "application/x-7z-compressed"), + {{0, 0}, {0, 0}}, + }; + size_t i = 0; + struct mg_str k, v, s = mg_str(extra); + + // Shrink path to its extension only + while (i < path.len && path.ptr[path.len - i - 1] != '.') i++; + path.ptr += path.len - i; + path.len = i; + + // Process user-provided mime type overrides, if any + while (mg_commalist(&s, &k, &v)) { + if (mg_strcmp(path, k) == 0) return v; + } + + // Process built-in mime types + for (i = 0; tab[i].extension.ptr != NULL; i++) { + if (mg_strcmp(path, tab[i].extension) == 0) return tab[i].value; + } + + return mg_str("text/plain; charset=utf-8"); +} + +static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { + size_t i, numparsed = 0; + LOG(LL_INFO, ("%.*s", (int) s->len, s->ptr)); + for (i = 0; i + 6 < s->len; i++) { + if (memcmp(&s->ptr[i], "bytes=", 6) == 0) { + struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + *a = mg_to64(p); + // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--; + if (p.len && p.ptr[0] == '-') p.ptr++, p.len--; + *b = mg_to64(p); + if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; + // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); + break; + } + } + return (int) numparsed; +} + +void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, + const char *path, struct mg_http_serve_opts *opts) { + char etag[64]; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_fd *fd = fs->open(path, MG_FS_READ); + size_t size = 0; + time_t mtime = 0; + struct mg_str *inm = NULL; + + if (fd == NULL || fs->stat(path, &size, &mtime) == 0) { + LOG(LL_DEBUG, ("404 [%s] %p", path, (void *) fd)); + mg_http_reply(c, 404, "", "%s", "Not found\n"); + fs->close(fd); + // NOTE: mg_http_etag() call should go first! + } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && + (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && + mg_vcasecmp(inm, etag) == 0) { + fs->close(fd); + mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); + } else { + int n, status = 200; + char range[100] = ""; + int64_t r1 = 0, r2 = 0, cl = (int64_t) size; + struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); + + // Handle Range header + struct mg_str *rh = mg_http_get_header(hm, "Range"); + if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) { + // If range is specified like "400-", set second limit to content len + if (n == 1) r2 = cl - 1; + if (r1 > r2 || r2 >= cl) { + status = 416; + cl = 0; + snprintf(range, sizeof(range), + "Content-Range: bytes */" MG_INT64_FMT "\r\n", (int64_t) size); + } else { + status = 206; + cl = r2 - r1 + 1; + snprintf(range, sizeof(range), + "Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT + "/" MG_INT64_FMT "\r\n", + r1, r1 + cl - 1, (int64_t) size); + fs->seek(fd->fd, (size_t) r1); + } + } + + mg_printf(c, + "HTTP/1.1 %d %s\r\nContent-Type: %.*s\r\n" + "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n", + status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, + etag, cl, range, opts->extra_headers ? opts->extra_headers : ""); + if (mg_vcasecmp(&hm->method, "HEAD") == 0) { + c->is_draining = 1; + fs->close(fd); + } else { + c->pfn = static_cb; + c->pfn_data = fd; + } + } +} + +struct printdirentrydata { + struct mg_connection *c; + struct mg_http_message *hm; + struct mg_http_serve_opts *opts; + const char *dir; +}; + +static void printdirentry(const char *name, void *userdata) { + struct printdirentrydata *d = (struct printdirentrydata *) userdata; + struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; + size_t size = 0; + time_t mtime = 0; + char path[MG_PATH_MAX], sz[64], mod[64]; + int flags, n = 0; + + // LOG(LL_DEBUG, ("[%s] [%s]", d->dir, name)); + if (snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) < 0) { + LOG(LL_ERROR, ("%s truncated", name)); + } else if ((flags = fs->stat(path, &size, &mtime)) == 0) { + LOG(LL_ERROR, ("%lu stat(%s): %d", d->c->id, path, errno)); + } else { + const char *slash = flags & MG_FS_DIR ? "/" : ""; + struct tm t; + if (flags & MG_FS_DIR) { + snprintf(sz, sizeof(sz), "%s", "[DIR]"); + } else if (size < 1024) { + snprintf(sz, sizeof(sz), "%d", (int) size); + } else if (size < 0x100000) { + snprintf(sz, sizeof(sz), "%.1fk", (double) size / 1024.0); + } else if (size < 0x40000000) { + snprintf(sz, sizeof(sz), "%.1fM", (double) size / 1048576); + } else { + snprintf(sz, sizeof(sz), "%.1fG", (double) size / 1073741824); + } + strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&mtime, &t)); + n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); + mg_printf(d->c, + " <tr><td><a href=\"%.*s%s\">%s%s</a></td>" + "<td>%s</td><td>%s</td></tr>\n", + n, path, slash, name, slash, mod, sz); + } +} + +static void listdir(struct mg_connection *c, struct mg_http_message *hm, + struct mg_http_serve_opts *opts, char *dir) { + static const char *sort_js_code = + "<script>function srt(tb, sc, so, d) {" + "var tr = Array.prototype.slice.call(tb.rows, 0)," + "tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc]," + "n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), " + "t1 = a.cells[2].getAttribute('name'), " + "t2 = b.cells[2].getAttribute('name'); " + "return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : " + "n1 ? parseInt(n2) - parseInt(n1) : " + "c1.textContent.trim().localeCompare(c2.textContent.trim())); });"; + static const char *sort_js_code2 = + "for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); " + "if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); " + "};" + "window.onload = function() {" + "var tb = document.getElementById('tb');" + "var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];" + "var sc = m[1], so = m[2]; document.onclick = function(ev) { " + "var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); " + "sc = c; ev.preventDefault();}};" + "srt(tb, sc, so, true);" + "}" + "</script>"; + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct printdirentrydata d = {c, hm, opts, dir}; + char tmp[10]; + size_t off, n; + + mg_printf(c, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "%s" + "Content-Length: \r\n\r\n", + opts->extra_headers == NULL ? "" : opts->extra_headers); + off = c->send.len; // Start of body + mg_printf(c, + "<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s" + "<style>th,td {text-align: left; padding-right: 1em; " + "font-family: monospace; }</style></head>" + "<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>" + "<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>" + "<a href=\"#\" rel=\"1\">Modified</a></th>" + "<th><a href=\"#\" rel=\"2\">Size</a></th></tr>" + "<tr><td colspan=\"3\"><hr></td></tr>" + "</thead>" + "<tbody id=\"tb\">\n", + (int) hm->uri.len, hm->uri.ptr, sort_js_code, sort_js_code2, + (int) hm->uri.len, hm->uri.ptr); + + fs->list(dir, printdirentry, &d); + mg_printf(c, + "</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>" + "</table><address>Mongoose v.%s</address></body></html>\n", + MG_VERSION); + n = (size_t) snprintf(tmp, sizeof(tmp), "%lu", + (unsigned long) (c->send.len - off)); + if (n > sizeof(tmp)) n = 0; + memcpy(c->send.buf + off - 10, tmp, n); // Set content length +} + +static void remove_double_dots(char *s) { + char *p = s; + while (*s != '\0') { + *p++ = *s++; + if (s[-1] == '/' || s[-1] == '\\') { + while (s[0] != '\0') { + if (s[0] == '/' || s[0] == '\\') { + s++; + } else if (s[0] == '.' && s[1] == '.') { + s += 2; + } else { + break; + } + } + } + } + *p = '\0'; +} + +// Resolve requested file into `path` and return its fs->stat() result +static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, + struct mg_fs *fs, struct mg_str url, struct mg_str dir, + char *path, size_t path_size) { + int flags = 0, tmp; + // Append URI to the root_dir, and sanitize it + size_t n = (size_t) snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr); + if (n > path_size) n = path_size; + path[path_size - 1] = '\0'; + if ((fs->stat(path, NULL, NULL) & MG_FS_DIR) == 0) { + mg_http_reply(c, 400, "", "Invalid web root [%.*s]\n", (int) dir.len, + dir.ptr); + } else { + if (n + 2 < path_size) path[n++] = '/', path[n] = '\0'; + mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n, + path_size - n, 0); + path[path_size - 1] = '\0'; // Double-check + remove_double_dots(path); + n = strlen(path); + LOG(LL_DEBUG, ("--> %s", path)); + while (n > 0 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes + flags = fs->stat(path, NULL, NULL); // Does it exist? + if (flags == 0) { + mg_http_reply(c, 404, "", "Not found\n"); // Does not exist, doh + } else if (flags & MG_FS_DIR) { + if (((snprintf(path + n, path_size - n, "/index.html") > 0 && + (tmp = fs->stat(path, NULL, NULL)) != 0) || + (snprintf(path + n, path_size - n, "/index.shtml") > 0 && + (tmp = fs->stat(path, NULL, NULL)) != 0))) { + flags = tmp; + } else { + path[n] = '\0'; // Remove appended index file name + } + } + } + return flags; +} + +static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, + struct mg_http_serve_opts *opts, char *path, + size_t path_size) { + struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; + struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p = {0, 0}; + while (mg_commalist(&s, &k, &v)) { + if (v.len == 0) v = k, k = mg_str("/"); + if (hm->uri.len < k.len) continue; + if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue; + u = k, p = v; + } + return uri_to_path2(c, hm, fs, u, p, path, path_size); +} + +void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, + struct mg_http_serve_opts *opts) { + char path[MG_PATH_MAX] = ""; + const char *sp = opts->ssi_pattern; + int flags = uri_to_path(c, hm, opts, path, sizeof(path)); + if (flags == 0) return; + LOG(LL_DEBUG, ("%.*s %s %d", (int) hm->uri.len, hm->uri.ptr, path, flags)); + if (flags & MG_FS_DIR) { + listdir(c, hm, opts, path); + } else if (sp != NULL && mg_globmatch(sp, strlen(sp), path, strlen(path))) { + mg_http_serve_ssi(c, opts->root_dir, path); + } else { + mg_http_serve_file(c, hm, path, opts); + } +} + +static bool mg_is_url_safe(int c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~'; +} + +size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) { + size_t i, n = 0; + for (i = 0; i < sl; i++) { + int c = *(unsigned char *) &s[i]; + if (n + 4 >= len) return 0; + if (mg_is_url_safe(c)) { + buf[n++] = s[i]; + } else { + buf[n++] = '%'; + mg_hex(&s[i], 1, &buf[n]); + n += 2; + } + } + return n; +} + +void mg_http_creds(struct mg_http_message *hm, char *user, size_t userlen, + char *pass, size_t passlen) { + struct mg_str *v = mg_http_get_header(hm, "Authorization"); + user[0] = pass[0] = '\0'; + if (v != NULL && v->len > 6 && memcmp(v->ptr, "Basic ", 6) == 0) { + char buf[256]; + int n = mg_base64_decode(v->ptr + 6, (int) v->len - 6, buf); + const char *p = (const char *) memchr(buf, ':', n > 0 ? (size_t) n : 0); + if (p != NULL) { + snprintf(user, userlen, "%.*s", (int) (p - buf), buf); + snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1); + } + } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) { + snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7); + } else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) { + struct mg_str t = mg_http_get_header_var(*v, mg_str_n("access_token", 12)); + if (t.len > 0) snprintf(pass, passlen, "%.*s", (int) t.len, t.ptr); + } else { + mg_http_get_var(&hm->query, "access_token", pass, passlen); + } +} + +static struct mg_str stripquotes(struct mg_str s) { + return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"' + ? mg_str_n(s.ptr + 1, s.len - 2) + : s; +} + +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { + size_t i; + for (i = 0; i + v.len + 2 < s.len; i++) { + if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) { + const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len]; + int q = p < x && *p == '"' ? 1 : 0; + while (p < x && (q ? p == b || *p != '"' : *p != ';' && *p != ' ')) p++; + // LOG(LL_INFO, ("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len, + // v.ptr, (int) (p - b), b)); + return stripquotes(mg_str_n(b, (size_t) (p - b + q))); + } + } + return mg_str_n(NULL, 0); +} + +bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { + return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len); +} + +static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) { + size_t i = 0, n; + while (i < len && buf[i] != '\r' && i != '\n') i++; + n = mg_unhexn((char *) buf, i); + while (i < len && (buf[i] == '\r' || i == '\n')) i++; + // LOG(LL_INFO, ("len %zu i %zu n %zu ", len, i, n)); + if (ll != NULL) *ll = i + 1; + if (i < len && i + n + 2 < len) return i + n + 3; + return 0; +} + +// Walk through all chunks in the chunked body. For each chunk, fire +// an MG_EV_HTTP_CHUNK event. +static void walkchunks(struct mg_connection *c, struct mg_http_message *hm, + size_t reqlen) { + size_t off = 0, bl, ll; + while (off + reqlen < c->recv.len) { + char *buf = (char *) &c->recv.buf[reqlen]; + size_t memo = c->recv.len; + size_t cl = get_chunk_length(&buf[off], memo - reqlen - off, &ll); + // LOG(LL_INFO, ("len %zu off %zu cl %zu ll %zu", len, off, cl, ll)); + if (cl == 0) break; + hm->chunk = mg_str_n(&buf[off + ll], cl < ll + 2 ? 0 : cl - ll - 2); + mg_call(c, MG_EV_HTTP_CHUNK, hm); + // Increase offset only if user has not deleted this chunk + if (memo == c->recv.len) off += cl; + if (cl <= 5) { + // Zero chunk - last one. Prepare body - cut off chunk lengths + off = bl = 0; + while (off + reqlen < c->recv.len) { + char *buf2 = (char *) &c->recv.buf[reqlen]; + size_t memo2 = c->recv.len; + size_t cl2 = get_chunk_length(&buf2[off], memo2 - reqlen - off, &ll); + size_t n = cl2 < ll + 2 ? 0 : cl2 - ll - 2; + memmove(buf2 + bl, buf2 + off + ll, n); + bl += n; + off += cl2; + if (cl2 <= 5) break; + } + // LOG(LL_INFO, ("BL->%d del %d off %d", (int) bl, (int) del, (int) off)); + c->recv.len -= off - bl; + // Set message length to indicate we've received + // everything, to fire MG_EV_HTTP_MSG + hm->message.len = bl + reqlen; + hm->body.len = bl; + break; + } + } +} + +static bool mg_is_chunked(struct mg_http_message *hm) { + struct mg_str needle = mg_str_n("chunked", 7); + struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding"); + return te != NULL && mg_strstr(*te, needle) != NULL; +} + +void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) { + struct mg_str ch = hm->chunk; + if (mg_is_chunked(hm)) { + ch.len += 4; // \r\n before and after the chunk + ch.ptr -= 2; + while (ch.ptr > hm->body.ptr && *ch.ptr != '\n') ch.ptr--, ch.len++; + } + { + const char *end = &ch.ptr[ch.len]; + size_t n = (size_t) (end - (char *) c->recv.buf); + if (c->recv.len > n) { + memmove((char *) ch.ptr, end, (size_t) (c->recv.len - n)); + } + // LOG(LL_INFO, ("DELETING CHUNK: %zu %zu %zu\n%.*s", c->recv.len, n, + // ch.len, (int) ch.len, ch.ptr)); + } + c->recv.len -= ch.len; +} + +static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { + if (ev == MG_EV_READ || ev == MG_EV_CLOSE) { + struct mg_http_message hm; + for (;;) { + int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); + bool is_chunked = n > 0 && mg_is_chunked(&hm); + if (ev == MG_EV_CLOSE) { + hm.message.len = c->recv.len; + hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); + } else if (is_chunked && n > 0) { + walkchunks(c, &hm, (size_t) n); + } + // LOG(LL_INFO, + //("---->%d %d\n%.*s", n, is_chunked, (int) c->recv.len, c->recv.buf)); + if (n < 0 && ev == MG_EV_READ) { + mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf); + break; + } else if (n > 0 && (size_t) c->recv.len >= hm.message.len) { + mg_call(c, MG_EV_HTTP_MSG, &hm); + mg_iobuf_del(&c->recv, 0, hm.message.len); + } else { + if (n > 0 && !is_chunked) { + hm.chunk = + mg_str_n((char *) &c->recv.buf[n], c->recv.len - (size_t) n); + mg_call(c, MG_EV_HTTP_CHUNK, &hm); + } + break; + } + } + } + (void) fnd; + (void) evd; +} + +struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; +} + +struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = http_cb; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/iobuf.c" +#endif + + + +#include <string.h> + +// Not using memset for zeroing memory, cause it can be dropped by compiler +// See https://github.com/cesanta/mongoose/pull/1265 +static void zeromem(volatile unsigned char *buf, size_t len) { + if (buf != NULL) { + while (len--) *buf++ = 0; + } +} + +int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { + int ok = 1; + if (new_size == 0) { + zeromem(io->buf, io->size); + free(io->buf); + io->buf = NULL; + io->len = io->size = 0; + } else if (new_size != io->size) { + // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the + // porting to some obscure platforms like FreeRTOS + void *p = calloc(1, new_size); + if (p != NULL) { + size_t len = new_size < io->len ? new_size : io->len; + if (len > 0) memcpy(p, io->buf, len); + zeromem(io->buf, io->size); + free(io->buf); + io->buf = (unsigned char *) p; + io->size = new_size; + } else { + ok = 0; + LOG(LL_ERROR, + ("%lu->%lu", (unsigned long) io->size, (unsigned long) new_size)); + } + } + return ok; +} + +int mg_iobuf_init(struct mg_iobuf *io, size_t size) { + return mg_iobuf_resize(io, size); +} + +size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, + size_t len, size_t chunk_size) { + size_t new_size = io->len + len; + if (new_size > io->size) { + new_size += chunk_size; // Make sure that io->size + new_size -= new_size % chunk_size; // is aligned by chunk_size boundary + mg_iobuf_resize(io, new_size); // Attempt to realloc + if (new_size != io->size) len = 0; // Realloc failure, append nothing + } + if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs); + if (buf != NULL) memmove(io->buf + ofs, buf, len); + if (ofs > io->len) io->len += ofs - io->len; + io->len += len; + return len; +} + +size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) { + if (ofs > io->len) ofs = io->len; + if (ofs + len > io->len) len = io->len - ofs; + memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); + zeromem(io->buf + io->len - len, len); + io->len -= len; + return len; +} + +void mg_iobuf_free(struct mg_iobuf *io) { + mg_iobuf_resize(io, 0); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/log.c" +#endif + + + +#if MG_ENABLE_LOG +static void mg_log_stdout(const void *buf, size_t len, void *userdata) { + (void) userdata; + fwrite(buf, 1, len, stdout); +} + +static const char *s_spec = "2"; +static void (*s_fn)(const void *, size_t, void *) = mg_log_stdout; +static void *s_fn_param = NULL; + +void mg_log_set(const char *spec) { + LOG(LL_DEBUG, ("Setting log level to %s", spec)); + s_spec = spec; +} + +bool mg_log_prefix(int level, const char *file, int line, const char *fname) { + // static unsigned long seq; + int max = LL_INFO; + struct mg_str k, v, s = mg_str(s_spec); + const char *p = strrchr(file, '/'); + + if (s_fn == NULL) return false; + + if (p == NULL) p = strrchr(file, '\\'); + p = p == NULL ? file : p + 1; + + while (mg_commalist(&s, &k, &v)) { + if (v.len == 0) max = atoi(k.ptr); + if (v.len > 0 && strncmp(p, k.ptr, k.len) == 0) max = atoi(v.ptr); + } + + if (level <= max) { + char timebuf[21], buf[50] = ""; + time_t t = time(NULL); + struct tm tmp, *tm = gmtime_r(&t, &tmp); + int n; + (void) tmp; + strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm); + n = snprintf(buf, sizeof(buf), "%s %d %s:%d:%s", timebuf, level, p, line, + fname); + if (n < 0 || n > (int) sizeof(buf) - 2) n = sizeof(buf) - 2; + while (n < (int) sizeof(buf) - 1) buf[n++] = ' '; + s_fn(buf, sizeof(buf) - 1, s_fn_param); + return true; + } else { + return false; + } +} + +void mg_log(const char *fmt, ...) { + char mem[256], *buf = mem; + va_list ap; + int len = 0; + va_start(ap, fmt); + len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + va_end(ap); + s_fn(buf, len > 0 ? (size_t) len : 0, s_fn_param); + s_fn("\n", 1, s_fn_param); + if (buf != mem) free(buf); +} + +void mg_log_set_callback(void (*fn)(const void *, size_t, void *), void *fnd) { + s_fn = fn; + s_fn_param = fnd; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/md5.c" +#endif +#include <string.h> + + +#if MG_ENABLE_MD5 +#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER) +#define BYTE_ORDER __BYTE_ORDER +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif /* LITTLE_ENDIAN */ +#ifndef BIG_ENDIAN +#define BIG_ENDIAN __LITTLE_ENDIAN +#endif /* BIG_ENDIAN */ +#endif /* BYTE_ORDER */ + +static void mg_byte_reverse(unsigned char *buf, unsigned longs) { +/* Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN */ +#if BYTE_ORDER == BIG_ENDIAN + do { + uint32_t t = (uint32_t)((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +#else + (void) buf; + (void) longs; +#endif +} + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +#define MD5STEP(f, w, x, y, z, data, s) \ + (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x) + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void mg_md5_init(mg_md5_ctx *ctx) { + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) { + uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +void mg_md5_update(mg_md5_ctx *ctx, const unsigned char *buf, size_t len) { + uint32_t t; + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; + ctx->bits[1] += (uint32_t) len >> 29; + + t = (t >> 3) & 0x3f; + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + memcpy(ctx->in, buf, len); +} + +void mg_md5_final(mg_md5_ctx *ctx, unsigned char digest[16]) { + unsigned count; + unsigned char *p; + uint32_t *a; + + count = (ctx->bits[0] >> 3) & 0x3F; + + p = ctx->in + count; + *p++ = 0x80; + count = 64 - 1 - count; + if (count < 8) { + memset(p, 0, count); + mg_byte_reverse(ctx->in, 16); + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + memset(ctx->in, 0, 56); + } else { + memset(p, 0, count - 8); + } + mg_byte_reverse(ctx->in, 14); + + a = (uint32_t *) ctx->in; + a[14] = ctx->bits[0]; + a[15] = ctx->bits[1]; + + mg_md5_transform(ctx->buf, (uint32_t *) ctx->in); + mg_byte_reverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset((char *) ctx, 0, sizeof(*ctx)); +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/mqtt.c" +#endif + + + + + + + + + +#define MQTT_CLEAN_SESSION 0x02 +#define MQTT_HAS_WILL 0x04 +#define MQTT_WILL_RETAIN 0x20 +#define MQTT_HAS_PASSWORD 0x40 +#define MQTT_HAS_USER_NAME 0x80 +#define MQTT_GET_WILL_QOS(flags) (((flags) &0x18) >> 3) +#define MQTT_SET_WILL_QOS(flags, qos) (flags) = ((flags) & ~0x18) | ((qos) << 3) + +enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED }; + +void mg_mqtt_send_header(struct mg_connection *c, uint8_t cmd, uint8_t flags, + uint32_t len) { + uint8_t buf[1 + sizeof(len)], *vlen = &buf[1]; + buf[0] = (uint8_t) ((cmd << 4) | flags); + do { + *vlen = len % 0x80; + len /= 0x80; + if (len > 0) *vlen |= 0x80; + vlen++; + } while (len > 0 && vlen < &buf[sizeof(buf)]); + mg_send(c, buf, (size_t) (vlen - buf)); +} + +static void mg_send_u16(struct mg_connection *c, uint16_t value) { + mg_send(c, &value, sizeof(value)); +} + +void mg_mqtt_login(struct mg_connection *c, const char *url, + struct mg_mqtt_opts *opts) { + uint32_t total_len = 7 + 1 + 2 + 2; + uint16_t flags = (uint16_t) (((uint16_t) opts->qos & 3) << 3); + struct mg_str user = mg_url_user(url); + struct mg_str pass = mg_url_pass(url); + + if (user.len > 0) { + total_len += 2 + (uint32_t) user.len; + flags |= MQTT_HAS_USER_NAME; + } + if (pass.len > 0) { + total_len += 2 + (uint32_t) pass.len; + flags |= MQTT_HAS_PASSWORD; + } + if (opts->will_topic.len > 0 && opts->will_message.len > 0) { + total_len += + 4 + (uint32_t) opts->will_topic.len + (uint32_t) opts->will_message.len; + flags |= MQTT_HAS_WILL; + } + if (opts->clean || opts->client_id.len == 0) flags |= MQTT_CLEAN_SESSION; + if (opts->will_retain) flags |= MQTT_WILL_RETAIN; + total_len += (uint32_t) opts->client_id.len; + + mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len); + mg_send(c, "\00\04MQTT\04", 7); + mg_send(c, &flags, 1); + // keepalive == 0 means "do not disconnect us!" + mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); + mg_send_u16(c, mg_htons((uint16_t) opts->client_id.len)); + mg_send(c, opts->client_id.ptr, opts->client_id.len); + if (flags & MQTT_HAS_WILL) { + mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len)); + mg_send(c, opts->will_topic.ptr, opts->will_topic.len); + mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len)); + mg_send(c, opts->will_message.ptr, opts->will_message.len); + } + if (user.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) user.len)); + mg_send(c, user.ptr, user.len); + } + if (pass.len > 0) { + mg_send_u16(c, mg_htons((uint16_t) pass.len)); + mg_send(c, pass.ptr, pass.len); + } +} + +void mg_mqtt_pub(struct mg_connection *c, struct mg_str *topic, + struct mg_str *data, int qos, bool retain) { + uint8_t flags = (uint8_t) (((qos & 3) << 1) | (retain ? 1 : 0)); + uint32_t total_len = 2 + (uint32_t) topic->len + (uint32_t) data->len; + LOG(LL_DEBUG, ("%lu [%.*s] -> [%.*s]", c->id, (int) topic->len, + (char *) topic->ptr, (int) data->len, (char *) data->ptr)); + if (MQTT_GET_QOS(flags) > 0) total_len += 2; + mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, total_len); + mg_send_u16(c, mg_htons((uint16_t) topic->len)); + mg_send(c, topic->ptr, topic->len); + if (MQTT_GET_QOS(flags) > 0) { + static uint16_t s_id; + if (++s_id == 0) s_id++; + mg_send_u16(c, mg_htons(s_id)); + } + mg_send(c, data->ptr, data->len); +} + +void mg_mqtt_sub(struct mg_connection *c, struct mg_str *topic, int qos) { + static uint16_t s_id; + uint8_t qos_ = qos & 3; + uint32_t total_len = 2 + (uint32_t) topic->len + 2 + 1; + mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, (uint8_t) MQTT_QOS(qos_), + total_len); + if (++s_id == 0) ++s_id; + mg_send_u16(c, mg_htons(s_id)); + mg_send_u16(c, mg_htons((uint16_t) topic->len)); + mg_send(c, topic->ptr, topic->len); + mg_send(c, &qos_, sizeof(qos_)); +} + +int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m) { + uint8_t lc = 0, *p, *end; + uint32_t n = 0, len_len = 0; + + memset(m, 0, sizeof(*m)); + m->dgram.ptr = (char *) buf; + if (len < 2) return MQTT_INCOMPLETE; + m->cmd = (uint8_t) (buf[0] >> 4); + m->qos = (buf[0] >> 1) & 3; + + n = len_len = 0; + p = (uint8_t *) buf + 1; + while ((size_t) (p - buf) < len) { + lc = *((uint8_t *) p++); + n += (uint32_t) ((lc & 0x7f) << 7 * len_len); + len_len++; + if (!(lc & 0x80)) break; + if (len_len >= 4) return MQTT_MALFORMED; + } + end = p + n; + if (lc & 0x80 || end > buf + len) return MQTT_INCOMPLETE; + m->dgram.len = (size_t) (end - buf); + + switch (m->cmd) { + case MQTT_CMD_CONNACK: + if (end - p < 2) return MQTT_MALFORMED; + m->ack = p[1]; + break; + case MQTT_CMD_PUBACK: + case MQTT_CMD_PUBREC: + case MQTT_CMD_PUBREL: + case MQTT_CMD_PUBCOMP: + case MQTT_CMD_SUBACK: + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + break; + case MQTT_CMD_SUBSCRIBE: { + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + break; + } + case MQTT_CMD_PUBLISH: { + if (p + 2 > end) return MQTT_MALFORMED; + m->topic.len = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + m->topic.ptr = (char *) p + 2; + p += 2 + m->topic.len; + if (p > end) return MQTT_MALFORMED; + if (m->qos > 0) { + if (p + 2 > end) return MQTT_MALFORMED; + m->id = (uint16_t) ((((uint16_t) p[0]) << 8) | p[1]); + p += 2; + } + if (p > end) return MQTT_MALFORMED; + m->data.ptr = (char *) p; + m->data.len = (size_t) (end - p); + break; + } + default: + break; + } + return MQTT_OK; +} + +static size_t mg_mqtt_next_topic(struct mg_mqtt_message *msg, + struct mg_str *topic, uint8_t *qos, + size_t pos) { + unsigned char *buf = (unsigned char *) msg->dgram.ptr + pos; + size_t new_pos; + if (pos >= msg->dgram.len) return 0; + + topic->len = (size_t) (((unsigned) buf[0]) << 8 | buf[1]); + topic->ptr = (char *) buf + 2; + new_pos = pos + 2 + topic->len + (qos == NULL ? 0 : 1); + if ((size_t) new_pos > msg->dgram.len) return 0; + if (qos != NULL) *qos = buf[2 + topic->len]; + return new_pos; +} + +size_t mg_mqtt_next_sub(struct mg_mqtt_message *msg, struct mg_str *topic, + uint8_t *qos, size_t pos) { + uint8_t tmp; + return mg_mqtt_next_topic(msg, topic, qos == NULL ? &tmp : qos, pos); +} + +size_t mg_mqtt_next_unsub(struct mg_mqtt_message *msg, struct mg_str *topic, + size_t pos) { + return mg_mqtt_next_topic(msg, topic, NULL, pos); +} + +static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + if (ev == MG_EV_READ) { + for (;;) { + struct mg_mqtt_message mm; + int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, &mm); + if (rc == MQTT_MALFORMED) { + LOG(LL_ERROR, ("%lu MQTT malformed message", c->id)); + c->is_closing = 1; + break; + } else if (rc == MQTT_OK) { + LOG(LL_VERBOSE_DEBUG, + ("%p MQTT CMD %d len %d [%.*s]", c->fd, mm.cmd, (int) mm.dgram.len, + (int) mm.data.len, mm.data.ptr)); + switch (mm.cmd) { + case MQTT_CMD_CONNACK: + mg_call(c, MG_EV_MQTT_OPEN, &mm.ack); + if (mm.ack == 0) { + LOG(LL_INFO, ("%lu Connected", c->id)); + } else { + LOG(LL_ERROR, ("%lu MQTT auth failed, code %d", c->id, mm.ack)); + c->is_closing = 1; + } + break; + case MQTT_CMD_PUBLISH: { + LOG(LL_DEBUG, ("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, + mm.topic.ptr, (int) mm.data.len, mm.data.ptr)); + mg_call(c, MG_EV_MQTT_MSG, &mm); + break; + } + } + mg_call(c, MG_EV_MQTT_CMD, &mm); + mg_iobuf_del(&c->recv, 0, mm.dgram.len); + } else { + break; + } + } + } + (void) ev_data; + (void) fn_data; +} + +void mg_mqtt_ping(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGREQ, 0, 0); +} + +void mg_mqtt_pong(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_PINGRESP, 0, 0); +} + +void mg_mqtt_disconnect(struct mg_connection *nc) { + mg_mqtt_send_header(nc, MQTT_CMD_DISCONNECT, 0, 0); +} + +struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url, + struct mg_mqtt_opts *opts, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) { + struct mg_mqtt_opts empty; + memset(&empty, 0, sizeof(empty)); + mg_mqtt_login(c, url, opts == NULL ? &empty : opts); + c->pfn = mqtt_cb; + } + return c; +} + +struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); + if (c != NULL) c->pfn = mqtt_cb, c->pfn_data = mgr; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/net.c" +#endif + + + + +int mg_vprintf(struct mg_connection *c, const char *fmt, va_list ap) { + char mem[256], *buf = mem; + int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); + len = mg_send(c, buf, len > 0 ? (size_t) len : 0); + if (buf != mem) free(buf); + return len; +} + +int mg_printf(struct mg_connection *c, const char *fmt, ...) { + int len = 0; + va_list ap; + va_start(ap, fmt); + len = mg_vprintf(c, fmt, ap); + va_end(ap); + return len; +} + +char *mg_straddr(struct mg_connection *c, char *buf, size_t len) { + char tmp[100]; + const char *fmt = c->peer.is_ip6 ? "[%s]:%d" : "%s:%d"; + mg_ntoa(&c->peer, tmp, sizeof(tmp)); + snprintf(buf, len, fmt, tmp, (int) mg_ntohs(c->peer.port)); + return buf; +} + +char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len) { + if (addr->is_ip6) { + uint16_t *p = (uint16_t *) addr->ip6; + snprintf(buf, len, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]), + mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]), mg_htons(p[4]), + mg_htons(p[5]), mg_htons(p[6]), mg_htons(p[7])); + } else { + uint8_t p[4]; + memcpy(p, &addr->ip, sizeof(p)); + snprintf(buf, len, "%d.%d.%d.%d", (int) p[0], (int) p[1], (int) p[2], + (int) p[3]); + } + return buf; +} + +static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { + if (mg_vcasecmp(&str, "localhost") != 0) return false; + addr->ip = mg_htonl(0x7f000001); + addr->is_ip6 = false; + return true; +} + +static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { + uint8_t data[4] = {0, 0, 0, 0}; + size_t i, num_dots = 0; + for (i = 0; i < str.len; i++) { + if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { + int octet = data[num_dots] * 10 + (str.ptr[i] - '0'); + if (octet > 255) return false; + data[num_dots] = (uint8_t) octet; + } else if (str.ptr[i] == '.') { + if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; + num_dots++; + } else { + return false; + } + } + if (num_dots != 3 || str.ptr[i - 1] == '.') return false; + memcpy(&addr->ip, data, sizeof(data)); + addr->is_ip6 = false; + return true; +} + +static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { + int i; + if (str.len < 14) return false; + if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false; + for (i = 2; i < 6; i++) { + if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false; + } + if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false; + memset(addr->ip6, 0, sizeof(addr->ip6)); + addr->ip6[10] = addr->ip6[11] = 255; + memcpy(&addr->ip6[12], &addr->ip, 4); + addr->is_ip6 = true; + return true; +} + +static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { + size_t i, j = 0, n = 0, dc = 42; + if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2; + if (mg_v4mapped(str, addr)) return true; + for (i = 0; i < str.len; i++) { + if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') || + (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') || + (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) { + unsigned long val; + if (i > j + 3) return false; + // LOG(LL_DEBUG, ("%zu %zu [%.*s]", i, j, (int) (i - j + 1), + // &str.ptr[j])); + val = mg_unhexn(&str.ptr[j], i - j + 1); + addr->ip6[n] = (uint8_t) ((val >> 8) & 255); + addr->ip6[n + 1] = (uint8_t) (val & 255); + } else if (str.ptr[i] == ':') { + j = i + 1; + if (i > 0 && str.ptr[i - 1] == ':') { + dc = n; // Double colon + if (i > 1 && str.ptr[i - 2] == ':') return false; + } else if (i > 0) { + n += 2; + } + if (n > 14) return false; + addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing :: + } else { + return false; + } + } + if (n < 14 && dc == 42) return false; + if (n < 14) { + memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2); + memset(&addr->ip6[dc], 0, 14 - n); + } + addr->is_ip6 = true; + return true; +} + +bool mg_aton(struct mg_str str, struct mg_addr *addr) { + // LOG(LL_INFO, ("[%.*s]", (int) str.len, str.ptr)); + return mg_atonl(str, addr) || mg_aton4(str, addr) || mg_aton6(str, addr); +} + +void mg_mgr_free(struct mg_mgr *mgr) { + struct mg_connection *c; + for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; + mg_mgr_poll(mgr, 0); +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + FreeRTOS_DeleteSocketSet(mgr->ss); +#endif + LOG(LL_INFO, ("All connections closed")); +} + +void mg_mgr_init(struct mg_mgr *mgr) { + memset(mgr, 0, sizeof(*mgr)); +#if defined(_WIN32) && MG_ENABLE_WINSOCK + // clang-format off + { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } + // clang-format on +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP + mgr->ss = FreeRTOS_CreateSocketSet(); +#elif defined(__unix) || defined(__unix__) || defined(__APPLE__) + // Ignore SIGPIPE signal, so if client cancels the request, it + // won't kill the whole process. + signal(SIGPIPE, SIG_IGN); +#endif + mgr->dnstimeout = 3000; + mgr->dns4.url = "udp://8.8.8.8:53"; + mgr->dns6.url = "udp://[2001:4860:4860::8888]:53"; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sha1.c" +#endif +/* Copyright(c) By Steve Reid <steve@edmweb.com> */ +/* 100% Public Domain */ + +#include <string.h> + +/* + * clang with std=-c99 uses __LITTLE_ENDIAN, by default + * while for ex, RTOS gcc - LITTLE_ENDIAN, by default + * it depends on __USE_BSD, but let's have everything + */ +#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER) +#define BYTE_ORDER __BYTE_ORDER +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif /* LITTLE_ENDIAN */ +#ifndef BIG_ENDIAN +#define BIG_ENDIAN __LITTLE_ENDIAN +#endif /* BIG_ENDIAN */ +#endif /* BYTE_ORDER */ + +union char64long16 { + unsigned char c[64]; + uint32_t l[16]; +}; + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +static uint32_t blk0(union char64long16 *block, int i) { +/* Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN */ +#if BYTE_ORDER == LITTLE_ENDIAN + block->l[i] = + (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF); +#endif + return block->l[i]; +} + +/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */ +#undef blk +#undef R0 +#undef R1 +#undef R2 +#undef R3 +#undef R4 + +#define blk(i) \ + (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \ + block->l[(i + 2) & 15] ^ block->l[i & 15], \ + 1)) +#define R0(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R1(v, w, x, y, z, i) \ + z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ + w = rol(w, 30); +#define R2(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ + w = rol(w, 30); +#define R3(v, w, x, y, z, i) \ + z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ + w = rol(w, 30); +#define R4(v, w, x, y, z, i) \ + z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ + w = rol(w, 30); + +void mg_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) { + uint32_t a, b, c, d, e; + union char64long16 block[1]; + + memcpy(block, buffer, 64); + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Erase working structures. The order of operations is important, + * used to ensure that compiler doesn't optimize those out. */ + memset(block, 0, sizeof(block)); + a = b = c = d = e = 0; + (void) a; + (void) b; + (void) c; + (void) d; + (void) e; +} + +void mg_sha1_init(mg_sha1_ctx *context) { + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data, + size_t len) { + size_t i, j; + + j = context->count[0]; + if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++; + context->count[1] += (uint32_t)(len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64 - j)); + mg_sha1_transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) { + mg_sha1_transform(context->state, &data[i]); + } + j = 0; + } else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) { + unsigned i; + unsigned char finalcount[8], c; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> + ((3 - (i & 3)) * 8)) & + 255); + } + c = 0200; + mg_sha1_update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + mg_sha1_update(context, &c, 1); + } + mg_sha1_update(context, finalcount, 8); + for (i = 0; i < 20; i++) { + digest[i] = + (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +void mg_hmac_sha1(const unsigned char *key, size_t keylen, + const unsigned char *data, size_t datalen, + unsigned char out[20]) { + mg_sha1_ctx ctx; + unsigned char buf1[64], buf2[64], tmp_key[20], i; + + if (keylen > sizeof(buf1)) { + mg_sha1_init(&ctx); + mg_sha1_update(&ctx, key, keylen); + mg_sha1_final(tmp_key, &ctx); + key = tmp_key; + keylen = sizeof(tmp_key); + } + + memset(buf1, 0, sizeof(buf1)); + memset(buf2, 0, sizeof(buf2)); + memcpy(buf1, key, keylen); + memcpy(buf2, key, keylen); + + for (i = 0; i < sizeof(buf1); i++) { + buf1[i] ^= 0x36; + buf2[i] ^= 0x5c; + } + + mg_sha1_init(&ctx); + mg_sha1_update(&ctx, buf1, sizeof(buf1)); + mg_sha1_update(&ctx, data, datalen); + mg_sha1_final(out, &ctx); + + mg_sha1_init(&ctx); + mg_sha1_update(&ctx, buf2, sizeof(buf2)); + mg_sha1_update(&ctx, out, 20); + mg_sha1_final(out, &ctx); +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sntp.c" +#endif + + + + + + +#define SNTP_INTERVAL_SEC (3600) +#define SNTP_TIME_OFFSET 2208988800UL + +static unsigned long s_sntmp_next; + +int mg_sntp_parse(const unsigned char *buf, size_t len, struct timeval *tv) { + int mode = len > 0 ? buf[0] & 7 : 0, res = -1; + if (len < 48) { + LOG(LL_ERROR, ("%s", "corrupt packet")); + } else if ((buf[0] & 0x38) >> 3 != 4) { + LOG(LL_ERROR, ("%s", "wrong version")); + } else if (mode != 4 && mode != 5) { + LOG(LL_ERROR, ("%s", "not a server reply")); + } else if (buf[1] == 0) { + LOG(LL_ERROR, ("%s", "server sent a kiss of death")); + } else { + uint32_t *data = (uint32_t *) &buf[40]; + tv->tv_sec = (time_t) (mg_ntohl(data[0]) - SNTP_TIME_OFFSET); + tv->tv_usec = (suseconds_t) mg_ntohl(data[1]); + s_sntmp_next = (unsigned long) (tv->tv_sec + SNTP_INTERVAL_SEC); + res = 0; + } + return res; +} + +static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { + if (ev == MG_EV_READ) { + struct timeval tv = {0, 0}; + if (mg_sntp_parse(c->recv.buf, c->recv.len, &tv) == 0) { + mg_call(c, MG_EV_SNTP_TIME, &tv); + LOG(LL_DEBUG, ("%u.%u, next at %lu", (unsigned) tv.tv_sec, + (unsigned) tv.tv_usec, s_sntmp_next)); + } + c->recv.len = 0; // Clear receive buffer + } else if (ev == MG_EV_CONNECT) { + mg_sntp_send(c, (unsigned long) time(NULL)); + } else if (ev == MG_EV_CLOSE) { + } + (void) fnd; + (void) evd; +} + +void mg_sntp_send(struct mg_connection *c, unsigned long utc) { + if (c->is_resolving) { + LOG(LL_ERROR, ("%lu wait until resolved", c->id)); + } else if (utc > s_sntmp_next) { + uint8_t buf[48] = {0}; + s_sntmp_next = utc + SNTP_INTERVAL_SEC; + buf[0] = (3 << 6) | (4 << 3) | 3; + mg_send(c, buf, sizeof(buf)); + LOG(LL_DEBUG, + ("%p request sent, ct %lu, next at %lu", c->fd, utc, s_sntmp_next)); + } +} + +struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fnd) { + struct mg_connection *c = NULL; + if (url == NULL) url = "udp://time.google.com:123"; + if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb; + return c; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/sock.c" +#endif + + + + + + + + + + + +#if MG_ENABLE_SOCKET +#if defined(_WIN32) && MG_ENABLE_WINSOCK +#define MG_SOCK_ERRNO WSAGetLastError() +#ifndef SO_EXCLUSIVEADDRUSE +#define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR)) +#endif +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP +#define MG_SOCK_ERRNO errno +typedef Socket_t SOCKET; +#define INVALID_SOCKET FREERTOS_INVALID_SOCKET +#else +#define MG_SOCK_ERRNO errno +#ifndef closesocket +#define closesocket(x) close(x) +#endif +#define INVALID_SOCKET (-1) +typedef int SOCKET; +#endif + +#define FD(c_) ((SOCKET) (size_t) (c_)->fd) +#define S2PTR(s_) ((void *) (size_t) (s_)) + +#ifndef MSG_NONBLOCKING +#define MSG_NONBLOCKING 0 +#endif + +#ifndef AF_INET6 +#define AF_INET6 10 +#endif + +union usa { + struct sockaddr sa; + struct sockaddr_in sin; +#if MG_ENABLE_IPV6 + struct sockaddr_in6 sin6; +#endif +}; + +static socklen_t tousa(struct mg_addr *a, union usa *usa) { + socklen_t len = sizeof(usa->sin); + memset(usa, 0, sizeof(*usa)); + usa->sin.sin_family = AF_INET; + usa->sin.sin_port = a->port; + *(uint32_t *) &usa->sin.sin_addr = a->ip; +#if MG_ENABLE_IPV6 + if (a->is_ip6) { + usa->sin.sin_family = AF_INET6; + usa->sin6.sin6_port = a->port; + memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6)); + len = sizeof(usa->sin6); + } +#endif + return len; +} + +static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) { + a->is_ip6 = is_ip6; + a->port = usa->sin.sin_port; + memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip)); +#if MG_ENABLE_IPV6 + if (is_ip6) { + memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6)); + a->port = usa->sin6.sin6_port; + } +#endif +} + +static bool mg_sock_would_block(void) { + int err = MG_SOCK_ERRNO; + return err == EINPROGRESS || err == EWOULDBLOCK +#ifndef WINCE + || err == EAGAIN || err == EINTR +#endif +#if defined(_WIN32) && MG_ENABLE_WINSOCK + || err == WSAEINTR || err == WSAEWOULDBLOCK +#endif + ; +} + +static struct mg_connection *alloc_conn(struct mg_mgr *mgr, bool is_client, + SOCKET fd) { + struct mg_connection *c = (struct mg_connection *) calloc(1, sizeof(*c)); + if (c != NULL) { + c->is_client = is_client; + c->fd = S2PTR(fd); + c->mgr = mgr; + c->id = ++mgr->nextid; + } + return c; +} + +static long mg_sock_send(struct mg_connection *c, const void *buf, size_t len) { + long n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING); + return n == 0 ? -1 : n < 0 && mg_sock_would_block() ? 0 : n; +} + +bool mg_send(struct mg_connection *c, const void *buf, size_t len) { + return c->is_udp ? mg_sock_send(c, buf, len) > 0 + : mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE); +} + +static void mg_set_non_blocking_mode(SOCKET fd) { +#if defined(_WIN32) && MG_ENABLE_WINSOCK + unsigned long on = 1; + ioctlsocket(fd, FIONBIO, &on); +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP + const BaseType_t off = 0; + setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)); + setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)); +#elif MG_ARCH == MG_ARCH_FREERTOS_LWIP + lwip_fcntl(fd, F_SETFL, O_NONBLOCK); +#elif MG_ARCH == MG_ARCH_AZURERTOS + fcntl(fd, F_SETFL, O_NONBLOCK); +#else + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK, FD_CLOEXEC); +#endif +} + +SOCKET mg_open_listener(const char *url, struct mg_addr *addr) { + SOCKET fd = INVALID_SOCKET; + int s_err = 0; // Memoized socket error, in case closesocket() overrides it + memset(addr, 0, sizeof(*addr)); + addr->port = mg_htons(mg_url_port(url)); + if (!mg_aton(mg_url_host(url), addr)) { + LOG(LL_ERROR, ("invalid listening URL: %s", url)); + } else { + union usa usa; + socklen_t slen = tousa(addr, &usa); + int on = 1, af = addr->is_ip6 ? AF_INET6 : AF_INET; + int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM; + int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; + (void) on; + + if ((fd = socket(af, type, proto)) != INVALID_SOCKET && +#if (!defined(_WIN32) || !defined(SO_EXCLUSIVEADDRUSE)) && \ + (!defined(LWIP_SOCKET) || (defined(LWIP_SOCKET) && SO_REUSE == 1)) + // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of + // SO_REUSEADDR on UNIX and Windows is different. On Windows, + // SO_REUSEADDR allows to bind a socket to a port without error even + // if the port is already open by another program. This is not the + // behavior SO_REUSEADDR was designed for, and leads to hard-to-track + // failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows + // unless SO_EXCLUSIVEADDRUSE is supported and set on a socket. + // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by + // defining + // SO_REUSE (in lwipopts.h), otherwise the code below will compile + // but won't work! (setsockopt will return EINVAL) + !setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) && +#endif +#if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE) + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" + //! setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &on, sizeof(on)) + //! && + !setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on, + sizeof(on)) && +#endif + bind(fd, &usa.sa, slen) == 0 && + // NOTE(lsm): FreeRTOS uses backlog value as a connection limit + (type == SOCK_DGRAM || listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0)) { + // In case port was set to 0, get the real port number + if (getsockname(fd, &usa.sa, &slen) == 0) { + addr->port = usa.sin.sin_port; +#if MG_ENABLE_IPV6 + if (addr->is_ip6) addr->port = usa.sin6.sin6_port; +#endif + } + mg_set_non_blocking_mode(fd); + } else if (fd != INVALID_SOCKET) { + s_err = MG_SOCK_ERRNO; + closesocket(fd); + fd = INVALID_SOCKET; + } + } + if (fd == INVALID_SOCKET) { + if (s_err == 0) s_err = MG_SOCK_ERRNO; + LOG(LL_ERROR, ("Failed to listen on %s, errno %d", url, s_err)); + } + + return fd; +} + +static long mg_sock_recv(struct mg_connection *c, void *buf, size_t len) { + long n = 0; + if (c->is_udp) { + union usa usa; + socklen_t slen = tousa(&c->peer, &usa); + n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); + if (n > 0) tomgaddr(&usa, &c->peer, slen != sizeof(usa.sin)); + } else { + n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); + } + return n == 0 ? -1 : n < 0 && mg_sock_would_block() ? 0 : n; +} + +// NOTE(lsm): do only one iteration of reads, cause some systems +// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data +static void read_conn(struct mg_connection *c) { + if (c->recv.len >= MG_MAX_RECV_BUF_SIZE) { + mg_error(c, "max_recv_buf_size reached"); + } else if (c->recv.size - c->recv.len < MG_IO_SIZE && + !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) { + mg_error(c, "oom"); + } else { + char *buf = (char *) &c->recv.buf[c->recv.len]; + size_t len = c->recv.size - c->recv.len; + long n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_sock_recv(c, buf, len); + LOG(n > 0 ? LL_VERBOSE_DEBUG : LL_DEBUG, + ("%-3lu %d%d%d%d%d%d%d%d%d%d%d%d%d%d %7ld %ld/%ld err %d", c->id, + c->is_listening, c->is_client, c->is_accepted, c->is_resolving, + c->is_connecting, c->is_tls, c->is_tls_hs, c->is_udp, c->is_websocket, + c->is_hexdumping, c->is_draining, c->is_closing, c->is_readable, + c->is_writable, (long) c->recv.len, n, (long) len, MG_SOCK_ERRNO)); + if (n == 0) { + // Do nothing + } else if (n < 0) { + c->is_closing = 1; // Error, or normal termination + } else if (n > 0) { + struct mg_str evd = mg_str_n(buf, (size_t) n); + if (c->is_hexdumping) { + char *s = mg_hexdump(buf, (size_t) n); + LOG(LL_INFO, ("\n-- %lu %s %s %ld\n%s", c->id, c->label, "<-", n, s)); + free(s); + } + c->recv.len += (size_t) n; + mg_call(c, MG_EV_READ, &evd); + } + } +} + +static void write_conn(struct mg_connection *c) { + char *buf = (char *) c->send.buf; + size_t len = c->send.len; + long n = c->is_tls ? mg_tls_send(c, buf, len) : mg_sock_send(c, buf, len); + + LOG(n > 0 ? LL_VERBOSE_DEBUG : LL_DEBUG, + ("%-3lu %d%d%d%d%d%d%d%d%d%d%d%d%d%d %7ld %ld err %d", c->id, + c->is_listening, c->is_client, c->is_accepted, c->is_resolving, + c->is_connecting, c->is_tls, c->is_tls_hs, c->is_udp, c->is_websocket, + c->is_hexdumping, c->is_draining, c->is_closing, c->is_readable, + c->is_writable, (long) c->send.len, n, MG_SOCK_ERRNO)); + + if (n == 0) { + // Do nothing + } else if (n < 0) { + c->is_closing = 1; // Error, or normal termination + } else if (n > 0) { + // Hexdump before deleting + if (c->is_hexdumping) { + char *s = mg_hexdump(buf, (size_t) n); + LOG(LL_INFO, ("\n-- %lu %s %s %ld\n%s", c->id, c->label, "<-", n, s)); + free(s); + } + mg_iobuf_del(&c->send, 0, (size_t) n); + if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); + mg_call(c, MG_EV_WRITE, &n); + // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); + } +} + +static void close_conn(struct mg_connection *c) { + mg_resolve_cancel(c); // Close any pending DNS query + LIST_DELETE(struct mg_connection, &c->mgr->conns, c); + if (c == c->mgr->dns4.c) c->mgr->dns4.c = NULL; + if (c == c->mgr->dns6.c) c->mgr->dns6.c = NULL; + // Order of operations is important. `MG_EV_CLOSE` event must be fired + // before we deallocate received data, see #1331 + mg_call(c, MG_EV_CLOSE, NULL); + LOG(LL_DEBUG, ("%lu closed", c->id)); + if (FD(c) != INVALID_SOCKET) { + closesocket(FD(c)); +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); +#endif + c->fd = S2PTR(INVALID_SOCKET); + } + mg_tls_free(c); + mg_iobuf_free(&c->recv); + mg_iobuf_free(&c->send); + memset(c, 0, sizeof(*c)); + free(c); +} + +static void setsockopts(struct mg_connection *c) { +#if MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS + (void) c; +#else + int on = 1; +#if !defined(SOL_TCP) +#define SOL_TCP IPPROTO_TCP +#endif + setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)); +#if defined(TCP_QUICKACK) + setsockopt(FD(c), SOL_TCP, TCP_QUICKACK, (char *) &on, sizeof(on)); +#endif + setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)); +#if ESP32 || ESP8266 || defined(__linux__) + int idle = 60; + setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); +#endif +#if !defined(_WIN32) && !defined(__QNX__) + { + int cnt = 3, intvl = 20; + setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt)); + setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)); + } +#endif +#endif +} + +void mg_connect_resolved(struct mg_connection *c) { + char buf[40]; + int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; + int rc, af = c->peer.is_ip6 ? AF_INET6 : AF_INET; + mg_straddr(c, buf, sizeof(buf)); + c->fd = S2PTR(socket(af, type, 0)); + if (FD(c) == INVALID_SOCKET) { + mg_error(c, "socket(): %d", MG_SOCK_ERRNO); + } else { + union usa usa; + socklen_t slen = tousa(&c->peer, &usa); + if (c->is_udp == 0) mg_set_non_blocking_mode(FD(c)); + if (c->is_udp == 0) setsockopts(c); + mg_call(c, MG_EV_RESOLVE, NULL); + if ((rc = connect(FD(c), &usa.sa, slen)) == 0) { + mg_call(c, MG_EV_CONNECT, NULL); + } else if (mg_sock_would_block()) { + c->is_connecting = 1; + } else { + mg_error(c, "connect: %d", MG_SOCK_ERRNO); + } + } +} + +struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + if ((c = alloc_conn(mgr, 1, INVALID_SOCKET)) == NULL) { + LOG(LL_ERROR, ("OOM")); + } else { + struct mg_str host = mg_url_host(url); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->is_udp = (strncmp(url, "udp:", 4) == 0); + c->peer.port = mg_htons(mg_url_port(url)); + c->fn = fn; + c->fn_data = fn_data; + LOG(LL_DEBUG, ("%lu -> %s", c->id, url)); + mg_resolve(c, &host, mgr->dnstimeout); + } + return c; +} + +static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) { + struct mg_connection *c = NULL; + union usa usa; + socklen_t sa_len = sizeof(usa); + SOCKET fd = accept(FD(lsn), &usa.sa, &sa_len); + if (fd == INVALID_SOCKET) { +#if MG_ARCH == MG_ARCH_AZURERTOS + // AzureRTOS, in non-block socket mode can mark listening socket readable + // even it is not. See comment for 'select' func implementation in nx_bsd.c + // That's not an error, just should try later + if (MG_SOCK_ERRNO != EAGAIN) +#endif + LOG(LL_ERROR, ("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERRNO)); +#if (!defined(_WIN32) && (MG_ARCH != MG_ARCH_FREERTOS_TCP)) + } else if ((long) fd >= FD_SETSIZE) { + LOG(LL_ERROR, ("%ld > %ld", (long) fd, (long) FD_SETSIZE)); + closesocket(fd); +#endif + } else if ((c = alloc_conn(mgr, 0, fd)) == NULL) { + LOG(LL_ERROR, ("%lu OOM", lsn->id)); + closesocket(fd); + } else { + char buf[40]; + tomgaddr(&usa, &c->peer, sa_len != sizeof(usa.sin)); + mg_straddr(c, buf, sizeof(buf)); + LOG(LL_DEBUG, ("%lu accepted %s", c->id, buf)); + mg_set_non_blocking_mode(FD(c)); + setsockopts(c); + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->is_accepted = 1; + c->is_hexdumping = lsn->is_hexdumping; + c->pfn = lsn->pfn; + c->pfn_data = lsn->pfn_data; + c->fn = lsn->fn; + c->fn_data = lsn->fn_data; + mg_call(c, MG_EV_ACCEPT, NULL); + } +} + +static bool mg_socketpair(SOCKET sp[2], union usa usa[2]) { + socklen_t n = sizeof(usa[0].sin); + bool result = false; + + (void) memset(&usa[0], 0, sizeof(usa[0])); + usa[0].sin.sin_family = AF_INET; + *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001); // 127.0.0.1 + usa[1] = usa[0]; + + if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET && + (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET && + bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n) == 0 && + getsockname(sp[0], &usa[0].sa, &n) == 0 && + getsockname(sp[1], &usa[1].sa, &n) == 0 && + connect(sp[0], &usa[1].sa, n) == 0 && + connect(sp[1], &usa[0].sa, n) == 0) { + mg_set_non_blocking_mode(sp[1]); // Set close-on-exec + result = true; + } else { + if (sp[0] != INVALID_SOCKET) closesocket(sp[0]); + if (sp[1] != INVALID_SOCKET) closesocket(sp[1]); + sp[0] = sp[1] = INVALID_SOCKET; + } + + return result; +} + +void mg_mgr_wakeup(struct mg_connection *c) { + LOG(LL_INFO, ("skt: %p", c->pfn_data)); + send((SOCKET) (size_t) c->pfn_data, "\x01", 1, MSG_NONBLOCKING); +} + +static void pf1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { + if (ev == MG_EV_READ) mg_iobuf_free(&c->recv); + (void) ev_data, (void) fn_data; +} + +struct mg_connection *mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, + void *fn_data) { + union usa usa[2]; + SOCKET sp[2] = {INVALID_SOCKET, INVALID_SOCKET}; + struct mg_connection *c = NULL; + if (!mg_socketpair(sp, usa)) { + LOG(LL_ERROR, ("Cannot create socket pair")); + } else if ((c = alloc_conn(mgr, false, sp[1])) == NULL) { + closesocket(sp[0]); + closesocket(sp[1]); + LOG(LL_ERROR, ("OOM")); + } else { + LOG(LL_INFO, ("pipe %lu", (unsigned long) sp[0])); + tomgaddr(&usa[0], &c->peer, false); + c->is_udp = 1; + c->pfn = pf1; + c->pfn_data = (void *) (size_t) sp[0]; + c->fn = fn; + c->fn_data = fn_data; + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + } + return c; +} + +struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data) { + struct mg_connection *c = NULL; + bool is_udp = strncmp(url, "udp:", 4) == 0; + struct mg_addr addr; + SOCKET fd = mg_open_listener(url, &addr); + if (fd == INVALID_SOCKET) { + LOG(LL_ERROR, ("Failed: %s, errno %d", url, MG_SOCK_ERRNO)); + } else if ((c = alloc_conn(mgr, 0, fd)) == NULL) { + LOG(LL_ERROR, ("OOM %s", url)); + closesocket(fd); + } else { + memcpy(&c->peer, &addr, sizeof(struct mg_addr)); + c->fd = S2PTR(fd); + c->is_listening = 1; + c->is_udp = is_udp; + LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); + c->fn = fn; + c->fn_data = fn_data; + LOG(LL_INFO, + ("%lu accepting on %s (port %u)", c->id, url, mg_ntohs(c->peer.port))); + } + return c; +} + +static void mg_iotest(struct mg_mgr *mgr, int ms) { +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + struct mg_connection *c; + for (c = mgr->conns; c != NULL; c = c->next) { + if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue; + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT); + if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0)) + FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE); + } + FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms)); + for (c = mgr->conns; c != NULL; c = c->next) { + EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss); + c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1 : 0; + c->is_writable = bits & eSELECT_WRITE ? 1 : 0; + FreeRTOS_FD_CLR(c->fd, mgr->ss, + eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE); + } +#else + struct timeval tv = {ms / 1000, (ms % 1000) * 1000}; + struct mg_connection *c; + fd_set rset, wset; + SOCKET maxfd = 0; + int rc; + + FD_ZERO(&rset); + FD_ZERO(&wset); + + for (c = mgr->conns; c != NULL; c = c->next) { + if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue; + FD_SET(FD(c), &rset); + if (FD(c) > maxfd) maxfd = FD(c); + if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0)) + FD_SET(FD(c), &wset); + } + + if ((rc = select((int) maxfd + 1, &rset, &wset, NULL, &tv)) < 0) { + LOG(LL_DEBUG, ("select: %d %d", rc, MG_SOCK_ERRNO)); + FD_ZERO(&rset); + FD_ZERO(&wset); + } + + for (c = mgr->conns; c != NULL; c = c->next) { + // TLS might have stuff buffered, so dig everything + c->is_readable = c->is_tls && c->is_readable + ? 1 + : FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &rset); + c->is_writable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &wset); + } +#endif +} + +static void connect_conn(struct mg_connection *c) { + int rc = 0; +#if MG_ARCH != MG_ARCH_FREERTOS_TCP + socklen_t len = sizeof(rc); + if (getsockopt(FD(c), SOL_SOCKET, SO_ERROR, (char *) &rc, &len)) rc = 1; +#endif + if (rc == EAGAIN || rc == EWOULDBLOCK) rc = 0; + c->is_connecting = 0; + if (rc) { + char buf[40]; + mg_error(c, "error connecting to %s", mg_straddr(c, buf, sizeof(buf))); + } else { + if (c->is_tls_hs) mg_tls_handshake(c); + mg_call(c, MG_EV_CONNECT, NULL); + } +} + +void mg_mgr_poll(struct mg_mgr *mgr, int ms) { + struct mg_connection *c, *tmp; + unsigned long now; + + mg_iotest(mgr, ms); + now = mg_millis(); + mg_timer_poll(now); + + for (c = mgr->conns; c != NULL; c = tmp) { + tmp = c->next; + mg_call(c, MG_EV_POLL, &now); + LOG(LL_VERBOSE_DEBUG, + ("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-', + c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't', + c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h', + c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); + if (c->is_resolving || c->is_closing) { + // Do nothing + } else if (c->is_listening && c->is_udp == 0) { + if (c->is_readable) accept_conn(mgr, c); + } else if (c->is_connecting) { + if (c->is_readable || c->is_writable) connect_conn(c); + } else if (c->is_tls_hs) { + if ((c->is_readable || c->is_writable)) mg_tls_handshake(c); + } else { + if (c->is_readable) read_conn(c); + if (c->is_writable) write_conn(c); + } + + if (c->is_draining && c->send.len == 0) c->is_closing = 1; + if (c->is_closing) close_conn(c); + } +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/ssi.c" +#endif + + + + +#ifndef MG_MAX_SSI_DEPTH +#define MG_MAX_SSI_DEPTH 5 +#endif + +#if MG_ENABLE_SSI +static char *mg_ssi(const char *path, const char *root, int depth) { + struct mg_iobuf b = {NULL, 0, 0}; + FILE *fp = fopen(path, "rb"); + if (fp != NULL) { + char buf[BUFSIZ], arg[sizeof(buf)]; + int ch, intag = 0; + size_t len = 0, align = MG_IO_SIZE; + while ((ch = fgetc(fp)) != EOF) { + if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { + buf[len++] = (char) (ch & 0xff); + if (sscanf(buf, "<!--#include file=\"%[^\"]", arg)) { + char tmp[MG_PATH_MAX + BUFSIZ + 10], + *p = (char *) path + strlen(path), *data; + while (p > path && p[-1] != MG_DIRSEP && p[-1] != '/') p--; + snprintf(tmp, sizeof(tmp), "%.*s%s", (int) (p - path), path, arg); + if (depth < MG_MAX_SSI_DEPTH && + (data = mg_ssi(tmp, root, depth + 1)) != NULL) { + mg_iobuf_add(&b, b.len, data, strlen(data), align); + free(data); + } else { + LOG(LL_ERROR, ("%s: file=%s error or too deep", path, arg)); + } + } else if (sscanf(buf, "<!--#include virtual=\"%[^\"]", arg)) { + char tmp[MG_PATH_MAX + BUFSIZ + 10], *data; + snprintf(tmp, sizeof(tmp), "%s%s", root, arg); + if (depth < MG_MAX_SSI_DEPTH && + (data = mg_ssi(tmp, root, depth + 1)) != NULL) { + mg_iobuf_add(&b, b.len, data, strlen(data), align); + free(data); + } else { + LOG(LL_ERROR, ("%s: virtual=%s error or too deep", path, arg)); + } + } else { + // Unknown SSI tag + LOG(LL_INFO, ("Unknown SSI tag: %.*s", (int) len, buf)); + mg_iobuf_add(&b, b.len, buf, len, align); + } + intag = 0; + len = 0; + } else if (ch == '<') { + intag = 1; + if (len > 0) mg_iobuf_add(&b, b.len, buf, len, align); + len = 0; + buf[len++] = (char) (ch & 0xff); + } else if (intag) { + if (len == 5 && strncmp(buf, "<!--#", 5) != 0) { + intag = 0; + } else if (len >= sizeof(buf) - 2) { + LOG(LL_ERROR, ("%s: SSI tag is too large", path)); + len = 0; + } + buf[len++] = (char) (ch & 0xff); + } else { + buf[len++] = (char) (ch & 0xff); + if (len >= sizeof(buf)) { + mg_iobuf_add(&b, b.len, buf, len, align); + len = 0; + } + } + } + if (len > 0) mg_iobuf_add(&b, b.len, buf, len, align); + if (b.len > 0) mg_iobuf_add(&b, b.len, "", 1, align); // nul-terminate + fclose(fp); + } + (void) depth; + (void) root; + return (char *) b.buf; +} + +void mg_http_serve_ssi(struct mg_connection *c, const char *root, + const char *fullpath) { + const char *headers = "Content-Type: text/html; charset=utf-8\r\n"; + char *data = mg_ssi(fullpath, root, 0); + mg_http_reply(c, 200, headers, "%s", data == NULL ? "" : data); + free(data); +} +#else +void mg_http_serve_ssi(struct mg_connection *c, const char *root, + const char *fullpath) { + mg_http_reply(c, 501, NULL, "SSI not enabled"); + (void) root, (void) fullpath; +} +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/str.c" +#endif + +#include <stdlib.h> + +struct mg_str mg_str_s(const char *s) { + struct mg_str str = {s, s == NULL ? 0 : strlen(s)}; + return str; +} + +struct mg_str mg_str_n(const char *s, size_t n) { + struct mg_str str = {s, n}; + return str; +} + +int mg_lower(const char *s) { + return tolower(*(const unsigned char *) s); +} + +int mg_ncasecmp(const char *s1, const char *s2, size_t len) { + int diff = 0; + if (len > 0) do { + diff = mg_lower(s1++) - mg_lower(s2++); + } while (diff == 0 && s1[-1] != '\0' && --len > 0); + return diff; +} + +int mg_casecmp(const char *s1, const char *s2) { + return mg_ncasecmp(s1, s2, (size_t) ~0); +} + +int mg_vcmp(const struct mg_str *s1, const char *s2) { + size_t n2 = strlen(s2), n1 = s1->len; + int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2); + if (r == 0) return (int) (n1 - n2); + return r; +} + +int mg_vcasecmp(const struct mg_str *str1, const char *str2) { + size_t n2 = strlen(str2), n1 = str1->len; + int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2); + if (r == 0) return (int) (n1 - n2); + return r; +} + +struct mg_str mg_strdup(const struct mg_str s) { + struct mg_str r = {NULL, 0}; + if (s.len > 0 && s.ptr != NULL) { + char *sc = (char *) calloc(1, s.len + 1); + if (sc != NULL) { + memcpy(sc, s.ptr, s.len); + sc[s.len] = '\0'; + r.ptr = sc; + r.len = s.len; + } + } + return r; +} + +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) { + size_t i = 0; + while (i < str1.len && i < str2.len) { + int c1 = str1.ptr[i]; + int c2 = str2.ptr[i]; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + i++; + } + if (i < str1.len) return 1; + if (i < str2.len) return -1; + return 0; +} + +const char *mg_strstr(const struct mg_str haystack, + const struct mg_str needle) { + size_t i; + if (needle.len > haystack.len) return NULL; + for (i = 0; i <= haystack.len - needle.len; i++) { + if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) { + return haystack.ptr + i; + } + } + return NULL; +} + +struct mg_str mg_strstrip(struct mg_str s) { + while (s.len > 0 && isspace((int) *s.ptr)) s.ptr++, s.len--; + while (s.len > 0 && isspace((int) *(s.ptr + s.len - 1))) s.len--; + return s; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/timer.c" +#endif +// Copyright (c) Cesanta Software Limited +// All rights reserved + + + + +struct mg_timer *g_timers; + +void mg_timer_init(struct mg_timer *t, unsigned long ms, unsigned flags, + void (*fn)(void *), void *arg) { + struct mg_timer tmp = {ms, flags, fn, arg, 0UL, g_timers}; + *t = tmp; + g_timers = t; + if (flags & MG_TIMER_RUN_NOW) fn(arg); +} + +void mg_timer_free(struct mg_timer *t) { + struct mg_timer **head = &g_timers; + while (*head && *head != t) head = &(*head)->next; + if (*head) *head = t->next; +} + +void mg_timer_poll(unsigned long now_ms) { + // If time goes back (wrapped around), reset timers + struct mg_timer *t, *tmp; + static unsigned long oldnow; // Timestamp in a previous invocation + if (oldnow > now_ms) { // If it is wrapped, reset timers + for (t = g_timers; t != NULL; t = t->next) t->expire = 0; + } + oldnow = now_ms; + + for (t = g_timers; t != NULL; t = tmp) { + tmp = t->next; + if (t->expire == 0) t->expire = now_ms + t->period_ms; + if (t->expire > now_ms) continue; + t->fn(t->arg); + // Try to tick timers with the given period as accurate as possible, + // even if this polling function is called with some random period. + t->expire = now_ms - t->expire > (unsigned long) t->period_ms + ? now_ms + t->period_ms + : t->expire + t->period_ms; + if (!(t->flags & MG_TIMER_REPEAT)) mg_timer_free(t); + } +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/tls.c" +#endif + + +#if MG_ENABLE_MBEDTLS ///////////////////////////////////////// MBEDTLS + + + + +#include <mbedtls/debug.h> +#include <mbedtls/ssl.h> + +#ifndef EXTERN_C +#ifdef __cplusplus +#define EXTERN_C extern "C" +#else +#define EXTERN_C +#endif +#endif + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 +#define RNG , rng_get, NULL +#else +#define RNG +#endif + +// Different versions have those in different files, so declare here +EXTERN_C int mbedtls_net_recv(void *, unsigned char *, size_t); +EXTERN_C int mbedtls_net_send(void *, const unsigned char *, size_t); + +struct mg_tls { + char *cafile; // CA certificate path + mbedtls_x509_crt ca; // Parsed CA certificate + mbedtls_x509_crt cert; // Parsed certificate + mbedtls_ssl_context ssl; // SSL/TLS context + mbedtls_ssl_config conf; // SSL-TLS config + mbedtls_pk_context pk; // Private key context +}; + +void mg_tls_handshake(struct mg_connection *c) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + int rc; + mbedtls_ssl_set_bio(&tls->ssl, &c->fd, mbedtls_net_send, mbedtls_net_recv, 0); + rc = mbedtls_ssl_handshake(&tls->ssl); + if (rc == 0) { // Success + LOG(LL_DEBUG, ("%lu success", c->id)); + c->is_tls_hs = 0; + } else if (rc == MBEDTLS_ERR_SSL_WANT_READ || + rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending + LOG(LL_VERBOSE_DEBUG, ("%lu pending, %d%d %d (-%#x)", c->id, + c->is_connecting, c->is_tls_hs, rc, -rc)); + } else { + mg_error(c, "TLS handshake: -%#x", -rc); // Error + } +} + +static int mbed_rng(void *ctx, unsigned char *buf, size_t len) { + mg_random(buf, len); + (void) ctx; + return 0; +} + +static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) { + n = (int) strlen(s2) - 1; + LOG(LL_VERBOSE_DEBUG, ("%p %.*s", ((struct mg_connection *) c)->fd, n, s2)); + (void) s; + (void) c; + (void) lev; +} + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 +static int rng_get(void *p_rng, unsigned char *buf, size_t len) { + (void) p_rng; + mg_random(buf, len); + return 0; +} +#endif + +void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) { + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls)); + int rc = 0; + const char *ca = opts->ca == NULL ? "-" + : opts->ca[0] == '-' ? "(emb)" + : opts->ca; + const char *cert = opts->cert == NULL ? "-" + : opts->cert[0] == '-' ? "(emb)" + : opts->cert; + const char *certkey = opts->certkey == NULL ? "-" + : opts->certkey[0] == '-' ? "(emb)" + : opts->certkey; + if (tls == NULL) { + mg_error(c, "TLS OOM"); + goto fail; + } + LOG(LL_DEBUG, + ("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id, ca, cert, certkey)); + mbedtls_ssl_init(&tls->ssl); + mbedtls_ssl_config_init(&tls->conf); + mbedtls_x509_crt_init(&tls->ca); + mbedtls_x509_crt_init(&tls->cert); + mbedtls_pk_init(&tls->pk); + mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c); + //#if !defined(ESP_PLATFORM) + // mbedtls_debug_set_threshold(5); + //#endif + if ((rc = mbedtls_ssl_config_defaults( + &tls->conf, + c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { + mg_error(c, "tls defaults %#x", -rc); + goto fail; + } + mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c); + if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) { + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE); + } + if (opts->ca != NULL && opts->ca[0] != '\0') { +#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK) + tls->cafile = strdup(opts->ca); + rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile, NULL); + if (rc != 0) { + mg_error(c, "parse on-disk chain(%s) err %#x", ca, -rc); + goto fail; + } +#else + rc = opts->ca[0] == '-' + ? mbedtls_x509_crt_parse(&tls->ca, (uint8_t *) opts->ca, + strlen(opts->ca) + 1) + : mbedtls_x509_crt_parse_file(&tls->ca, opts->ca); + if (rc != 0) { + mg_error(c, "parse(%s) err %#x", ca, -rc); + goto fail; + } + mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL); +#endif + if (opts->srvname.len > 0) { + char mem[128], *buf = mem; + mg_asprintf(&buf, sizeof(mem), "%.*s", (int) opts->srvname.len, + opts->srvname.ptr); + mbedtls_ssl_set_hostname(&tls->ssl, buf); + if (buf != mem) free(buf); + } + mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED); + } + if (opts->cert != NULL && opts->cert[0] != '\0') { + const char *key = opts->certkey; + if (key == NULL) { + key = opts->cert; + certkey = cert; + } + rc = opts->cert[0] == '-' + ? mbedtls_x509_crt_parse(&tls->cert, (uint8_t *) opts->cert, + strlen(opts->cert) + 1) + : mbedtls_x509_crt_parse_file(&tls->cert, opts->cert); + if (rc != 0) { + mg_error(c, "parse(%s) err %#x", cert, -rc); + goto fail; + } + rc = key[0] == '-' ? mbedtls_pk_parse_key(&tls->pk, (uint8_t *) key, + strlen(key) + 1, NULL, 0 RNG) + : mbedtls_pk_parse_keyfile(&tls->pk, key, NULL RNG); + if (rc != 0) { + mg_error(c, "tls key(%s) %#x", certkey, -rc); + goto fail; + } + rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk); + if (rc != 0) { + mg_error(c, "own cert %#x", -rc); + goto fail; + } + } + if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) { + mg_error(c, "setup err %#x", -rc); + goto fail; + } + c->tls = tls; + c->is_tls = 1; + c->is_tls_hs = 1; + if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) { + mg_tls_handshake(c); + } + return; +fail: + c->is_closing = 1; + free(tls); +} + +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + long n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len); + return n == 0 ? -1 : n == MBEDTLS_ERR_SSL_WANT_READ ? 0 : n; +} + +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + long n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len); + return n == 0 ? -1 : n == MBEDTLS_ERR_SSL_WANT_WRITE ? 0 : n; +} + +void mg_tls_free(struct mg_connection *c) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + if (tls == NULL) return; + free(tls->cafile); + mbedtls_ssl_free(&tls->ssl); + mbedtls_pk_free(&tls->pk); + mbedtls_x509_crt_free(&tls->ca); + mbedtls_x509_crt_free(&tls->cert); + mbedtls_ssl_config_free(&tls->conf); + free(tls); + c->tls = NULL; +} +#elif MG_ENABLE_OPENSSL ///////////////////////////////////////// OPENSSL + +#include <openssl/err.h> +#include <openssl/ssl.h> + +struct mg_tls { + SSL_CTX *ctx; + SSL *ssl; +}; + +static int mg_tls_err(struct mg_tls *tls, int res) { + int err = SSL_get_error(tls->ssl, res); + // We've just fetched the last error from the queue. + // Now we need to clear the error queue. If we do not, then the following + // can happen (actually reported): + // - A new connection is accept()-ed with cert error (e.g. self-signed cert) + // - Since all accept()-ed connections share listener's context, + // - *ALL* SSL accepted connection report read error on the next poll cycle. + // Thus a single errored connection can close all the rest, unrelated ones. + // Clearing the error keeps the shared SSL_CTX in an OK state. + + if (err != 0) ERR_print_errors_fp(stderr); + ERR_clear_error(); + if (err == SSL_ERROR_WANT_READ) return 0; + if (err == SSL_ERROR_WANT_WRITE) return 0; + return err; +} + +void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) { + struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls)); + const char *id = "mongoose"; + static unsigned char s_initialised = 0; + int rc; + + if (tls == NULL) { + mg_error(c, "TLS OOM"); + goto fail; + } + + if (!s_initialised) { + SSL_library_init(); + s_initialised++; + } + LOG(LL_DEBUG, ("%lu Setting TLS, CA: %s, cert: %s, key: %s", c->id, + opts->ca == NULL ? "null" : opts->ca, + opts->cert == NULL ? "null" : opts->cert, + opts->certkey == NULL ? "null" : opts->certkey)); + tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method()) + : SSL_CTX_new(SSLv23_server_method()); + if ((tls->ssl = SSL_new(tls->ctx)) == NULL) { + mg_error(c, "SSL_new"); + goto fail; + } + SSL_set_session_id_context(tls->ssl, (const uint8_t *) id, + (unsigned) strlen(id)); + // Disable deprecated protocols + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2); + SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3); + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1); + SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1_1); +#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION + SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION); +#endif +#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE + SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE); +#endif + + if (opts->ca != NULL && opts->ca[0] != '\0') { + SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca, NULL)) != 1) { + mg_error(c, "parse(%s): err %d", opts->ca, mg_tls_err(tls, rc)); + goto fail; + } + } + if (opts->cert != NULL && opts->cert[0] != '\0') { + const char *key = opts->certkey; + if (key == NULL) key = opts->cert; + if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) != 1) { + mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc)); + goto fail; + } else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) != 1) { + mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc)); + goto fail; +#if OPENSSL_VERSION_NUMBER > 0x10100000L + } else if ((rc = SSL_use_certificate_chain_file(tls->ssl, opts->cert)) != + 1) { + mg_error(c, "Invalid CA, err %d", mg_tls_err(tls, rc)); + goto fail; +#endif + } else { + SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +#if OPENSSL_VERSION_NUMBER > 0x10002000L + SSL_set_ecdh_auto(tls->ssl, 1); +#endif + } + } + if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl, opts->ciphers); + if (opts->srvname.len > 0) { + char mem[128], *buf = mem; + mg_asprintf(&buf, sizeof(mem), "%.*s", (int) opts->srvname.len, + opts->srvname.ptr); + SSL_set_tlsext_host_name(tls->ssl, buf); + if (buf != mem) free(buf); + } + c->tls = tls; + c->is_tls = 1; + c->is_tls_hs = 1; + if (c->is_client && c->is_resolving == 0 && c->is_connecting == 0) { + mg_tls_handshake(c); + } + c->is_hexdumping = 1; + LOG(LL_DEBUG, ("%lu SSL %s OK", c->id, c->is_accepted ? "accept" : "client")); + return; +fail: + c->is_closing = 1; + free(tls); +} + +void mg_tls_handshake(struct mg_connection *c) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + int rc; + SSL_set_fd(tls->ssl, (int) (size_t) c->fd); + rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl); + if (rc == 1) { + LOG(LL_DEBUG, ("%lu success", c->id)); + c->is_tls_hs = 0; + } else { + int code = mg_tls_err(tls, rc); + if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code); + } +} + +void mg_tls_free(struct mg_connection *c) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + if (tls == NULL) return; + SSL_free(tls->ssl); + SSL_CTX_free(tls->ctx); + free(tls); + c->tls = NULL; +} + +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + int n = SSL_read(tls->ssl, buf, (int) len); + return n == 0 ? -1 : n < 0 && mg_tls_err(tls, n) == 0 ? 0 : n; +} + +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) { + struct mg_tls *tls = (struct mg_tls *) c->tls; + int n = SSL_write(tls->ssl, buf, (int) len); + return n == 0 ? -1 : n < 0 && mg_tls_err(tls, n) == 0 ? 0 : n; +} + +#else ////////////////////////////////////////// NO TLS + +void mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) { + (void) opts; + mg_error(c, "TLS is not enabled"); +} +void mg_tls_handshake(struct mg_connection *c) { + (void) c; +} +void mg_tls_free(struct mg_connection *c) { + (void) c; +} +long mg_tls_recv(struct mg_connection *c, void *buf, size_t len) { + return c == NULL || buf == NULL || len == 0 ? 0 : -1; +} +long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) { + return c == NULL || buf == NULL || len == 0 ? 0 : -1; +} + +#endif + +#ifdef MG_ENABLE_LINES +#line 1 "src/url.c" +#endif + +#include <stdlib.h> + +struct url { + size_t key, user, pass, host, port, uri, end; +}; + +int mg_url_is_ssl(const char *url) { + return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6) == 0 || + strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 || + strncmp(url, "tls:", 4) == 0; +} + +static struct url urlparse(const char *url) { + size_t i; + struct url u; + memset(&u, 0, sizeof(u)); + for (i = 0; url[i] != '\0'; i++) { + if (i > 0 && u.host == 0 && url[i - 1] == '/' && url[i] == '/') { + u.host = i + 1; + u.port = 0; + } else if (url[i] == ']') { + u.port = 0; // IPv6 URLs, like http://[::1]/bar + } else if (url[i] == ':' && u.port == 0 && u.uri == 0) { + u.port = i + 1; + } else if (url[i] == '@' && u.user == 0 && u.pass == 0) { + u.user = u.host; + u.pass = u.port; + u.host = i + 1; + u.port = 0; + } else if (u.host && u.uri == 0 && url[i] == '/') { + u.uri = i; + } + } + u.end = i; +#if 0 + printf("[%s] %d %d %d %d %d\n", url, u.user, u.pass, u.host, u.port, u.uri); +#endif + return u; +} + +struct mg_str mg_url_host(const char *url) { + struct url u = urlparse(url); + size_t n = u.port ? u.port - u.host - 1 + : u.uri ? u.uri - u.host + : u.end - u.host; + struct mg_str s = mg_str_n(url + u.host, n); + return s; +} + +const char *mg_url_uri(const char *url) { + struct url u = urlparse(url); + return u.uri ? url + u.uri : "/"; +} + +unsigned short mg_url_port(const char *url) { + struct url u = urlparse(url); + unsigned short port = 0; + if (memcmp(url, "http:", 5) == 0 || memcmp(url, "ws:", 3) == 0) port = 80; + if (memcmp(url, "wss:", 4) == 0 || memcmp(url, "https:", 6) == 0) port = 443; + if (memcmp(url, "mqtt:", 5) == 0) port = 1883; + if (memcmp(url, "mqtts:", 6) == 0) port = 8883; + if (u.port) port = (unsigned short) atoi(url + u.port); + return port; +} + +struct mg_str mg_url_user(const char *url) { + struct url u = urlparse(url); + struct mg_str s = mg_str(""); + if (u.user && (u.pass || u.host)) { + size_t n = u.pass ? u.pass - u.user - 1 : u.host - u.user - 1; + s = mg_str_n(url + u.user, n); + } + return s; +} + +struct mg_str mg_url_pass(const char *url) { + struct url u = urlparse(url); + struct mg_str s = mg_str_n("", 0UL); + if (u.pass && u.host) { + size_t n = u.host - u.pass - 1; + s = mg_str_n(url + u.pass, n); + } + return s; +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/util.c" +#endif + + + + +char *mg_file_read(const char *path, size_t *sizep) { + FILE *fp; + char *data = NULL; + size_t size = 0; + if ((fp = fopen(path, "rb")) != NULL) { + fseek(fp, 0, SEEK_END); + size = (size_t) ftell(fp); + rewind(fp); + data = (char *) calloc(1, size + 1); + if (data != NULL) { + if (fread(data, 1, size, fp) != size) { + free(data); + data = NULL; + } else { + data[size] = '\0'; + if (sizep != NULL) *sizep = size; + } + } + fclose(fp); + } + return data; +} + +bool mg_file_write(const char *path, const void *buf, size_t len) { + bool result = false; + FILE *fp; + char tmp[MG_PATH_MAX]; + snprintf(tmp, sizeof(tmp), "%s.%d", path, rand()); + fp = fopen(tmp, "wb"); + if (fp != NULL) { + result = fwrite(buf, 1, len, fp) == len; + fclose(fp); + if (result) { + remove(path); + rename(tmp, path); + } else { + remove(tmp); + } + } + return result; +} + +bool mg_file_printf(const char *path, const char *fmt, ...) { + char tmp[256], *buf = tmp; + bool result; + int len; + va_list ap; + va_start(ap, fmt); + len = mg_vasprintf(&buf, sizeof(tmp), fmt, ap); + va_end(ap); + result = mg_file_write(path, buf, len > 0 ? (size_t) len : 0); + if (buf != tmp) free(buf); + return result; +} + +#if MG_ENABLE_CUSTOM_RANDOM +#else +void mg_random(void *buf, size_t len) { + bool done = false; + unsigned char *p = (unsigned char *) buf; +#if MG_ARCH == MG_ARCH_ESP32 + while (len--) *p++ = (unsigned char) (esp_random() & 255); +#elif MG_ARCH == MG_ARCH_WIN32 +#elif MG_ARCH_UNIX + FILE *fp = fopen("/dev/urandom", "rb"); + if (fp != NULL) { + if (fread(buf, 1, len, fp) == len) done = true; + fclose(fp); + } +#endif + // Fallback to a pseudo random gen + if (!done) { + while (len--) *p++ = (unsigned char) (rand() & 255); + } +} +#endif + +bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) { + size_t i = 0, j = 0, ni = 0, nj = 0; + while (i < n1 || j < n2) { + if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) { + i++, j++; + } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) { + ni = i, nj = j + 1, i++; + } else if (nj > 0 && nj <= n2 && (s1[ni] == '#' || s2[j] != '/')) { + i = ni, j = nj; + } else { + // printf(">>: [%s] [%s] %d %d %d %d\n", s1, s2, i, j, ni, nj); + return false; + } + } + return true; +} + +static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff, + size_t *klen, size_t *voff, size_t *vlen) { + size_t kvlen, kl; + for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != ',';) kvlen++; + for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++; + if (koff != NULL) *koff = ofs; + if (klen != NULL) *klen = kl; + if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0; + if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0; + ofs += kvlen + 1; + return ofs > n ? n : ofs; +} + +bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) { + size_t koff = 0, klen = 0, voff = 0, vlen = 0; + size_t off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen); + if (k != NULL) *k = mg_str_n(s->ptr + koff, klen); + if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen); + *s = mg_str_n(s->ptr + off, s->len - off); + return off > 0; +} + +uint32_t mg_ntohl(uint32_t net) { + uint8_t data[4] = {0, 0, 0, 0}; + memcpy(&data, &net, sizeof(data)); + return (((uint32_t) data[3]) << 0) | (((uint32_t) data[2]) << 8) | + (((uint32_t) data[1]) << 16) | (((uint32_t) data[0]) << 24); +} + +uint16_t mg_ntohs(uint16_t net) { + uint8_t data[2] = {0, 0}; + memcpy(&data, &net, sizeof(data)); + return (uint16_t) ((uint16_t) data[1] | (((uint16_t) data[0]) << 8)); +} + +char *mg_hexdump(const void *buf, size_t len) { + const unsigned char *p = (const unsigned char *) buf; + size_t i, idx, n = 0, ofs = 0, dlen = len * 5 + 100; + char ascii[17] = "", *dst = (char *) calloc(1, dlen); + if (dst == NULL) return dst; + for (i = 0; i < len; i++) { + idx = i % 16; + if (idx == 0) { + if (i > 0 && dlen > n) + n += (size_t) snprintf(dst + n, dlen - n, " %s\n", ascii); + if (dlen > n) + n += (size_t) snprintf(dst + n, dlen - n, "%04x ", (int) (i + ofs)); + } + if (dlen < n) break; + n += (size_t) snprintf(dst + n, dlen - n, " %02x", p[i]); + ascii[idx] = (char) (p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i]); + ascii[idx + 1] = '\0'; + } + while (i++ % 16) { + if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, "%s", " "); + } + if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, " %s\n", ascii); + if (n > dlen - 1) n = dlen - 1; + dst[n] = '\0'; + return dst; +} + +char *mg_hex(const void *buf, size_t len, char *to) { + const unsigned char *p = (const unsigned char *) buf; + static const char *hex = "0123456789abcdef"; + size_t i = 0; + for (; len--; p++) { + to[i++] = hex[p[0] >> 4]; + to[i++] = hex[p[0] & 0x0f]; + } + to[i] = '\0'; + return to; +} + +static unsigned char mg_unhex_nimble(unsigned char c) { + return (c >= '0' && c <= '9') ? (unsigned char) (c - '0') + : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7') + : (unsigned char) (c - 'W'); +} + +unsigned long mg_unhexn(const char *s, size_t len) { + unsigned long i = 0, v = 0; + for (i = 0; i < len; i++) v <<= 4, v |= mg_unhex_nimble(((uint8_t *) s)[i]); + return v; +} + +void mg_unhex(const char *buf, size_t len, unsigned char *to) { + size_t i; + for (i = 0; i < len; i += 2) { + to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2); + } +} + +int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap) { + va_list ap_copy; + int len; + + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size, fmt, ap_copy); + va_end(ap_copy); + + if (len < 0) { + // eCos and Windows are not standard-compliant and return -1 when + // the buffer is too small. Keep allocating larger buffers until we + // succeed or out of memory. + // LCOV_EXCL_START + *buf = NULL; + while (len < 0) { + free(*buf); + if (size == 0) size = 5; + size *= 2; + if ((*buf = (char *) calloc(1, size)) == NULL) { + len = -1; + break; + } + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size - 1, fmt, ap_copy); + va_end(ap_copy); + } + // Microsoft version of vsnprintf() is not always null-terminated, so put + // the terminator manually + if (*buf != NULL) (*buf)[len] = 0; + // LCOV_EXCL_STOP + } else if (len >= (int) size) { + /// Standard-compliant code path. Allocate a buffer that is large enough + if ((*buf = (char *) calloc(1, (size_t) len + 1)) == NULL) { + len = -1; // LCOV_EXCL_LINE + } else { // LCOV_EXCL_LINE + va_copy(ap_copy, ap); + len = vsnprintf(*buf, (size_t) len + 1, fmt, ap_copy); + va_end(ap_copy); + } + } + + return len; +} + +int mg_asprintf(char **buf, size_t size, const char *fmt, ...) { + int ret; + va_list ap; + va_start(ap, fmt); + ret = mg_vasprintf(buf, size, fmt, ap); + va_end(ap); + return ret; +} + +int64_t mg_to64(struct mg_str str) { + int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */; + size_t i = 0; + while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++; + if (i < str.len && str.ptr[i] == '-') neg = -1, i++; + while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') { + if (result > max) return 0; + result *= 10; + result += (str.ptr[i] - '0'); + i++; + } + return result * neg; +} + +uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) { + int i; + crc = ~crc; + while (len--) { + crc ^= *(unsigned char *) buf++; + for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1; + } + return ~crc; +} + +static int isbyte(int n) { + return n >= 0 && n <= 255; +} + +static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) { + int n, a, b, c, d, slash = 32, len = 0; + if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 || + sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) && + isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && slash >= 0 && + slash < 33) { + len = n; + *net = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | + (uint32_t) d; + *mask = slash ? (uint32_t) (0xffffffffU << (32 - slash)) : (uint32_t) 0; + } + return len; +} + +int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) { + struct mg_str k, v; + int allowed = acl.len == 0 ? '+' : '-'; // If any ACL is set, deny by default + while (mg_commalist(&acl, &k, &v)) { + uint32_t net, mask; + if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1; + if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2; + if ((remote_ip & mask) == net) allowed = k.ptr[0]; + } + return allowed == '+'; +} + +double mg_time(void) { +#if MG_ARCH == MG_ARCH_WIN32 + SYSTEMTIME sysnow; + FILETIME ftime; + GetLocalTime(&sysnow); + SystemTimeToFileTime(&sysnow, &ftime); + /* + * 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64 + * This should not cause a problems in this (21th) century + * 2. Windows FILETIME is a number of 100-nanosecond intervals since January + * 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC, + * thus, we need to convert to seconds and adjust amount (subtract 11644473600 + * seconds) + */ + return (double) (((int64_t) ftime.dwLowDateTime + + ((int64_t) ftime.dwHighDateTime << 32)) / + 10000000.0) - + 11644473600; +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS + return mg_millis() / 1000.0; +#else + struct timeval tv; + if (gettimeofday(&tv, NULL /* tz */) != 0) return 0; + return (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0); +#endif /* _WIN32 */ +} + +void mg_usleep(unsigned long usecs) { +#if MG_ARCH == MG_ARCH_WIN32 + Sleep(usecs / 1000); +#elif MG_ARCH == MG_ARCH_ESP8266 + ets_delay_us(usecs); +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_FREERTOS_LWIP + vTaskDelay(pdMS_TO_TICKS(usecs / 1000)); +#elif MG_ARCH == MG_ARCH_AZURERTOS + tx_thread_sleep((usecs / 1000) * TX_TIMER_TICKS_PER_SECOND); +#else + usleep((useconds_t) usecs); +#endif +} + +unsigned long mg_millis(void) { +#if MG_ARCH == MG_ARCH_WIN32 + return GetTickCount(); +#elif MG_ARCH == MG_ARCH_ESP32 + return esp_timer_get_time() / 1000; +#elif MG_ARCH == MG_ARCH_ESP8266 + return xTaskGetTickCount() * portTICK_PERIOD_MS; +#elif MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_FREERTOS_LWIP + return xTaskGetTickCount() * portTICK_PERIOD_MS; +#elif MG_ARCH == MG_ARCH_AZURERTOS + return tx_time_get() * (1000 /* MS per SEC */ / TX_TIMER_TICKS_PER_SECOND); +#else + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (unsigned long) ((uint64_t) ts.tv_sec * 1000 + + (uint64_t) ts.tv_nsec / 1000000); +#endif +} + +#ifdef MG_ENABLE_LINES +#line 1 "src/ws.c" +#endif + + + + + + + + + + +struct ws_msg { + uint8_t flags; + size_t header_len; + size_t data_len; +}; + +static void ws_handshake(struct mg_connection *c, const struct mg_str *wskey, + const struct mg_str *wsproto, const char *fmt, + va_list ap) { + const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + unsigned char sha[20], b64_sha[30]; + char mem[128], *buf = mem; + + mg_sha1_ctx sha_ctx; + mg_sha1_init(&sha_ctx); + mg_sha1_update(&sha_ctx, (unsigned char *) wskey->ptr, wskey->len); + mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36); + mg_sha1_final(sha, &sha_ctx); + mg_base64_encode(sha, sizeof(sha), (char *) b64_sha); + buf[0] = '\0'; + if (fmt != NULL) mg_vasprintf(&buf, sizeof(mem), fmt, ap); + mg_printf(c, + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: %s\r\n" + "%s", + b64_sha, buf); + if (buf != mem) free(buf); + if (wsproto != NULL) { + mg_printf(c, "Sec-WebSocket-Protocol: %.*s\r\n", (int) wsproto->len, + wsproto->ptr); + } + mg_send(c, "\r\n", 2); +} + +static size_t ws_process(uint8_t *buf, size_t len, struct ws_msg *msg) { + size_t i, n = 0, mask_len = 0; + memset(msg, 0, sizeof(*msg)); + if (len >= 2) { + n = buf[1] & 0x7f; // Frame length + mask_len = buf[1] & 128 ? 4 : 0; // last bit is a mask bit + msg->flags = buf[0]; + if (n < 126 && len >= mask_len) { + msg->data_len = n; + msg->header_len = 2 + mask_len; + } else if (n == 126 && len >= 4 + mask_len) { + msg->header_len = 4 + mask_len; + msg->data_len = mg_ntohs(*(uint16_t *) &buf[2]); + } else if (len >= 10 + mask_len) { + msg->header_len = 10 + mask_len; + msg->data_len = + (size_t) (((uint64_t) mg_ntohl(*(uint32_t *) &buf[2])) << 32) + + mg_ntohl(*(uint32_t *) &buf[6]); + } + } + // Sanity check, and integer overflow protection for the boundary check below + // data_len should not be larger than 1 Gb + if (msg->data_len > 1024 * 1024 * 1024) return 0; + if (msg->header_len + msg->data_len > len) return 0; + if (mask_len > 0) { + uint8_t *p = buf + msg->header_len, *m = p - mask_len; + for (i = 0; i < msg->data_len; i++) p[i] ^= m[i & 3]; + } + return msg->header_len + msg->data_len; +} + +static size_t mkhdr(size_t len, int op, bool is_client, uint8_t *buf) { + size_t n = 0; + buf[0] = (uint8_t) (op | 128); + if (len < 126) { + buf[1] = (unsigned char) len; + n = 2; + } else if (len < 65536) { + uint16_t tmp = mg_htons((uint16_t) len); + buf[1] = 126; + memcpy(&buf[2], &tmp, sizeof(tmp)); + n = 4; + } else { + uint32_t tmp; + buf[1] = 127; + tmp = mg_htonl((uint32_t) ((uint64_t) len >> 32)); + memcpy(&buf[2], &tmp, sizeof(tmp)); + tmp = mg_htonl((uint32_t) (len & 0xffffffff)); + memcpy(&buf[6], &tmp, sizeof(tmp)); + n = 10; + } + if (is_client) { + buf[1] |= 1 << 7; // Set masking flag + mg_random(&buf[n], 4); + n += 4; + } + return n; +} + +static void mg_ws_mask(struct mg_connection *c, size_t len) { + if (c->is_client && c->send.buf != NULL) { + size_t i; + uint8_t *p = c->send.buf + c->send.len - len, *mask = p - 4; + for (i = 0; i < len; i++) p[i] ^= mask[i & 3]; + } +} + +size_t mg_ws_send(struct mg_connection *c, const char *buf, size_t len, + int op) { + uint8_t header[14]; + size_t header_len = mkhdr(len, op, c->is_client, header); + mg_send(c, header, header_len); + LOG(LL_VERBOSE_DEBUG, ("WS out: %d [%.*s]", (int) len, (int) len, buf)); + mg_send(c, buf, len); + mg_ws_mask(c, len); + return header_len + len; +} + +static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data, + void *fn_data) { + struct ws_msg msg; + size_t ofs = (size_t) c->pfn_data; + + // assert(ofs < c->recv.len); + if (ev == MG_EV_READ) { + if (!c->is_websocket && c->is_client) { + int n = mg_http_get_request_len(c->recv.buf, c->recv.len); + if (n < 0) { + c->is_closing = 1; // Some just, not an HTTP request + } else if (n > 0) { + if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) { + LOG(LL_ERROR, + ("%lu WS handshake error: %.*s", c->id, 15, c->recv.buf)); + c->is_closing = 1; + } else { + struct mg_http_message hm; + mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); + c->is_websocket = 1; + mg_call(c, MG_EV_WS_OPEN, &hm); + } + mg_iobuf_del(&c->recv, 0, (size_t) n); + } else { + return; // A request is not yet received + } + } + + while (ws_process(c->recv.buf + ofs, c->recv.len - ofs, &msg) > 0) { + char *s = (char *) c->recv.buf + ofs + msg.header_len; + struct mg_ws_message m = {{s, msg.data_len}, msg.flags}; + size_t len = msg.header_len + msg.data_len; + uint8_t final = msg.flags & 128, op = msg.flags & 15; + // LOG(LL_VERBOSE_DEBUG, ("fin %d op %d len %d [%.*s]", final, op, + // (int) m.data.len, (int) m.data.len, m.data.ptr)); + switch (op) { + case WEBSOCKET_OP_CONTINUE: + mg_call(c, MG_EV_WS_CTL, &m); + break; + case WEBSOCKET_OP_PING: + LOG(LL_DEBUG, ("%s", "WS PONG")); + mg_ws_send(c, s, msg.data_len, WEBSOCKET_OP_PONG); + mg_call(c, MG_EV_WS_CTL, &m); + break; + case WEBSOCKET_OP_PONG: + mg_call(c, MG_EV_WS_CTL, &m); + break; + case WEBSOCKET_OP_TEXT: + case WEBSOCKET_OP_BINARY: + if (final) mg_call(c, MG_EV_WS_MSG, &m); + break; + case WEBSOCKET_OP_CLOSE: + LOG(LL_ERROR, ("%lu Got WS CLOSE", c->id)); + mg_call(c, MG_EV_WS_CTL, &m); + c->is_closing = 1; + break; + default: + // Per RFC6455, close conn when an unknown op is recvd + mg_error(c, "unknown WS op %d", op); + break; + } + + // Handle fragmented frames: strip header, keep in c->recv + if (final == 0 || op == 0) { + if (op) ofs++, len--, msg.header_len--; // First frame + mg_iobuf_del(&c->recv, ofs, msg.header_len); // Strip header + len -= msg.header_len; + ofs += len; + c->pfn_data = (void *) ofs; + // LOG(LL_INFO, ("FRAG %d [%.*s]", (int) ofs, (int) ofs, c->recv.buf)); + } + // Remove non-fragmented frame + if (final && op) mg_iobuf_del(&c->recv, ofs, len); + // Last chunk of the fragmented frame + if (final && !op) { + m.flags = c->recv.buf[0]; + m.data = mg_str_n((char *) &c->recv.buf[1], (size_t) (ofs - 1)); + mg_call(c, MG_EV_WS_MSG, &m); + mg_iobuf_del(&c->recv, 0, ofs); + ofs = 0; + c->pfn_data = NULL; + } + } + } + (void) fn_data; + (void) ev_data; +} + +struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url, + mg_event_handler_t fn, void *fn_data, + const char *fmt, ...) { + struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); + if (c != NULL) { + char nonce[16], key[30], mem1[128], mem2[256], *buf1 = mem1, *buf2 = mem2; + struct mg_str host = mg_url_host(url); + int n1 = 0, n2 = 0; + if (fmt != NULL) { + va_list ap; + va_start(ap, fmt); + n1 = mg_vasprintf(&buf1, sizeof(mem1), fmt, ap); + va_end(ap); + } + // Send handshake request + // mg_url_host(url, host, sizeof(host)); + mg_random(nonce, sizeof(nonce)); + mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key); + // LOG(LL_DEBUG, "%p [%s]", uri, uri == NULL ? "???" : uri); + n2 = mg_asprintf(&buf2, sizeof(mem2), + "GET %s HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Host: %.*s\r\n" + "Connection: Upgrade\r\n" + "%.*s" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: %s\r\n" + "\r\n", + mg_url_uri(url), (int) host.len, host.ptr, n1, buf1, key); + mg_send(c, buf2, n2 > 0 ? (size_t) n2 : 0); + if (buf1 != mem1) free(buf1); + if (buf2 != mem2) free(buf2); + c->pfn = mg_ws_cb; + c->pfn_data = NULL; + } + return c; +} + +void mg_ws_upgrade(struct mg_connection *c, struct mg_http_message *hm, + const char *fmt, ...) { + struct mg_str *wskey = mg_http_get_header(hm, "Sec-WebSocket-Key"); + c->pfn = mg_ws_cb; + c->pfn_data = NULL; + if (wskey == NULL) { + mg_http_reply(c, 426, "", "WS upgrade expected\n"); + c->is_draining = 1; + } else { + struct mg_str *wsproto = mg_http_get_header(hm, "Sec-WebSocket-Protocol"); + va_list ap; + va_start(ap, fmt); + ws_handshake(c, wskey, wsproto, fmt, ap); + va_end(ap); + c->is_websocket = 1; + mg_call(c, MG_EV_WS_OPEN, hm); + } +} + +size_t mg_ws_wrap(struct mg_connection *c, size_t len, int op) { + uint8_t header[14], *p; + size_t header_len = mkhdr(len, op, c->is_client, header); + + // NOTE: order of operations is important! + mg_iobuf_add(&c->send, c->send.len, NULL, header_len, MG_IO_SIZE); + p = &c->send.buf[c->send.len - len]; // p points to data + memmove(p, p - header_len, len); // Shift data + memcpy(p - header_len, header, header_len); // Prepend header + mg_ws_mask(c, len); // Mask data + + return c->send.len; +} diff --git a/3d_party/mongoose/mongoose.h b/3d_party/mongoose/mongoose.h new file mode 100644 index 0000000000000000000000000000000000000000..e8cdaa6bb6cd8de9316ee88a7a539299988afab4 --- /dev/null +++ b/3d_party/mongoose/mongoose.h @@ -0,0 +1,1051 @@ +// Copyright (c) 2004-2013 Sergey Lyubka +// Copyright (c) 2013-2021 Cesanta Software Limited +// All rights reserved +// +// This software is dual-licensed: you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. For the terms of this +// license, see <http://www.gnu.org/licenses/>. +// +// You are free to use this software under the terms of the GNU General +// Public License, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// Alternatively, you can license this software under a commercial +// license, as set out in <https://www.cesanta.com/license>. + +#ifndef MONGOOSE_H +#define MONGOOSE_H + +#define MG_VERSION "7.4" + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifndef MG_ENABLE_SOCKET +#define MG_ENABLE_SOCKET 1 +#endif + +#ifndef MG_ENABLE_MBEDTLS +#define MG_ENABLE_MBEDTLS 0 +#endif + +#ifndef MG_ENABLE_OPENSSL +#define MG_ENABLE_OPENSSL 0 +#endif + +#ifndef MG_ENABLE_SSI +#define MG_ENABLE_SSI 1 +#endif + +#ifndef MG_ENABLE_IPV6 +#define MG_ENABLE_IPV6 0 +#endif + +#ifndef MG_ENABLE_LOG +#define MG_ENABLE_LOG 1 +#endif + +#ifndef MG_ENABLE_MD5 +#define MG_ENABLE_MD5 0 +#endif + +// Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP) +#ifndef MG_ENABLE_WINSOCK +#define MG_ENABLE_WINSOCK 1 +#endif + +#ifndef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 0 +#endif + +#ifndef MG_ENABLE_CUSTOM_RANDOM +#define MG_ENABLE_CUSTOM_RANDOM 0 +#endif + +#ifndef MG_ENABLE_PACKED_FS +#define MG_ENABLE_PACKED_FS 0 +#endif + +// Granularity of the send/recv IO buffer growth +#ifndef MG_IO_SIZE +#define MG_IO_SIZE 2048 +#endif + +// Maximum size of the recv IO buffer +#ifndef MG_MAX_RECV_BUF_SIZE +#define MG_MAX_RECV_BUF_SIZE (3 * 1024 * 1024) +#endif + +#ifndef MG_MAX_HTTP_HEADERS +#define MG_MAX_HTTP_HEADERS 40 +#endif + +#ifndef MG_PATH_MAX +#define MG_PATH_MAX PATH_MAX +#endif + +#ifndef MG_SOCK_LISTEN_BACKLOG_SIZE +#define MG_SOCK_LISTEN_BACKLOG_SIZE 128 +#endif + + +#define MG_ARCH_CUSTOM 0 +#define MG_ARCH_UNIX 1 +#define MG_ARCH_WIN32 2 +#define MG_ARCH_ESP32 3 +#define MG_ARCH_ESP8266 4 +#define MG_ARCH_FREERTOS_TCP 5 +#define MG_ARCH_FREERTOS_LWIP 6 +#define MG_ARCH_AZURERTOS 7 + +#if !defined(MG_ARCH) +#if defined(__unix__) || defined(__APPLE__) +#define MG_ARCH MG_ARCH_UNIX +#elif defined(_WIN32) +#define MG_ARCH MG_ARCH_WIN32 +#elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR) +#define MG_ARCH MG_ARCH_ESP8266 +#elif defined(ESP_PLATFORM) +#define MG_ARCH MG_ARCH_ESP32 +#elif defined(FREERTOS_IP_H) +#define MG_ARCH MG_ARCH_FREERTOS_TCP +#elif defined(AZURE_RTOS_THREADX) +#define MG_ARCH MG_ARCH_AZURERTOS +#endif + +#if !defined(MG_ARCH) +#error "MG_ARCH is not specified and we couldn't guess it." +#endif +#endif // !defined(MG_ARCH) + +#if !defined(PRINTF_LIKE) +#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__) +#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a))) +#else +#define PRINTF_LIKE(f, a) +#endif +#endif + +#if MG_ARCH == MG_ARCH_CUSTOM +#include <mongoose_custom.h> +#endif + + + + + + + + + +#if MG_ARCH == MG_ARCH_AZURERTOS + +#include <stdarg.h> +#include <stdint.h> +#include <stdbool.h> +#include <time.h> +#include <stdio.h> + +#include <tx_api.h> +#include <fx_api.h> + +#include <tx_port.h> +#include <nx_port.h> +#include <nx_api.h> +#include <nx_bsd.h> + +#ifdef __REDLIB__ +#define va_copy(d,s)__builtin_va_copy(d,s) +#endif + +#define PATH_MAX FX_MAXIMUM_PATH +#define MG_DIRSEP '\\' + +#define socklen_t int +#define closesocket(x) soc_close(x) +#define gmtime_r(a, b) gmtime(a) +#define MG_INT64_FMT "%lld" + +static __inline struct tm* localtime_r(time_t* t, struct tm* tm) { + (void) tm; + return localtime(t); +} + +#undef FOPEN_MAX + +#endif + + +#if MG_ARCH == MG_ARCH_ESP32 + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <time.h> + +#define MG_DIRSEP '/' +#define MG_INT64_FMT "%lld" +#ifndef MG_PATH_MAX +#define MG_PATH_MAX 128 +#endif +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 + +#endif + + +#if MG_ARCH == MG_ARCH_ESP8266 + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> + +#include <esp_system.h> + +#define MG_DIRSEP '/' +#define MG_INT64_FMT "%lld" + +#endif + + +#if MG_ARCH == MG_ARCH_FREERTOS_LWIP + +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#if defined(__GNUC__) +#include <sys/stat.h> +#include <sys/time.h> +#else +typedef long suseconds_t; +struct timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; +#endif + +#include <FreeRTOS.h> +#include <task.h> + +#include <lwip/sockets.h> + +#if LWIP_SOCKET != 1 +// Sockets support disabled in LWIP by default +#error Set LWIP_SOCKET variable to 1 (in lwipopts.h) +#endif + +#if LWIP_POSIX_SOCKETS_IO_NAMES != 0 +// LWIP_POSIX_SOCKETS_IO_NAMES must be disabled in posix-compatible OS +// enviroment (freertos mimics to one) otherwise names like `read` and `write` +// conflict +#error LWIP_POSIX_SOCKETS_IO_NAMES must be set to 0 (in lwipopts.h) for FreeRTOS +#endif + +#define MG_INT64_FMT "%lld" +#define MG_DIRSEP '/' + +// Re-route calloc/free to the FreeRTOS's functions, don't use stdlib +static inline void* mg_calloc(int cnt, size_t size) { + void* p = pvPortMalloc(cnt * size); + if (p != NULL) memset(p, 0, size); + return p; +} +#define calloc(a, b) mg_calloc((a), (b)) +#define free(a) vPortFree(a) +#define malloc(a) pvPortMalloc(a) + +#define gmtime_r(a, b) gmtime(a) + +#endif // MG_ARCH == MG_ARCH_FREERTOS_LWIP + + +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> + +#include <FreeRTOS.h> +#include <FreeRTOS_IP.h> +#include <FreeRTOS_Sockets.h> +#include <task.h> + +#define MG_INT64_FMT "%lld" +#define MG_DIRSEP '/' + +// Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing +// with FreeRTOS_ prefix, is beyond me +#define IPPROTO_TCP FREERTOS_IPPROTO_TCP +#define IPPROTO_UDP FREERTOS_IPPROTO_UDP +#define AF_INET FREERTOS_AF_INET +#define SOCK_STREAM FREERTOS_SOCK_STREAM +#define SOCK_DGRAM FREERTOS_SOCK_DGRAM +#define SO_BROADCAST 0 +#define SO_ERROR 0 +#define SOL_SOCKET 0 +#define SO_REUSEADDR 0 +#define sockaddr_in freertos_sockaddr +#define sockaddr freertos_sockaddr +#define accept(a, b, c) FreeRTOS_accept((a), (b), (c)) +#define connect(a, b, c) FreeRTOS_connect((a), (b), (c)) +#define bind(a, b, c) FreeRTOS_bind((a), (b), (c)) +#define listen(a, b) FreeRTOS_listen((a), (b)) +#define socket(a, b, c) FreeRTOS_socket((a), (b), (c)) +#define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d)) +#define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d)) +#define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e)) +#define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f)) +#define recvfrom(a, b, c, d, e, f) \ + FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f)) +#define closesocket(x) FreeRTOS_closesocket(x) +#define gethostbyname(x) FreeRTOS_gethostbyname(x) +#define getsockname(a, b, c) (-1) + +// Re-route calloc/free to the FreeRTOS's functions, don't use stdlib +static inline void* mg_calloc(int cnt, size_t size) { + void* p = pvPortMalloc(cnt * size); + if (p != NULL) memset(p, 0, size); + return p; +} +#define calloc(a, b) mg_calloc((a), (b)) +#define free(a) vPortFree(a) +#define malloc(a) pvPortMalloc(a) + +#define gmtime_r(a, b) gmtime(a) + +#if !defined(__GNUC__) +// copied from GCC on ARM; for some reason useconds are signed +typedef long suseconds_t; +struct timeval { + time_t tv_sec; + suseconds_t tv_usec; +}; +#endif + +#ifndef EINPROGRESS +#define EINPROGRESS pdFREERTOS_ERRNO_EINPROGRESS +#endif +#ifndef EWOULDBLOCK +#define EWOULDBLOCK pdFREERTOS_ERRNO_EWOULDBLOCK +#endif +#ifndef EAGAIN +#define EAGAIN pdFREERTOS_ERRNO_EAGAIN +#endif +#ifndef EINTR +#define EINTR pdFREERTOS_ERRNO_EINTR +#endif + +#endif // MG_ARCH == MG_ARCH_FREERTOS_TCP + + +#if MG_ARCH == MG_ARCH_UNIX + +#define _DARWIN_UNLIMITED_SELECT 1 // No limit on file descriptors + +#include <arpa/inet.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <netdb.h> +#include <netinet/tcp.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#define MG_DIRSEP '/' +#define MG_INT64_FMT "%" PRId64 +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 + +#endif + + +#if MG_ARCH == MG_ARCH_WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <time.h> + +#if defined(_MSC_VER) && _MSC_VER < 1700 +#define __func__ "" +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +typedef unsigned char uint8_t; +typedef char int8_t; +typedef unsigned short uint16_t; +typedef short int16_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef enum { false = 0, true = 1 } bool; +#else +#include <stdbool.h> +#include <stdint.h> +#include <ws2tcpip.h> +#endif + +#include <winsock2.h> + +// Protect from calls like std::snprintf in app code +// See https://github.com/cesanta/mongoose/issues/1047 +#ifndef __cplusplus +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#ifndef strdup // For MSVC with _DEBUG, see #1359 +#define strdup(x) _strdup(x) +#endif +#endif + +typedef unsigned suseconds_t; +typedef int socklen_t; +#define MG_DIRSEP '\\' +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#ifndef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#endif +#ifndef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif + +#ifndef va_copy +#ifdef __va_copy +#define va_copy __va_copy +#else +#define va_copy(x, y) (x) = (y) +#endif +#endif +#ifndef S_ISDIR +#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR) +#endif + +#define MG_INT64_FMT "%I64d" + +#undef MG_ENABLE_DIRLIST +#define MG_ENABLE_DIRLIST 1 + +// https://lgtm.com/rules/2154840805/ -gmtime, localtime, ctime and asctime +static __inline struct tm* gmtime_r(time_t* t, struct tm* tm) { + (void) tm; + return gmtime(t); +} + +static __inline struct tm* localtime_r(time_t* t, struct tm* tm) { + (void) tm; + return localtime(t); +} + +#endif + + +#include <ctype.h> +#include <string.h> + +struct mg_str { + const char* ptr; // Pointer to string data + size_t len; // String len +}; + +#define MG_NULL_STR \ + { NULL, 0 } + +// Using macro to avoid shadowing C++ struct constructor, see #1298 +#define mg_str(s) mg_str_s(s) + +struct mg_str mg_str(const char* s); +struct mg_str mg_str_n(const char* s, size_t n); +int mg_lower(const char* s); +int mg_ncasecmp(const char* s1, const char* s2, size_t len); +int mg_casecmp(const char* s1, const char* s2); +int mg_vcmp(const struct mg_str* s1, const char* s2); +int mg_vcasecmp(const struct mg_str* str1, const char* str2); +int mg_strcmp(const struct mg_str str1, const struct mg_str str2); +struct mg_str mg_strstrip(struct mg_str s); +struct mg_str mg_strdup(const struct mg_str s); +const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle); + + + + + +#if MG_ENABLE_LOG +#define LOG(level, args) \ + do { \ + if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \ + } while (0) +enum { LL_NONE, LL_ERROR, LL_INFO, LL_DEBUG, LL_VERBOSE_DEBUG }; +bool mg_log_prefix(int ll, const char* file, int line, const char* fname); +void mg_log(const char* fmt, ...) PRINTF_LIKE(1, 2); +void mg_log_set(const char* spec); +void mg_log_set_callback(void (*fn)(const void*, size_t, void*), void* param); +#else +#define LOG(level, args) (void) 0 +#define mg_log_set(x) (void) (x) +#endif + + +struct mg_timer { + unsigned long period_ms; // Timer period in milliseconds + unsigned flags; // Possible flags values below + void (*fn)(void*); // Function to call + void* arg; // Function argument + unsigned long expire; // Expiration timestamp in milliseconds + struct mg_timer* next; // Linkage in g_timers list +}; + +#define MG_TIMER_REPEAT 1 // Call function periodically, otherwise run once +#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set + +extern struct mg_timer* g_timers; // Global list of timers + +void mg_timer_init(struct mg_timer*, unsigned long ms, unsigned, + void (*fn)(void*), void*); +void mg_timer_free(struct mg_timer*); +void mg_timer_poll(unsigned long uptime_ms); + + + + + +char* mg_file_read(const char* path, size_t* size); +bool mg_file_write(const char* path, const void* buf, size_t len); +bool mg_file_printf(const char* path, const char* fmt, ...); +void mg_random(void* buf, size_t len); +bool mg_globmatch(const char* pattern, size_t plen, const char* s, size_t n); +bool mg_commalist(struct mg_str* s, struct mg_str* k, struct mg_str* v); +uint16_t mg_ntohs(uint16_t net); +uint32_t mg_ntohl(uint32_t net); +uint32_t mg_crc32(uint32_t crc, const char* buf, size_t len); +char* mg_hexdump(const void* buf, size_t len); +char* mg_hex(const void* buf, size_t len, char* dst); +void mg_unhex(const char* buf, size_t len, unsigned char* to); +unsigned long mg_unhexn(const char* s, size_t len); +int mg_asprintf(char** buf, size_t size, const char* fmt, ...); +int mg_vasprintf(char** buf, size_t size, const char* fmt, va_list ap); +int64_t mg_to64(struct mg_str str); +int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip); +double mg_time(void); +unsigned long mg_millis(void); +void mg_usleep(unsigned long usecs); + +#define mg_htons(x) mg_ntohs(x) +#define mg_htonl(x) mg_ntohl(x) + +// Expands to a string representation of its argument: e.g. +// MG_STRINGIFY_LITERAL(5) expands to "5" +#if !defined(_MSC_VER) || _MSC_VER >= 1900 +#define MG_STRINGIFY_LITERAL(...) #__VA_ARGS__ +#else +#define MG_STRINGIFY_LITERAL(x) #x +#endif +// Expands to a string representation of its argument, which can be a macro: +// #define FOO 123 +// MG_STRINGIFY_MACRO(FOO) // Expands to 123 +#define MG_STRINGIFY_MACRO(x) MG_STRINGIFY_LITERAL(x) + +// Linked list management macros +#define LIST_ADD_HEAD(type_, head_, elem_) \ + do { \ + (elem_)->next = (*head_); \ + *(head_) = (elem_); \ + } while (0) + +#define LIST_ADD_TAIL(type_, head_, elem_) \ + do { \ + type_ **h = head_; \ + while (*h != NULL) h = &(*h)->next; \ + *h = (elem_); \ + } while (0) + +#define LIST_DELETE(type_, head_, elem_) \ + do { \ + type_ **h = head_; \ + while (*h != (elem_)) h = &(*h)->next; \ + *h = (elem_)->next; \ + } while (0) + + + + +enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 }; + +// Filesystem API functions +// stat() returns MG_FS_* flags and populates file size and modification time +// list() calls fn() for every directory entry, allowing to list a directory +struct mg_fs { + int (*stat)(const char* path, size_t* size, time_t* mtime); + void (*list)(const char* path, void (*fn)(const char*, void*), void*); + struct mg_fd* (*open)(const char* path, int flags); // Open file + void (*close)(struct mg_fd* fd); // Close file + size_t (*read)(void* fd, void* buf, size_t len); // Read file + size_t (*write)(void* fd, const void* buf, size_t len); // Write file + size_t (*seek)(void* fd, size_t offset); // Set file position +}; + +// File descriptor +struct mg_fd { + void* fd; + struct mg_fs* fs; +}; + +extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek +extern struct mg_fs mg_fs_packed; // Packed FS, see examples/complete + + + +unsigned short mg_url_port(const char* url); +int mg_url_is_ssl(const char* url); +struct mg_str mg_url_host(const char* url); +struct mg_str mg_url_user(const char* url); +struct mg_str mg_url_pass(const char* url); +const char* mg_url_uri(const char* url); + + +#include <stddef.h> + +struct mg_iobuf { + unsigned char* buf; // Pointer to stored data + size_t size; // Total size available + size_t len; // Current number of bytes +}; + +int mg_iobuf_init(struct mg_iobuf*, size_t); +int mg_iobuf_resize(struct mg_iobuf*, size_t); +void mg_iobuf_free(struct mg_iobuf*); +size_t mg_iobuf_add(struct mg_iobuf*, size_t, const void*, size_t, size_t); +size_t mg_iobuf_del(struct mg_iobuf*, size_t ofs, size_t len); + +int mg_base64_update(unsigned char p, char* to, int len); +int mg_base64_final(char* to, int len); +int mg_base64_encode(const unsigned char* p, int n, char* to); +int mg_base64_decode(const char* src, int n, char* dst); + + + + +typedef struct { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +} mg_md5_ctx; + +void mg_md5_init(mg_md5_ctx* c); +void mg_md5_update(mg_md5_ctx* c, const unsigned char* data, size_t len); +void mg_md5_final(mg_md5_ctx* c, unsigned char[16]); + + + + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} mg_sha1_ctx; + +void mg_sha1_init(mg_sha1_ctx*); +void mg_sha1_update(mg_sha1_ctx*, const unsigned char* data, size_t len); +void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx*); +void mg_hmac_sha1(const unsigned char* key, size_t key_len, + const unsigned char* text, size_t text_len, + unsigned char out[20]); + + +struct mg_connection; +typedef void (*mg_event_handler_t)(struct mg_connection*, int ev, + void* ev_data, void* fn_data); +void mg_call(struct mg_connection* c, int ev, void* ev_data); +void mg_error(struct mg_connection* c, const char* fmt, ...); + +enum { + MG_EV_ERROR, // Error char *error_message + MG_EV_POLL, // mg_mgr_poll iteration unsigned long *millis + MG_EV_RESOLVE, // Host name is resolved NULL + MG_EV_CONNECT, // Connection established NULL + MG_EV_ACCEPT, // Connection accepted NULL + MG_EV_READ, // Data received from socket struct mg_str * + MG_EV_WRITE, // Data written to socket long *bytes_written + MG_EV_CLOSE, // Connection closed NULL + MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message * + MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg) struct mg_http_message * + MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * + MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * + MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * + MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message * + MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message * + MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code + MG_EV_SNTP_TIME, // SNTP time received struct timeval * + MG_EV_USER, // Starting ID for user events +}; + + + + + + + +struct mg_dns { + const char* url; // DNS server URL + struct mg_connection* c; // DNS server connection +}; + +struct mg_addr { + uint16_t port; // TCP or UDP port in network byte order + uint32_t ip; // IP address in network byte order + uint8_t ip6[16]; // IPv6 address + bool is_ip6; // True when address is IPv6 address +}; + +struct mg_mgr { + struct mg_connection* conns; // List of active connections + struct mg_dns dns4; // DNS for IPv4 + struct mg_dns dns6; // DNS for IPv6 + int dnstimeout; // DNS resolve timeout in milliseconds + unsigned long nextid; // Next connection ID + void* userdata; // Arbitrary user data pointer +#if MG_ARCH == MG_ARCH_FREERTOS_TCP + SocketSet_t ss; // NOTE(lsm): referenced from socket struct +#endif +}; + +struct mg_connection { + struct mg_connection* next; // Linkage in struct mg_mgr :: connections + struct mg_mgr* mgr; // Our container + struct mg_addr peer; // Remote address. For listeners, local address + void* fd; // Connected socket, or LWIP data + unsigned long id; // Auto-incrementing unique connection ID + struct mg_iobuf recv; // Incoming data + struct mg_iobuf send; // Outgoing data + mg_event_handler_t fn; // User-specified event handler function + void* fn_data; // User-specified function parameter + mg_event_handler_t pfn; // Protocol-specific handler function + void* pfn_data; // Protocol-specific function parameter + char label[50]; // Arbitrary label + void* tls; // TLS specific data + unsigned is_listening : 1; // Listening connection + unsigned is_client : 1; // Outbound (client) connection + unsigned is_accepted : 1; // Accepted (server) connection + unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress + unsigned is_connecting : 1; // Non-blocking connect is in progress + unsigned is_tls : 1; // TLS-enabled connection + unsigned is_tls_hs : 1; // TLS handshake is in progress + unsigned is_udp : 1; // UDP connection + unsigned is_websocket : 1; // WebSocket connection + unsigned is_hexdumping : 1; // Hexdump in/out traffic + unsigned is_draining : 1; // Send remaining data, then close and free + unsigned is_closing : 1; // Close and free the connection immediately + unsigned is_readable : 1; // Connection is ready to read + unsigned is_writable : 1; // Connection is ready to write +}; + +void mg_mgr_poll(struct mg_mgr*, int ms); +void mg_mgr_init(struct mg_mgr*); +void mg_mgr_free(struct mg_mgr*); + +struct mg_connection* mg_listen(struct mg_mgr*, const char* url, + mg_event_handler_t fn, void* fn_data); +struct mg_connection* mg_connect(struct mg_mgr*, const char* url, + mg_event_handler_t fn, void* fn_data); +bool mg_send(struct mg_connection*, const void*, size_t); +int mg_printf(struct mg_connection*, const char* fmt, ...); +int mg_vprintf(struct mg_connection*, const char* fmt, va_list ap); +char* mg_straddr(struct mg_connection*, char*, size_t); +bool mg_aton(struct mg_str str, struct mg_addr* addr); +char* mg_ntoa(const struct mg_addr* addr, char* buf, size_t len); + +struct mg_connection* mg_mkpipe(struct mg_mgr*, mg_event_handler_t, void*); +void mg_mgr_wakeup(struct mg_connection* pipe); + + + + + + + +struct mg_http_header { + struct mg_str name; // Header name + struct mg_str value; // Header value +}; + +struct mg_http_message { + struct mg_str method, uri, query, proto; // Request/response line + struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers + struct mg_str body; // Body + struct mg_str head; // Request + headers + struct mg_str chunk; // Chunk for chunked encoding, or partial body + struct mg_str message; // Request + headers + body +}; + +// Parameter for mg_http_serve_dir() +struct mg_http_serve_opts { + const char* root_dir; // Web root directory, must be non-NULL + const char* ssi_pattern; // SSI file name pattern, e.g. #.shtml + const char* extra_headers; // Extra HTTP headers to add in responses + const char* mime_types; // Extra mime types, ext1=type1,ext2=type2,.. + struct mg_fs* fs; // Filesystem implementation. Use NULL for POSIX +}; + +// Parameter for mg_http_next_multipart +struct mg_http_part { + struct mg_str name; // Form field name + struct mg_str filename; // Filename for file uploads + struct mg_str body; // Part contents +}; + +int mg_http_parse(const char* s, size_t len, struct mg_http_message*); +int mg_http_get_request_len(const unsigned char* buf, size_t buf_len); +void mg_http_printf_chunk(struct mg_connection* cnn, const char* fmt, ...); +void mg_http_write_chunk(struct mg_connection* c, const char* buf, size_t len); +void mg_http_delete_chunk(struct mg_connection* c, struct mg_http_message* hm); +struct mg_connection* mg_http_listen(struct mg_mgr*, const char* url, + mg_event_handler_t fn, void* fn_data); +struct mg_connection* mg_http_connect(struct mg_mgr*, const char* url, + mg_event_handler_t fn, void* fn_data); +void mg_http_serve_dir(struct mg_connection*, struct mg_http_message* hm, + struct mg_http_serve_opts* opts); +void mg_http_serve_file(struct mg_connection*, struct mg_http_message* hm, + const char* path, struct mg_http_serve_opts* opts); +void mg_http_reply(struct mg_connection*, int status_code, const char* headers, + const char* body_fmt, ...); +struct mg_str* mg_http_get_header(struct mg_http_message*, const char* name); +int mg_http_get_var(const struct mg_str*, const char* name, char*, size_t); +int mg_url_decode(const char* s, size_t n, char* to, size_t to_len, int form); +size_t mg_url_encode(const char* s, size_t n, char* buf, size_t len); +void mg_http_creds(struct mg_http_message*, char*, size_t, char*, size_t); +bool mg_http_match_uri(const struct mg_http_message*, const char* glob); +int mg_http_upload(struct mg_connection*, struct mg_http_message* hm, + const char* dir); +void mg_http_bauth(struct mg_connection*, const char* user, const char* pass); +struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); +size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part*); + + +void mg_http_serve_ssi(struct mg_connection* c, const char* root, + const char* fullpath); + + + +struct mg_tls_opts { + const char* ca; // CA certificate file. For both listeners and clients + const char* cert; // Certificate + const char* certkey; // Certificate key + const char* ciphers; // Cipher list + struct mg_str srvname; // If not empty, enables server name verification +}; + +void mg_tls_init(struct mg_connection*, struct mg_tls_opts*); +void mg_tls_free(struct mg_connection*); +long mg_tls_send(struct mg_connection*, const void* buf, size_t len); +long mg_tls_recv(struct mg_connection*, void* buf, size_t len); +void mg_tls_handshake(struct mg_connection*); + + +#define WEBSOCKET_OP_CONTINUE 0 +#define WEBSOCKET_OP_TEXT 1 +#define WEBSOCKET_OP_BINARY 2 +#define WEBSOCKET_OP_CLOSE 8 +#define WEBSOCKET_OP_PING 9 +#define WEBSOCKET_OP_PONG 10 + + + +struct mg_ws_message { + struct mg_str data; // Websocket message data + uint8_t flags; // Websocket message flags +}; + +struct mg_connection* mg_ws_connect(struct mg_mgr*, const char* url, + mg_event_handler_t fn, void* fn_data, + const char* fmt, ...); +void mg_ws_upgrade(struct mg_connection*, struct mg_http_message*, + const char* fmt, ...); +size_t mg_ws_send(struct mg_connection*, const char* buf, size_t len, int op); +size_t mg_ws_wrap(struct mg_connection*, size_t len, int op); + + + + +struct mg_connection* mg_sntp_connect(struct mg_mgr* mgr, const char* url, + mg_event_handler_t fn, void* fn_data); +void mg_sntp_send(struct mg_connection* c, unsigned long utc); +int mg_sntp_parse(const unsigned char* buf, size_t len, struct timeval* tv); + + + + + +#define MQTT_CMD_CONNECT 1 +#define MQTT_CMD_CONNACK 2 +#define MQTT_CMD_PUBLISH 3 +#define MQTT_CMD_PUBACK 4 +#define MQTT_CMD_PUBREC 5 +#define MQTT_CMD_PUBREL 6 +#define MQTT_CMD_PUBCOMP 7 +#define MQTT_CMD_SUBSCRIBE 8 +#define MQTT_CMD_SUBACK 9 +#define MQTT_CMD_UNSUBSCRIBE 10 +#define MQTT_CMD_UNSUBACK 11 +#define MQTT_CMD_PINGREQ 12 +#define MQTT_CMD_PINGRESP 13 +#define MQTT_CMD_DISCONNECT 14 + +#define MQTT_QOS(qos) ((qos) << 1) +#define MQTT_GET_QOS(flags) (((flags) &0x6) >> 1) +#define MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1) + +struct mg_mqtt_opts { + struct mg_str client_id; // Client ID + struct mg_str will_topic; // Will topic + struct mg_str will_message; // Will message + uint8_t qos; // Quality of service + bool will_retain; // Retain last will + bool clean; // Use clean session, 0 or 1 + uint16_t keepalive; // Keep-alive timer in seconds +}; + +struct mg_mqtt_message { + struct mg_str topic; // Parsed topic + struct mg_str data; // Parsed message + struct mg_str dgram; // Whole MQTT datagram, including headers + uint16_t id; // Set for PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH + uint8_t cmd; // MQTT command, one of MQTT_CMD_* + uint8_t qos; // Quality of service + uint8_t ack; // Connack return code. 0 - success +}; + +struct mg_connection* mg_mqtt_connect(struct mg_mgr*, const char* url, + struct mg_mqtt_opts* opts, + mg_event_handler_t fn, void* fn_data); +struct mg_connection* mg_mqtt_listen(struct mg_mgr* mgr, const char* url, + mg_event_handler_t fn, void* fn_data); +void mg_mqtt_login(struct mg_connection* c, const char* url, + struct mg_mqtt_opts* opts); +void mg_mqtt_pub(struct mg_connection* c, struct mg_str* topic, + struct mg_str* data, int qos, bool retain); +void mg_mqtt_sub(struct mg_connection*, struct mg_str* topic, int qos); +int mg_mqtt_parse(const uint8_t* buf, size_t len, struct mg_mqtt_message* m); +void mg_mqtt_send_header(struct mg_connection*, uint8_t cmd, uint8_t flags, + uint32_t len); +size_t mg_mqtt_next_sub(struct mg_mqtt_message* msg, struct mg_str* topic, + uint8_t* qos, size_t pos); +size_t mg_mqtt_next_unsub(struct mg_mqtt_message* msg, struct mg_str* topic, + size_t pos); +void mg_mqtt_ping(struct mg_connection*); +void mg_mqtt_pong(struct mg_connection*); +void mg_mqtt_disconnect(struct mg_connection*); + + + + + +// Mongoose sends DNS queries that contain only one question: +// either A (IPv4) or AAAA (IPv6) address lookup. +// Therefore, we expect zero or one answer. +// If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. +struct mg_dns_message { + uint16_t txnid; // Transaction ID + bool resolved; // Resolve successful, addr is set + struct mg_addr addr; // Resolved address + char name[256]; // Host name +}; + +struct mg_dns_header { + uint16_t txnid; // Transaction ID + uint16_t flags; + uint16_t num_questions; + uint16_t num_answers; + uint16_t num_authority_prs; + uint16_t num_other_prs; +}; + +// DNS resource record +struct mg_dns_rr { + uint16_t nlen; // Name or pointer length + uint16_t atype; // Address type + uint16_t aclass; // Address class + uint16_t alen; // Address length +}; + +void mg_resolve(struct mg_connection*, struct mg_str*, int); +void mg_resolve_cancel(struct mg_connection*); +bool mg_dns_parse(const uint8_t* buf, size_t len, struct mg_dns_message*); +size_t mg_dns_parse_rr(const uint8_t* buf, size_t len, size_t ofs, + bool is_question, struct mg_dns_rr*); +size_t mg_dns_decode_name(const uint8_t*, size_t, size_t, char*, size_t); + +#ifdef __cplusplus +} +#endif +#endif // MONGOOSE_H diff --git a/discovery/src/asapo_discovery/server/server.go b/discovery/src/asapo_discovery/server/server.go index b1292d7a182c298f9b89bf41c367768eaa72b0be..85657a77c0f82b077d9c401d6d2948352bbb9b99 100644 --- a/discovery/src/asapo_discovery/server/server.go +++ b/discovery/src/asapo_discovery/server/server.go @@ -1,8 +1,9 @@ package server import ( - "asapo_discovery/request_handler" "asapo_discovery/common" + "asapo_discovery/request_handler" + "github.com/prometheus/client_golang/prometheus" ) var requestHandler request_handler.Agent @@ -10,6 +11,15 @@ var requestHandler request_handler.Agent var settings common.Settings +var ( + nReqests = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "http_requests", + Help: "Number of discovery requests", + }, + ) +) + func SetHandler(rh request_handler.Agent) error { requestHandler = rh err := requestHandler.Init(settings) diff --git a/discovery/src/asapo_discovery/server/server_nottested.go b/discovery/src/asapo_discovery/server/server_nottested.go index b6e14de06fd681bf15c2accfe466d530f7b0cc08..72168156d6ce17856fa4512492b394ab6d35d794 100644 --- a/discovery/src/asapo_discovery/server/server_nottested.go +++ b/discovery/src/asapo_discovery/server/server_nottested.go @@ -13,15 +13,6 @@ import ( "strconv" ) -var ( - nReqests = prometheus.NewCounter( - prometheus.CounterOpts{ - Name: "http_requests", - Help: "Number of discovery requests", - }, - ) -) - func init() { prometheus.MustRegister(nReqests) } diff --git a/receiver/CMakeLists.txt b/receiver/CMakeLists.txt index dbfd7d1ae1ecf86c48ecb7928c0e1aef2f1eeb7e..5dd1bde163c50e9cdd81cd325526d39d847fdfaf 100644 --- a/receiver/CMakeLists.txt +++ b/receiver/CMakeLists.txt @@ -28,6 +28,8 @@ set(RECEIVER_CORE_FILES src/file_processors/write_file_processor.cpp src/file_processors/file_processor.cpp src/file_processors/receive_file_processor.cpp + src/metrics/receiver_prometheus_metrics.cpp + src/metrics/receiver_mongoose_server.cpp ) set(RDS_FILES diff --git a/receiver/src/main.cpp b/receiver/src/main.cpp index 78a16f45ec458770ba64ce7a0f0bee711377ec37..f88186cbe3a00934cb0ccc5deaa2955d859b9687 100644 --- a/receiver/src/main.cpp +++ b/receiver/src/main.cpp @@ -12,6 +12,9 @@ #include "receiver_data_server/net_server/rds_tcp_server.h" #include "receiver_data_server/net_server/rds_fabric_server.h" +#include "metrics/receiver_prometheus_metrics.h" +#include "metrics/receiver_mongoose_server.h" + asapo::Error ReadConfigFile(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " <config file>" << std::endl; @@ -88,10 +91,18 @@ int StartReceiver(const asapo::ReceiverConfig* config, asapo::SharedCache cache, return 0; } +asapo::Error StartMetricsServer() { + auto srv = std::unique_ptr<asapo::ReceiverMetricsServer>(new asapo::ReceiverMongooseServer()); + auto* provider = new asapo::ReceiverPrometheusMetrics(); + srv->ListenAndServe("7009", std::unique_ptr<asapo::ReceiverMetricsProvider>(provider)); + return nullptr; +} int main (int argc, char* argv[]) { asapo::ExitAfterPrintVersionIfNeeded("ASAPO Receiver", argc, argv); +// StartMetricsServer(); + auto err = ReadConfigFile(argc, argv); const auto& logger = asapo::GetDefaultReceiverLogger(); if (err) { diff --git a/receiver/src/metrics/receiver_metrics_provider.h b/receiver/src/metrics/receiver_metrics_provider.h new file mode 100644 index 0000000000000000000000000000000000000000..7d2130a567111af86b6eff8e3f6e175e3cc7428d --- /dev/null +++ b/receiver/src/metrics/receiver_metrics_provider.h @@ -0,0 +1,19 @@ +#ifndef ASAPO_RECEIVER_SRC_METRICS_RECEIVER_METRICS_PROVIDER_H_ +#define ASAPO_RECEIVER_SRC_METRICS_RECEIVER_METRICS_PROVIDER_H_ + +#include <string> + +namespace asapo { + +class ReceiverMetricsProvider { + public: + virtual std::string Metrics() const = 0; + virtual ~ReceiverMetricsProvider() = default; +}; + + +} + + + +#endif //ASAPO_RECEIVER_SRC_METRICS_RECEIVER_METRICS_PROVIDER_H_ diff --git a/receiver/src/metrics/receiver_metrics_server.h b/receiver/src/metrics/receiver_metrics_server.h new file mode 100644 index 0000000000000000000000000000000000000000..d2d668757b0fcb5772d8f8e796a5624b13708245 --- /dev/null +++ b/receiver/src/metrics/receiver_metrics_server.h @@ -0,0 +1,17 @@ +#ifndef ASAPO_RECEIVER_SRC_METRICS_RECEIVER_METRICS_SERVER_H_ +#define ASAPO_RECEIVER_SRC_METRICS_RECEIVER_METRICS_SERVER_H_ + +#include "asapo/common/error.h" +#include "receiver_metrics_provider.h" + +namespace asapo { + +class ReceiverMetricsServer { + public: + virtual Error ListenAndServe(std::string port, std::unique_ptr<ReceiverMetricsProvider> provider) = 0; + virtual ~ReceiverMetricsServer() = default; +}; + +} + +#endif //ASAPO_RECEIVER_SRC_METRICS_RECEIVER_METRICS_SERVER_H_ diff --git a/receiver/src/metrics/receiver_mongoose_server.cpp b/receiver/src/metrics/receiver_mongoose_server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7db058bbc7cecb4608e8a1169be3110a03340654 --- /dev/null +++ b/receiver/src/metrics/receiver_mongoose_server.cpp @@ -0,0 +1,37 @@ +#include "receiver_mongoose_server.h" + +#include "../../../3d_party/mongoose/mongoose.h" +#include "../../../3d_party/mongoose/mongoose.c" + +namespace asapo { + +static void fn(struct mg_connection* c, int ev, void* ev_data, void* fn_data) { + auto provider = static_cast<const ReceiverMetricsProvider*>(fn_data); + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message* hm = (struct mg_http_message*) ev_data; + if (mg_http_match_uri(hm, "/metrics")) { + mg_http_reply(c, 200, "", "{\"result\": %s}\n", provider->Metrics().c_str()); + } else { + mg_http_reply(c, 404, "", "%s", "Not found\n"); + } + } +} + +asapo::Error asapo::ReceiverMongooseServer::ListenAndServe(std::string port, + std::unique_ptr<ReceiverMetricsProvider> provider) { + struct mg_mgr mgr; // Event manager + mg_mgr_init(&mgr); // Initialise event manager + auto uri = "0.0.0.0:" + port; + if (mg_http_listen(&mgr, uri.c_str(), fn, (void*) provider.get()) == NULL) { + LOG(LL_ERROR, ("Cannot listen on %s. Use http://ADDR:PORT or :PORT", + port.c_str())); + exit(EXIT_FAILURE); + } + for (;;) mg_mgr_poll(&mgr, 1000); // Infinite event loop + mg_mgr_free(&mgr); + return nullptr; +} + +} + + diff --git a/receiver/src/metrics/receiver_mongoose_server.h b/receiver/src/metrics/receiver_mongoose_server.h new file mode 100644 index 0000000000000000000000000000000000000000..88587a7f6c79c0de515917d81b78a068d9f2f970 --- /dev/null +++ b/receiver/src/metrics/receiver_mongoose_server.h @@ -0,0 +1,17 @@ +#ifndef ASAPO_RECEIVER_SRC_METRICS_RECEIVER_MONGOOSE_SERVER_H_ +#define ASAPO_RECEIVER_SRC_METRICS_RECEIVER_MONGOOSE_SERVER_H_ + +#include "receiver_metrics_server.h" + +namespace asapo { +class ReceiverMongooseServer final : public ReceiverMetricsServer { + public: + Error ListenAndServe(std::string port, std::unique_ptr<ReceiverMetricsProvider> provider) override; + +}; + +} + + + +#endif //ASAPO_RECEIVER_SRC_METRICS_RECEIVER_MONGOOSE_SERVER_H_ diff --git a/receiver/src/metrics/receiver_prometheus_metrics.cpp b/receiver/src/metrics/receiver_prometheus_metrics.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a0a671a76ac97edb0d4575b91e208521afa82c60 --- /dev/null +++ b/receiver/src/metrics/receiver_prometheus_metrics.cpp @@ -0,0 +1,11 @@ +#include "receiver_prometheus_metrics.h" + +namespace asapo { + +std::string ReceiverPrometheusMetrics::Metrics() const { + return "hello"; +} + +} + + diff --git a/receiver/src/metrics/receiver_prometheus_metrics.h b/receiver/src/metrics/receiver_prometheus_metrics.h new file mode 100644 index 0000000000000000000000000000000000000000..aec5a7a217727bcb12e68ffea64e5cc3d9b1ad91 --- /dev/null +++ b/receiver/src/metrics/receiver_prometheus_metrics.h @@ -0,0 +1,16 @@ +#ifndef ASAPO_RECEIVER_SRC_METRICS_RECEIVER_PROMETHEUS_METRICS_H_ +#define ASAPO_RECEIVER_SRC_METRICS_RECEIVER_PROMETHEUS_METRICS_H_ + +#include "receiver_metrics_provider.h" + +namespace asapo { + +class ReceiverPrometheusMetrics final : public ReceiverMetricsProvider { + public: + std::string Metrics() const override; + +}; + +} + +#endif //ASAPO_RECEIVER_SRC_METRICS_RECEIVER_PROMETHEUS_METRICS_H_