// Copyright (c) 2004-2013 Sergey Lyubka // Copyright (c) 2013-2022 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.mongoose.ws/licensing/ // // SPDX-License-Identifier: GPL-2.0 or commercial #include "mongoose.h" #ifdef MG_ENABLE_LINES #line 1 "src/base64.c" #endif 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; uint64_t expire; uint16_t txnid; }; static void mg_sendnsreq(struct mg_connection *, struct mg_str *, int, struct mg_dns *, bool); static void mg_dns_free(struct mg_connection *c, struct dns_data *d) { LIST_DELETE(struct dns_data, (struct dns_data **) &c->mgr->active_dns_requests, d); free(d); } void mg_resolve_cancel(struct mg_connection *c) { struct dns_data *tmp, *d = (struct dns_data *) c->mgr->active_dns_requests; for (; d != NULL; d = tmp) { tmp = d->next; if (d->c == c) mg_dns_free(c, d); } } static size_t mg_dns_parse_name_depth(const uint8_t *s, size_t len, size_t ofs, char *to, size_t tolen, size_t j, int depth) { size_t i = 0; if (tolen > 0 && depth == 0) to[0] = '\0'; if (depth > 5) return 0; // MG_INFO(("ofs %lx %x %x", (unsigned long) ofs, s[ofs], s[ofs + 1])); 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 // MG_INFO(("PTR %lx", (unsigned long) ptr)); if (ptr + 1 < len && (s[ptr] & 0xc0) == 0 && mg_dns_parse_name_depth(s, len, ptr, to, tolen, j, 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 // MG_INFO(("--> [%s]", to)); } if (tolen > 0) to[tolen - 1] = '\0'; // Make sure make sure it is nul-term return i; } static 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, 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; // MG_INFO(("Q %lu %lu %hu/%hu", ofs, n, rr.atype, rr.aclass)); ofs += n; } for (i = 0; i < mg_ntohs(h->num_answers); i++) { if ((n = mg_dns_parse_rr(buf, len, ofs, false, &rr)) == 0) return false; // MG_INFO(("A -- %lu %lu %hu/%hu %s", ofs, n, rr.atype, rr.aclass, // dm->name)); 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) { uint64_t now = *(uint64_t *) ev_data; for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL; d = tmp) { tmp = d->next; // MG_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) { MG_ERROR(("Unexpected DNS response:")); mg_hexdump(c->recv.buf, c->recv.len); } else { // MG_VERBOSE(("%s %d", dm.name, dm.resolved)); for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL; d = tmp) { tmp = d->next; // MG_INFO(("d %p %hu %hu", d, d->txnid, dm.txnid)); if (dm.txnid != d->txnid) continue; if (d->c->is_resolving) { if (dm.resolved) { char buf[100]; dm.addr.port = d->c->rem.port; // Save port d->c->rem = dm.addr; // Copy resolved address MG_DEBUG(("%lu %s is %s", d->c->id, dm.name, mg_ntoa(&d->c->rem, buf, sizeof(buf)))); mg_connect_resolved(d->c); #if MG_ENABLE_IPV6 } else if (dm.addr.is_ip6 == false && dm.name[0] != '\0' && c->mgr->use_dns6 == false) { 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 { MG_ERROR(("%lu already resolved", d->c->id)); } mg_dns_free(c, d); resolved = 1; } } if (!resolved) MG_ERROR(("stray DNS reply")); c->recv.len = 0; } else if (ev == MG_EV_CLOSE) { for (d = (struct dns_data *) c->mgr->active_dns_requests; d != NULL; d = tmp) { tmp = d->next; mg_error(d->c, "DNS error"); mg_dns_free(c, d); } } (void) fn_data; } static bool 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; return mg_send(c, &pkt, sizeof(pkt.header) + n); } 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; // 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 { struct dns_data *reqs = (struct dns_data *) c->mgr->active_dns_requests; char buf[100]; d->txnid = reqs ? (uint16_t) (reqs->txnid + 1) : 1; d->next = (struct dns_data *) c->mgr->active_dns_requests; c->mgr->active_dns_requests = d; d->expire = mg_millis() + (uint64_t) ms; d->c = c; c->is_resolving = 1; MG_VERBOSE(("%lu resolving %.*s @ %s, txnid %hu", c->id, (int) name->len, name->ptr, mg_ntoa(&dnsc->c->rem, buf, sizeof(buf)), d->txnid)); if (!mg_dns_send(dnsc->c, name, d->txnid, ipv6)) { mg_error(dnsc->c, "DNS send"); } } } void mg_resolve(struct mg_connection *c, const char *url) { struct mg_str host = mg_url_host(url); c->rem.port = mg_htons(mg_url_port(url)); if (mg_aton(host, &c->rem)) { // host is an IP address, do not fire name resolution mg_connect_resolved(c); } else { // host is not an IP, send DNS resolution request struct mg_dns *dns = c->mgr->use_dns6 ? &c->mgr->dns6 : &c->mgr->dns4; mg_sendnsreq(c, &host, c->mgr->dnstimeout, dns, c->mgr->use_dns6); } } #ifdef MG_ENABLE_LINES #line 1 "src/event.c" #endif void mg_call(struct mg_connection *c, int ev, void *ev_data) { // Run user-defined handler first, in order to give it an ability // to intercept processing (e.g. clean input buffer) before the // protocol handler kicks in if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data); if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data); } void mg_error(struct mg_connection *c, const char *fmt, ...) { char buf[64]; va_list ap; va_start(ap, fmt); mg_vsnprintf(buf, sizeof(buf), fmt, &ap); va_end(ap); MG_ERROR(("%lu %p %s", c->id, c->fd, buf)); c->is_closing = 1; // Set is_closing before sending MG_EV_CALL mg_call(c, MG_EV_ERROR, buf); // Let user handler to override it } #ifdef MG_ENABLE_LINES #line 1 "src/fmt.c" #endif static void mg_pfn_iobuf_private(char ch, void *param, bool expand) { struct mg_iobuf *io = (struct mg_iobuf *) param; if (expand && io->len + 2 > io->size) mg_iobuf_resize(io, io->len + 2); if (io->len + 2 <= io->size) { io->buf[io->len++] = (uint8_t) ch; io->buf[io->len] = 0; } else if (io->len < io->size) { io->buf[io->len++] = 0; // Guarantee to 0-terminate } } static void mg_putchar_iobuf_static(char ch, void *param) { mg_pfn_iobuf_private(ch, param, false); } void mg_pfn_iobuf(char ch, void *param) { mg_pfn_iobuf_private(ch, param, true); } size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list *ap) { struct mg_iobuf io = {(uint8_t *) buf, len, 0, 0}; size_t n = mg_vxprintf(mg_putchar_iobuf_static, &io, fmt, ap); if (n < len) buf[n] = '\0'; return n; } size_t mg_snprintf(char *buf, size_t len, const char *fmt, ...) { va_list ap; size_t n; va_start(ap, fmt); n = mg_vsnprintf(buf, len, fmt, &ap); va_end(ap); return n; } char *mg_vmprintf(const char *fmt, va_list *ap) { struct mg_iobuf io = {0, 0, 0, 256}; mg_vxprintf(mg_pfn_iobuf, &io, fmt, ap); return (char *) io.buf; } char *mg_mprintf(const char *fmt, ...) { char *s; va_list ap; va_start(ap, fmt); s = mg_vmprintf(fmt, &ap); va_end(ap); return s; } size_t mg_xprintf(void (*out)(char, void *), void *ptr, const char *fmt, ...) { size_t len = 0; va_list ap; va_start(ap, fmt); len = mg_vxprintf(out, ptr, fmt, &ap); va_end(ap); return len; } static bool is_digit(int c) { return c >= '0' && c <= '9'; } static int addexp(char *buf, int e, int sign) { int n = 0; buf[n++] = 'e'; buf[n++] = (char) sign; if (e > 400) return 0; if (e < 10) buf[n++] = '0'; if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); buf[n++] = (char) (e + '0'); return n; } static int xisinf(double x) { union { double f; uint64_t u; } ieee754 = {x}; return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) == 0x7ff00000 && ((unsigned) ieee754.u == 0); } static int xisnan(double x) { union { double f; uint64_t u; } ieee754 = {x}; return ((unsigned) (ieee754.u >> 32) & 0x7fffffff) + ((unsigned) ieee754.u != 0) > 0x7ff00000; } static size_t mg_dtoa(char *dst, size_t dstlen, double d, int width) { char buf[40]; int i, s = 0, n = 0, e = 0; double t, mul, saved; if (d == 0.0) return mg_snprintf(dst, dstlen, "%s", "0"); if (xisinf(d)) return mg_snprintf(dst, dstlen, "%s", d > 0 ? "inf" : "-inf"); if (xisnan(d)) return mg_snprintf(dst, dstlen, "%s", "nan"); if (d < 0.0) d = -d, buf[s++] = '-'; // Round saved = d; mul = 1.0; while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; d += t; // Calculate exponent, and 'mul' for scientific representation mul = 1.0; while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; // printf(" --> %g %d %g %g\n", saved, e, t, mul); if (e >= width) { n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width); // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, n, buf); n += addexp(buf + s + n, e, '+'); return mg_snprintf(dst, dstlen, "%.*s", n, buf); } else if (e <= -width) { n = (int) mg_dtoa(buf, sizeof(buf), saved / mul, width); // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, n, buf); n += addexp(buf + s + n, -e, '-'); return mg_snprintf(dst, dstlen, "%.*s", n, buf); } else { for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { int ch = (int) (d / t); if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); d -= ch * t; t /= 10.0; } // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf); if (n == 0) buf[s++] = '0'; while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { int ch = (int) (d / t); buf[s + n++] = (char) (ch + '0'); d -= ch * t; t /= 10.0; } } while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot buf[s + n] = '\0'; return mg_snprintf(dst, dstlen, "%s", buf); } static size_t mg_lld(char *buf, int64_t val, bool is_signed, bool is_hex) { const char *letters = "0123456789abcdef"; uint64_t v = (uint64_t) val; size_t s = 0, n, i; if (is_signed && val < 0) buf[s++] = '-', v = (uint64_t) (-val); // This loop prints a number in reverse order. I guess this is because we // write numbers from right to left: least significant digit comes last. // Maybe because we use Arabic numbers, and Arabs write RTL? if (is_hex) { for (n = 0; v; v >>= 4) buf[s + n++] = letters[v & 15]; } else { for (n = 0; v; v /= 10) buf[s + n++] = letters[v % 10]; } // Reverse a string for (i = 0; i < n / 2; i++) { char t = buf[s + i]; buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; } if (val == 0) buf[n++] = '0'; // Handle special case return n + s; } static size_t scpy(void (*out)(char, void *), void *ptr, char *buf, size_t len) { size_t i = 0; while (i < len && buf[i] != '\0') out(buf[i++], ptr); return i; } static char mg_esc(int c, bool esc) { const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\""; for (p = esc ? esc1 : esc2; *p != '\0'; p++) { if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2]; } return 0; } static char mg_escape(int c) { return mg_esc(c, true); } static size_t qcpy(void (*out)(char, void *), void *ptr, char *buf, size_t len) { size_t i = 0, extra = 0; for (i = 0; i < len && buf[i] != '\0'; i++) { char c = mg_escape(buf[i]); if (c) { out('\\', ptr), out(c, ptr), extra++; } else { out(buf[i], ptr); } } return i + extra; } static size_t Qcpy(void (*out)(char, void *), void *ptr, char *buf, size_t len) { size_t n = 2; out('"', ptr); n += qcpy(out, ptr, buf, len); out('"', ptr); return n; } static size_t bcpy(void (*out)(char, void *), void *ptr, uint8_t *buf, size_t len) { size_t i, n = 0; const char *t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; out('"', ptr), n++; for (i = 0; i < len; i += 3) { uint8_t c1 = buf[i], c2 = i + 1 < len ? buf[i + 1] : 0, c3 = i + 2 < len ? buf[i + 2] : 0; char tmp[4] = {t[c1 >> 2], t[(c1 & 3) << 4 | (c2 >> 4)], '=', '='}; if (i + 1 < len) tmp[2] = t[(c2 & 15) << 2 | (c3 >> 6)]; if (i + 2 < len) tmp[3] = t[c3 & 63]; n += scpy(out, ptr, tmp, sizeof(tmp)); } out('"', ptr), n++; return n; } size_t mg_vxprintf(void (*out)(char, void *), void *param, const char *fmt, va_list *ap) { size_t i = 0, n = 0; while (fmt[i] != '\0') { if (fmt[i] == '%') { size_t j, k, x = 0, is_long = 0, w = 0 /* width */, pr = ~0U /* prec */; char pad = ' ', minus = 0, c = fmt[++i]; if (c == '#') x++, c = fmt[++i]; if (c == '-') minus++, c = fmt[++i]; if (c == '0') pad = '0', c = fmt[++i]; while (is_digit(c)) w *= 10, w += (size_t) (c - '0'), c = fmt[++i]; if (c == '.') { c = fmt[++i]; if (c == '*') { pr = (size_t) va_arg(*ap, int); c = fmt[++i]; } else { pr = 0; while (is_digit(c)) pr *= 10, pr += (size_t) (c - '0'), c = fmt[++i]; } } while (c == 'h') c = fmt[++i]; // Treat h and hh as int if (c == 'l') { is_long++, c = fmt[++i]; if (c == 'l') is_long++, c = fmt[++i]; } if (c == 'p') x = 1, is_long = 1; if (c == 'd' || c == 'u' || c == 'x' || c == 'X' || c == 'p' || c == 'g' || c == 'f') { bool s = (c == 'd'), h = (c == 'x' || c == 'X' || c == 'p'); char tmp[40]; size_t xl = x ? 2 : 0; if (c == 'g' || c == 'f') { double v = va_arg(*ap, double); if (pr == ~0U) pr = 6; k = mg_dtoa(tmp, sizeof(tmp), v, (int) pr); } else if (is_long == 2) { int64_t v = va_arg(*ap, int64_t); k = mg_lld(tmp, v, s, h); } else if (is_long == 1) { long v = va_arg(*ap, long); k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned long) v, s, h); } else { int v = va_arg(*ap, int); k = mg_lld(tmp, s ? (int64_t) v : (int64_t) (unsigned) v, s, h); } for (j = 0; j < xl && w > 0; j++) w--; for (j = 0; pad == ' ' && !minus && k < w && j + k < w; j++) n += scpy(out, param, &pad, 1); n += scpy(out, param, (char *) "0x", xl); for (j = 0; pad == '0' && k < w && j + k < w; j++) n += scpy(out, param, &pad, 1); n += scpy(out, param, tmp, k); for (j = 0; pad == ' ' && minus && k < w && j + k < w; j++) n += scpy(out, param, &pad, 1); } else if (c == 'M') { mg_pm_t f = va_arg(*ap, mg_pm_t); n += f(out, param, ap); } else if (c == 'c') { int ch = va_arg(*ap, int); out((char) ch, param); n++; } else if (c == 'H') { // Print hex-encoded double-quoted string size_t bl = (size_t) va_arg(*ap, int); uint8_t *p = va_arg(*ap, uint8_t *), dquote = '"'; const char *hex = "0123456789abcdef"; n += scpy(out, param, (char *) &dquote, 1); for (j = 0; j < bl; j++) { n += scpy(out, param, (char *) &hex[(p[j] >> 4) & 15], 1); n += scpy(out, param, (char *) &hex[p[j] & 15], 1); } n += scpy(out, param, (char *) &dquote, 1); } else if (c == 'V') { // Print base64-encoded double-quoted string size_t len = (size_t) va_arg(*ap, int); uint8_t *buf = va_arg(*ap, uint8_t *); n += bcpy(out, param, buf, len); } else if (c == 's' || c == 'Q' || c == 'q') { char *p = va_arg(*ap, char *); size_t (*f)(void (*)(char, void *), void *, char *, size_t) = scpy; if (c == 'Q') f = Qcpy; if (c == 'q') f = qcpy; if (pr == ~0U) pr = p == NULL ? 0 : strlen(p); for (j = 0; !minus && pr < w && j + pr < w; j++) n += f(out, param, &pad, 1); n += f(out, param, p, pr); for (j = 0; minus && pr < w && j + pr < w; j++) n += f(out, param, &pad, 1); } else if (c == '%') { out('%', param); n++; } else { out('%', param); out(c, param); n += 2; } i++; } else { out(fmt[i], param), n++, i++; } } return n; } #ifdef MG_ENABLE_LINES #line 1 "src/fs.c" #endif struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags) { struct mg_fd *fd = (struct mg_fd *) calloc(1, sizeof(*fd)); if (fd != NULL) { fd->fd = fs->op(path, flags); fd->fs = fs; if (fd->fd == NULL) { free(fd); fd = NULL; } } return fd; } void mg_fs_close(struct mg_fd *fd) { if (fd != NULL) { fd->fs->cl(fd->fd); free(fd); } } char *mg_file_read(struct mg_fs *fs, const char *path, size_t *sizep) { struct mg_fd *fd; char *data = NULL; size_t size = 0; fs->st(path, &size, NULL); if ((fd = mg_fs_open(fs, path, MG_FS_READ)) != NULL) { data = (char *) calloc(1, size + 1); if (data != NULL) { if (fs->rd(fd->fd, data, size) != size) { free(data); data = NULL; } else { data[size] = '\0'; if (sizep != NULL) *sizep = size; } } mg_fs_close(fd); } return data; } bool mg_file_write(struct mg_fs *fs, const char *path, const void *buf, size_t len) { bool result = false; struct mg_fd *fd; char tmp[MG_PATH_MAX]; mg_snprintf(tmp, sizeof(tmp), "%s..%d", path, rand()); if ((fd = mg_fs_open(fs, tmp, MG_FS_WRITE)) != NULL) { result = fs->wr(fd->fd, buf, len) == len; mg_fs_close(fd); if (result) { fs->rm(path); fs->mv(tmp, path); } else { fs->rm(tmp); } } return result; } bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) { va_list ap; char *data; bool result = false; va_start(ap, fmt); data = mg_vmprintf(fmt, &ap); va_end(ap); result = mg_file_write(fs, path, data, strlen(data)); free(data); return result; } #ifdef MG_ENABLE_LINES #line 1 "src/fs_fat.c" #endif #if MG_ENABLE_FATFS #include static int mg_days_from_epoch(int y, int m, int d) { y -= m <= 2; int era = y / 400; int yoe = y - era * 400; int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; return era * 146097 + doe - 719468; } static time_t mg_timegm(const struct tm *t) { int year = t->tm_year + 1900; int month = t->tm_mon; // 0-11 if (month > 11) { year += month / 12; month %= 12; } else if (month < 0) { int years_diff = (11 - month) / 12; year -= years_diff; month += 12 * years_diff; } int x = mg_days_from_epoch(year, month + 1, t->tm_mday); return 60 * (60 * (24L * x + t->tm_hour) + t->tm_min) + t->tm_sec; } static time_t ff_time_to_epoch(uint16_t fdate, uint16_t ftime) { struct tm tm; memset(&tm, 0, sizeof(struct tm)); tm.tm_sec = (ftime << 1) & 0x3e; tm.tm_min = ((ftime >> 5) & 0x3f); tm.tm_hour = ((ftime >> 11) & 0x1f); tm.tm_mday = (fdate & 0x1f); tm.tm_mon = ((fdate >> 5) & 0x0f) - 1; tm.tm_year = ((fdate >> 9) & 0x7f) + 80; return mg_timegm(&tm); } static int ff_stat(const char *path, size_t *size, time_t *mtime) { FILINFO fi; if (path[0] == '\0') { if (size) *size = 0; if (mtime) *mtime = 0; return MG_FS_DIR; } else if (f_stat(path, &fi) == 0) { if (size) *size = (size_t) fi.fsize; if (mtime) *mtime = ff_time_to_epoch(fi.fdate, fi.ftime); return MG_FS_READ | MG_FS_WRITE | ((fi.fattrib & AM_DIR) ? MG_FS_DIR : 0); } else { return 0; } } static void ff_list(const char *dir, void (*fn)(const char *, void *), void *userdata) { DIR d; FILINFO fi; if (f_opendir(&d, dir) == FR_OK) { while (f_readdir(&d, &fi) == FR_OK && fi.fname[0] != '\0') { if (!strcmp(fi.fname, ".") || !strcmp(fi.fname, "..")) continue; fn(fi.fname, userdata); } f_closedir(&d); } } static void *ff_open(const char *path, int flags) { FIL f; unsigned char mode = FA_READ; if (flags & MG_FS_WRITE) mode |= FA_WRITE | FA_OPEN_ALWAYS | FA_OPEN_APPEND; if (f_open(&f, path, mode) == 0) { FIL *fp = calloc(1, sizeof(*fp)); memcpy(fp, &f, sizeof(*fp)); return fp; } else { return NULL; } } static void ff_close(void *fp) { if (fp != NULL) { f_close((FIL *) fp); free(fp); } } static size_t ff_read(void *fp, void *buf, size_t len) { UINT n = 0, misalign = ((size_t) buf) & 3; if (misalign) { char aligned[4]; f_read((FIL *) fp, aligned, len > misalign ? misalign : len, &n); memcpy(buf, aligned, n); } else { f_read((FIL *) fp, buf, len, &n); } return n; } static size_t ff_write(void *fp, const void *buf, size_t len) { UINT n = 0; return f_write((FIL *) fp, (char *) buf, len, &n) == FR_OK ? n : 0; } static size_t ff_seek(void *fp, size_t offset) { f_lseek((FIL *) fp, offset); return offset; } static bool ff_rename(const char *from, const char *to) { return f_rename(from, to) == FR_OK; } static bool ff_remove(const char *path) { return f_unlink(path) == FR_OK; } static bool ff_mkdir(const char *path) { return f_mkdir(path) == FR_OK; } struct mg_fs mg_fs_fat = {ff_stat, ff_list, ff_open, ff_close, ff_read, ff_write, ff_seek, ff_rename, ff_remove, ff_mkdir}; #endif #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) { // MG_INFO(("[%.*s] [%s] %c", (int) n, prefix, path, path[n])); return n < strlen(path) && strncmp(prefix, path, n) == 0 && (n == 0 || path[n] == '/' || path[n - 1] == '/'); } 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[MG_PATH_MAX], 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); mg_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 void *packed_open(const char *path, int flags) { size_t size = 0; const char *data = mg_unpack(path, &size, NULL); struct packed_file *fp = NULL; if (data == NULL) return NULL; if (flags & MG_FS_WRITE) return NULL; fp = (struct packed_file *) calloc(1, sizeof(*fp)); fp->size = size; fp->data = data; return (void *) fp; } static void packed_close(void *fp) { if (fp != NULL) free(fp); } 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; } static bool packed_rename(const char *from, const char *to) { (void) from, (void) to; return false; } static bool packed_remove(const char *path) { (void) path; return false; } static bool packed_mkdir(const char *path) { (void) path; return false; } struct mg_fs mg_fs_packed = { packed_stat, packed_list, packed_open, packed_close, packed_read, packed_write, packed_seek, packed_rename, packed_remove, packed_mkdir}; #ifdef MG_ENABLE_LINES #line 1 "src/fs_posix.c" #endif #if MG_ENABLE_FILE #ifndef MG_STAT_STRUCT #define MG_STAT_STRUCT stat #endif #ifndef MG_STAT_FUNC #define MG_STAT_FUNC stat #endif static int p_stat(const char *path, size_t *size, time_t *mtime) { #if !defined(S_ISDIR) MG_ERROR(("stat() API is not supported. %p %p %p", path, size, mtime)); return 0; #else #if MG_ARCH == MG_ARCH_WIN32 struct _stati64 st; wchar_t tmp[MG_PATH_MAX]; MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); if (_wstati64(tmp, &st) != 0) return 0; #else struct MG_STAT_STRUCT st; if (MG_STAT_FUNC(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); #endif } #if MG_ARCH == MG_ARCH_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 void *p_open(const char *path, int flags) { const char *mode = flags == MG_FS_READ ? "rb" : "a+b"; #if MG_ARCH == MG_ARCH_WIN32 wchar_t b1[MG_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])); return (void *) _wfopen(b1, b2); #else return (void *) fopen(path, mode); #endif } static void p_close(void *fp) { fclose((FILE *) fp); } 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 (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64) || \ (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L) || \ (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) if (fseeko((FILE *) fp, (off_t) offset, SEEK_SET) != 0) (void) 0; #else if (fseek((FILE *) fp, (long) offset, SEEK_SET) != 0) (void) 0; #endif return (size_t) ftell((FILE *) fp); } static bool p_rename(const char *from, const char *to) { return rename(from, to) == 0; } static bool p_remove(const char *path) { return remove(path) == 0; } static bool p_mkdir(const char *path) { return mkdir(path, 0775) == 0; } #else 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 void *p_open(const char *path, int flags) { (void) path, (void) flags; return NULL; } static void p_close(void *fp) { (void) fp; } 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; } static bool p_rename(const char *from, const char *to) { (void) from, (void) to; return false; } static bool p_remove(const char *path) { (void) path; return false; } static bool p_mkdir(const char *path) { (void) path; return false; } #endif struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, p_write, p_seek, p_rename, p_remove, p_mkdir}; #ifdef MG_ENABLE_LINES #line 1 "src/http.c" #endif // Chunk deletion marker is the MSB in the "processed" counter #define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1)) // 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; // MG_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; // MG_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); // MG_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 { MG_ERROR(("%lu %s cannot resize iobuf %d->%d ", c->id, c->label, (int) c->send.size, (int) need)); } } struct mg_str mg_http_var(struct mg_str buf, struct mg_str name) { struct mg_str k, v, result = mg_str_n(NULL, 0); while (mg_split(&buf, &k, &v, '&')) { if (name.len == k.len && mg_ncasecmp(name.ptr, k.ptr, k.len) == 0) { result = v; break; } } return result; } int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, size_t dst_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 { struct mg_str v = mg_http_var(*buf, mg_str(name)); if (v.ptr == NULL) { len = -4; // Name does not exist } else { len = mg_url_decode(v.ptr, v.len, dst, dst_len, 1); if (len < 0) len = -3; // Failed to decode } } return len; } static bool isx(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } 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 && isx(src[i + 1]) && isx(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; } static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c >= ' '; } 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 (!isok(buf[i])) 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; } static 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; // MG_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) { size_t len = c->send.len; va_list tmp; mg_send(c, " \r\n", 10); va_copy(tmp, ap); mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &tmp); va_end(tmp); if (c->send.len >= len + 10) { mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10); c->send.buf[len + 8] = '\r'; if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker } mg_send(c, "\r\n", 2); } 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); if (len == 0) c->is_resp = 0; } // clang-format off static const char *mg_http_status_code_str(int status_code) { switch (status_code) { case 100: return "Continue"; case 201: return "Created"; case 202: return "Accepted"; case 204: return "No Content"; case 206: return "Partial Content"; case 301: return "Moved Permanently"; case 302: return "Found"; case 304: return "Not Modified"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 403: return "Forbidden"; case 404: return "Not Found"; case 418: return "I'm a teapot"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; default: return "OK"; } } // clang-format on void mg_http_reply(struct mg_connection *c, int code, const char *headers, const char *fmt, ...) { va_list ap; size_t len; mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: \r\n\r\n", code, mg_http_status_code_str(code), headers == NULL ? "" : headers); len = c->send.len; va_start(ap, fmt); mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &ap); va_end(ap); if (c->send.len > 15) { mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu", (unsigned long) (c->send.len - len)); c->is_resp = 0; c->send.buf[len - 4] = '\r'; // Change ending 0 to space } c->is_resp = 0; } static void http_cb(struct mg_connection *, int, void *, void *); static void restore_http_cb(struct mg_connection *c) { mg_fs_close((struct mg_fd *) c->pfn_data); c->pfn_data = NULL; c->pfn = http_cb; c->is_resp = 0; } char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime); char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { mg_snprintf(buf, len, "\"%lld.%lld\"", (int64_t) mtime, (int64_t) size); return buf; } 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 = MG_IO_SIZE, space, *cl = (size_t *) c->label; if (c->send.size < max) mg_iobuf_resize(&c->send, max); if (c->send.len >= c->send.size) return; // Rate limit if ((space = c->send.size - c->send.len) > *cl) space = *cl; n = fd->fs->rd(fd->fd, c->send.buf + c->send.len, space); c->send.len += n; *cl -= n; if (n == 0) restore_http_cb(c); } else if (ev == MG_EV_CLOSE) { restore_http_cb(c); } (void) ev_data; } // Known mime types. Keep it outside guess_content_type() function, since // some environments don't like it defined there. // clang-format off static struct mg_str s_known_types[] = { MG_C_STR("html"), MG_C_STR("text/html; charset=utf-8"), MG_C_STR("htm"), MG_C_STR("text/html; charset=utf-8"), MG_C_STR("css"), MG_C_STR("text/css; charset=utf-8"), MG_C_STR("js"), MG_C_STR("text/javascript; charset=utf-8"), MG_C_STR("gif"), MG_C_STR("image/gif"), MG_C_STR("png"), MG_C_STR("image/png"), MG_C_STR("jpg"), MG_C_STR("image/jpeg"), MG_C_STR("jpeg"), MG_C_STR("image/jpeg"), MG_C_STR("woff"), MG_C_STR("font/woff"), MG_C_STR("ttf"), MG_C_STR("font/ttf"), MG_C_STR("svg"), MG_C_STR("image/svg+xml"), MG_C_STR("txt"), MG_C_STR("text/plain; charset=utf-8"), MG_C_STR("avi"), MG_C_STR("video/x-msvideo"), MG_C_STR("csv"), MG_C_STR("text/csv"), MG_C_STR("doc"), MG_C_STR("application/msword"), MG_C_STR("exe"), MG_C_STR("application/octet-stream"), MG_C_STR("gz"), MG_C_STR("application/gzip"), MG_C_STR("ico"), MG_C_STR("image/x-icon"), MG_C_STR("json"), MG_C_STR("application/json"), MG_C_STR("mov"), MG_C_STR("video/quicktime"), MG_C_STR("mp3"), MG_C_STR("audio/mpeg"), MG_C_STR("mp4"), MG_C_STR("video/mp4"), MG_C_STR("mpeg"), MG_C_STR("video/mpeg"), MG_C_STR("pdf"), MG_C_STR("application/pdf"), MG_C_STR("shtml"), MG_C_STR("text/html; charset=utf-8"), MG_C_STR("tgz"), MG_C_STR("application/tar-gz"), MG_C_STR("wav"), MG_C_STR("audio/wav"), MG_C_STR("webp"), MG_C_STR("image/webp"), MG_C_STR("zip"), MG_C_STR("application/zip"), MG_C_STR("3gp"), MG_C_STR("video/3gpp"), {0, 0}, }; // clang-format on static struct mg_str guess_content_type(struct mg_str path, const char *extra) { struct mg_str k, v, s = mg_str(extra); size_t i = 0; // 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; s_known_types[i].ptr != NULL; i += 2) { if (mg_strcmp(path, s_known_types[i]) == 0) return s_known_types[i + 1]; } 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; // MG_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); // MG_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++; // MG_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, const struct mg_http_serve_opts *opts) { char etag[64], tmp[MG_PATH_MAX]; struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; struct mg_fd *fd = path == NULL ? NULL : mg_fs_open(fs, path, MG_FS_READ); size_t size = 0; time_t mtime = 0; struct mg_str *inm = NULL; struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); bool gzip = false; // If file does not exist, we try to open file PATH.gz - and if such // pre-compressed .gz file exists, serve it with the Content-Encoding: gzip // Note - we ignore Accept-Encoding, cause we don't have a choice if (fd == NULL) { MG_DEBUG(("NULL [%s]", path)); mg_snprintf(tmp, sizeof(tmp), "%s.gz", path); if ((fd = mg_fs_open(fs, tmp, MG_FS_READ)) != NULL) { gzip = true; path = tmp; } else if (opts->page404 != NULL) { // No precompressed file, serve 404 fd = mg_fs_open(fs, opts->page404, MG_FS_READ); mime = guess_content_type(mg_str(path), opts->mime_types); path = opts->page404; } } if (fd == NULL || fs->st(path, &size, &mtime) == 0) { mg_http_reply(c, 404, opts->extra_headers, "Not found\n"); mg_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) { mg_fs_close(fd); mg_http_reply(c, 304, opts->extra_headers, ""); } else { int n, status = 200; char range[100]; int64_t r1 = 0, r2 = 0, cl = (int64_t) size; // Handle Range header struct mg_str *rh = mg_http_get_header(hm, "Range"); range[0] = '\0'; 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; mg_snprintf(range, sizeof(range), "Content-Range: bytes */%lld\r\n", (int64_t) size); } else { status = 206; cl = r2 - r1 + 1; mg_snprintf(range, sizeof(range), "Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1, (int64_t) size); fs->sk(fd->fd, (size_t) r1); } } mg_printf(c, "HTTP/1.1 %d %s\r\n" "Content-Type: %.*s\r\n" "Etag: %s\r\n" "Content-Length: %llu\r\n" "%s%s%s\r\n", status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range, opts->extra_headers ? opts->extra_headers : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { c->is_draining = 1; c->is_resp = 0; mg_fs_close(fd); } else { c->pfn = static_cb; c->pfn_data = fd; *(size_t *) c->label = (size_t) cl; // Track to-be-sent content length } } } struct printdirentrydata { struct mg_connection *c; struct mg_http_message *hm; const 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 t = 0; char path[MG_PATH_MAX], sz[40], mod[40]; int flags, n = 0; // MG_DEBUG(("[%s] [%s]", d->dir, name)); if (mg_snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) > sizeof(path)) { MG_ERROR(("%s truncated", name)); } else if ((flags = fs->st(path, &size, &t)) == 0) { MG_ERROR(("%lu stat(%s): %d", d->c->id, path, errno)); } else { const char *slash = flags & MG_FS_DIR ? "/" : ""; if (flags & MG_FS_DIR) { mg_snprintf(sz, sizeof(sz), "%s", "[DIR]"); } else { mg_snprintf(sz, sizeof(sz), "%lld", (uint64_t) size); } #if defined(MG_HTTP_DIRLIST_TIME) char time_str[30]; struct tm * time_info = localtime(&t); strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info); mg_snprintf(mod, sizeof(mod), "%s", time_str); #elif defined(MG_HTTP_DIRLIST_TIME_UTC) char time_str[30]; struct tm * time_info = gmtime(&t); strftime(time_str, sizeof time_str, "%Y/%m/%d %H:%M:%S", time_info); mg_snprintf(mod, sizeof(mod), "%s", time_str); #else mg_snprintf(mod, sizeof(mod), "%ld", (unsigned long) t); #endif n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); mg_printf(d->c, " %s%s" "%s%s\n", n, path, slash, name, slash, (unsigned long) t, mod, flags & MG_FS_DIR ? (int64_t) -1 : (int64_t) size, sz); } } static void listdir(struct mg_connection *c, struct mg_http_message *hm, const struct mg_http_serve_opts *opts, char *dir) { const char *sort_js_code = ""; struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; struct printdirentrydata d = {c, hm, opts, dir}; char tmp[10], buf[MG_PATH_MAX]; size_t off, n; int len = mg_url_decode(hm->uri.ptr, hm->uri.len, buf, sizeof(buf), 0); struct mg_str uri = len > 0 ? mg_str_n(buf, (size_t) len) : hm->uri; 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, "Index of %.*s%s%s" "" "

Index of %.*s

" "" "" "" "" "\n", (int) uri.len, uri.ptr, sort_js_code, sort_js_code2, (int) uri.len, uri.ptr); mg_printf(c, "%s", " " "\n"); fs->ls(dir, printdirentry, &d); mg_printf(c, "" "
Name" "ModifiedSize

..[DIR]

Mongoose v.%s
\n", MG_VERSION); n = mg_snprintf(tmp, sizeof(tmp), "%lu", (unsigned long) (c->send.len - off)); if (n > sizeof(tmp)) n = 0; memcpy(c->send.buf + off - 12, tmp, n); // Set content length c->is_resp = 0; // Mark response end } // Resolve requested file into `path` and return its fs->st() 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, tmp; // Append URI to the root_dir, and sanitize it size_t n = mg_snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr); if (n > path_size) n = path_size; path[path_size - 1] = '\0'; 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 mg_remove_double_dots(path); n = strlen(path); while (n > 1 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes flags = mg_vcmp(&hm->uri, "/") == 0 ? MG_FS_DIR : fs->st(path, NULL, NULL); MG_VERBOSE(("%lu %.*s -> %s %d", c->id, (int) hm->uri.len, hm->uri.ptr, path, flags)); if (flags == 0) { // Do nothing - let's caller decide } else if ((flags & MG_FS_DIR) && hm->uri.len > 0 && hm->uri.ptr[hm->uri.len - 1] != '/') { mg_printf(c, "HTTP/1.1 301 Moved\r\n" "Location: %.*s/\r\n" "Content-Length: 0\r\n" "\r\n", (int) hm->uri.len, hm->uri.ptr); flags = -1; } else if (flags & MG_FS_DIR) { if (((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX) > 0 && (tmp = fs->st(path, NULL, NULL)) != 0) || (mg_snprintf(path + n, path_size - n, "/index.shtml") > 0 && (tmp = fs->st(path, NULL, NULL)) != 0))) { flags = tmp; } else if ((mg_snprintf(path + n, path_size - n, "/" MG_HTTP_INDEX ".gz") > 0 && (tmp = fs->st(path, NULL, NULL)) != 0)) { // check for gzipped index flags = tmp; path[n + 1 + strlen(MG_HTTP_INDEX)] = '\0'; // Remove appended .gz in index file name } 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, const 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, const 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) { // Do nothing: the response has already been sent by uri_to_path() } else if (flags & MG_FS_DIR) { listdir(c, hm, opts, path); } else if (flags && 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) { mg_snprintf(user, userlen, "%.*s", (int) (p - buf), buf); mg_snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1); } } else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) { mg_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) mg_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; v.len > 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 != ',')) p++; // MG_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_match(hm->uri, mg_str(glob), NULL); } long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm, struct mg_fs *fs, const char *path, size_t max_size) { char buf[20] = "0"; long res = 0, offset; mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); offset = strtol(buf, NULL, 0); if (hm->body.len == 0) { mg_http_reply(c, 200, "", "%ld", res); // Nothing to write } else { struct mg_fd *fd; size_t current_size = 0; MG_DEBUG(("%s -> %d bytes @ %ld", path, (int) hm->body.len, offset)); if (offset == 0) fs->rm(path); // If offset if 0, truncate file fs->st(path, ¤t_size, NULL); if (offset < 0) { mg_http_reply(c, 400, "", "offset required"); res = -1; } else if (offset > 0 && current_size != (size_t) offset) { mg_http_reply(c, 400, "", "%s: offset mismatch", path); res = -2; } else if ((size_t) offset + hm->body.len > max_size) { mg_http_reply(c, 400, "", "%s: over max size of %lu", path, (unsigned long) max_size); res = -3; } else if ((fd = mg_fs_open(fs, path, MG_FS_WRITE)) == NULL) { mg_http_reply(c, 400, "", "open(%s): %d", path, errno); res = -4; } else { res = offset + (long) fs->wr(fd->fd, hm->body.ptr, hm->body.len); mg_fs_close(fd); mg_http_reply(c, 200, "", "%ld", res); } } return res; } int mg_http_status(const struct mg_http_message *hm) { return atoi(hm->uri.ptr); } // If a server sends data to the client using chunked encoding, Mongoose strips // off the chunking prefix (hex length and \r\n) and suffix (\r\n), appends the // stripped data to the body, and fires the MG_EV_HTTP_CHUNK event. When zero // chunk is received, we fire MG_EV_HTTP_MSG, and the body already has all // chunking prefixes/suffixes stripped. // // If a server sends data without chunked encoding, we also fire a series of // MG_EV_HTTP_CHUNK events for every received piece of data, and then we fire // MG_EV_HTTP_MSG event in the end. // // We track total processed length in the c->pfn_data, which is a void * // pointer: we store a size_t value there. static bool getchunk(struct mg_str s, size_t *prefixlen, size_t *datalen) { size_t i = 0, n; while (i < s.len && s.ptr[i] != '\r' && s.ptr[i] != '\n') i++; n = mg_unhexn(s.ptr, i); // MG_INFO(("%d %d", (int) (i + n + 4), (int) s.len)); if (s.len < i + n + 4) return false; // Chunk not yet fully buffered if (s.ptr[i] != '\r' || s.ptr[i + 1] != '\n') return false; if (s.ptr[i + n + 2] != '\r' || s.ptr[i + n + 3] != '\n') return false; *prefixlen = i + 2; *datalen = n; return true; } static bool mg_is_chunked(struct mg_http_message *hm) { const char *needle = "chunked"; struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding"); return te != NULL && mg_vcasecmp(te, needle) == 0; } void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) { size_t ofs = (size_t) (hm->chunk.ptr - (char *) c->recv.buf); mg_iobuf_del(&c->recv, ofs, hm->chunk.len); c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK); } static void deliver_chunked_chunks(struct mg_connection *c, size_t hlen, struct mg_http_message *hm, bool *next) { // | ... headers ... | HEXNUM\r\n ..data.. \r\n | ...... // +------------------+--------------------------+---- // | hlen | chunk1 | ...... char *buf = (char *) &c->recv.buf[hlen], *p = buf; size_t len = c->recv.len - hlen; size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK; size_t mark, pl, dl, del = 0, ofs = 0; bool last = false; if (processed <= len) len -= processed, buf += processed; while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) { size_t saved = c->recv.len; memmove(p + processed, buf + ofs + pl, dl); // MG_INFO(("P2 [%.*s]", (int) (processed + dl), p)); hm->chunk = mg_str_n(p + processed, dl); mg_call(c, MG_EV_HTTP_CHUNK, hm); ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix processed += dl; if (c->recv.len != saved) processed -= dl, buf -= dl; mg_hexdump(c->recv.buf, hlen + processed); last = (dl == 0); } mg_iobuf_del(&c->recv, hlen + processed, del); mark = ((size_t) c->pfn_data) & MG_DMARK; c->pfn_data = (void *) (processed | mark); if (last) { hm->body.len = processed; hm->message.len = hlen + processed; c->pfn_data = NULL; if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true; // MG_INFO(("LAST, mark: %lx", mark)); // mg_hexdump(c->recv.buf, c->recv.len); } } static void deliver_normal_chunks(struct mg_connection *c, size_t hlen, struct mg_http_message *hm, bool *next) { size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK; size_t deleted = ((size_t) c->pfn_data) & MG_DMARK; hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len - hlen); if (processed <= hm->chunk.len && !deleted) { hm->chunk.len -= processed; hm->chunk.ptr += processed; } left = hm->body.len < processed ? 0 : hm->body.len - processed; if (hm->chunk.len > left) hm->chunk.len = left; if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm); processed += hm->chunk.len; deleted = ((size_t) c->pfn_data) & MG_DMARK; // Re-evaluate after user call if (processed >= hm->body.len) { // Last, 0-len chunk hm->chunk.len = 0; // Reset length mg_call(c, MG_EV_HTTP_CHUNK, hm); // Call user handler c->pfn_data = NULL; // Reset processed counter if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen), *next = true; } else { c->pfn_data = (void *) (processed | deleted); // if it is set } } 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; // mg_hexdump(c->recv.buf, c->recv.len); while (c->recv.buf != NULL && c->recv.len > 0) { bool next = false; int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); if (hlen < 0) { mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf); break; } if (c->is_resp) break; // Response is still generated if (hlen == 0) break; // Request is not buffered yet if (ev == MG_EV_CLOSE) { // If client did not set Content-Length hm.message.len = c->recv.len; // and closes now, deliver a MSG hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); } if (mg_is_chunked(&hm)) { deliver_chunked_chunks(c, (size_t) hlen, &hm, &next); } else { deliver_normal_chunks(c, (size_t) hlen, &hm, &next); } if (next) continue; // Chunks & request were deleted // Chunk events are delivered. If we have full body, deliver MSG if (c->recv.len < hm.message.len) break; if (c->is_accepted) c->is_resp = 1; // Start generating response mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp mg_iobuf_del(&c->recv, 0, hm.message.len); } } (void) evd, (void) fnd; } 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 // 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; } } static size_t roundup(size_t size, size_t align) { return align == 0 ? size : (size + align - 1) / align * align; } int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { int ok = 1; new_size = roundup(new_size, io->align); 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) memmove(p, io->buf, len); zeromem(io->buf, io->size); free(io->buf); io->buf = (unsigned char *) p; io->size = new_size; } else { ok = 0; MG_ERROR(("%lld->%lld", (uint64_t) io->size, (uint64_t) new_size)); } } return ok; } int mg_iobuf_init(struct mg_iobuf *io, size_t size, size_t align) { io->buf = NULL; io->align = align; io->size = io->len = 0; 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 new_size = roundup(io->len + len, io->align); mg_iobuf_resize(io, new_size); // Attempt to resize if (new_size != io->size) len = 0; // Resize 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; if (io->buf) memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); if (io->buf) 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/json.c" #endif static const char *escapeseq(int esc) { return esc ? "\b\f\n\r\t\\\"" : "bfnrt\\\""; } static char json_esc(int c, int esc) { const char *p, *esc1 = escapeseq(esc), *esc2 = escapeseq(!esc); for (p = esc1; *p != '\0'; p++) { if (*p == c) return esc2[p - esc1]; } return 0; } static int mg_pass_string(const char *s, int len) { int i; for (i = 0; i < len; i++) { if (s[i] == '\\' && i + 1 < len && json_esc(s[i + 1], 1)) { i++; } else if (s[i] == '\0') { return MG_JSON_INVALID; } else if (s[i] == '"') { return i; } } return MG_JSON_INVALID; } static double mg_atod(const char *p, int len, int *numlen) { double d = 0.0; int i = 0, sign = 1; // Sign if (i < len && *p == '-') { sign = -1, i++; } else if (i < len && *p == '+') { i++; } // Decimal for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { d *= 10.0; d += p[i] - '0'; } d *= sign; // Fractional if (i < len && p[i] == '.') { double frac = 0.0, base = 0.1; i++; for (; i < len && p[i] >= '0' && p[i] <= '9'; i++) { frac += base * (p[i] - '0'); base /= 10.0; } d += frac * sign; } // Exponential if (i < len && (p[i] == 'e' || p[i] == 'E')) { int j, exp = 0, minus = 0; i++; if (i < len && p[i] == '-') minus = 1, i++; if (i < len && p[i] == '+') i++; while (i < len && p[i] >= '0' && p[i] <= '9' && exp < 308) exp = exp * 10 + (p[i++] - '0'); if (minus) exp = -exp; for (j = 0; j < exp; j++) d *= 10.0; for (j = 0; j < -exp; j++) d /= 10.0; } if (numlen != NULL) *numlen = i; return d; } int mg_json_get(struct mg_str json, const char *path, int *toklen) { const char *s = json.ptr; int len = (int) json.len; enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; unsigned char nesting[MG_JSON_MAX_DEPTH]; int i = 0; // Current offset in `s` int j = 0; // Offset in `s` we're looking for (return value) int depth = 0; // Current depth (nesting level) int ed = 0; // Expected depth int pos = 1; // Current position in `path` int ci = -1, ei = -1; // Current and expected index in array if (toklen) *toklen = 0; if (path[0] != '$') return MG_JSON_INVALID; #define MG_CHECKRET(x) \ do { \ if (depth == ed && path[pos] == '\0' && ci == ei) { \ if (toklen) *toklen = i - j + 1; \ return j; \ } \ } while (0) // In the ascii table, the distance between `[` and `]` is 2. // Ditto for `{` and `}`. Hence +2 in the code below. #define MG_EOO(x) \ do { \ if (depth == ed && ci != ei) return MG_JSON_NOT_FOUND; \ if (c != nesting[depth - 1] + 2) return MG_JSON_INVALID; \ depth--; \ MG_CHECKRET(x); \ } while (0) for (i = 0; i < len; i++) { unsigned char c = ((unsigned char *) s)[i]; if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; switch (expecting) { case S_VALUE: // p("V %s [%.*s] %d %d %d %d\n", path, pos, path, depth, ed, ci, ei); if (depth == ed) j = i; if (c == '{') { if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; if (depth == ed && path[pos] == '.' && ci == ei) { // If we start the object, reset array indices ed++, pos++, ci = ei = -1; } nesting[depth++] = c; expecting = S_KEY; break; } else if (c == '[') { if (depth >= (int) sizeof(nesting)) return MG_JSON_TOO_DEEP; if (depth == ed && path[pos] == '[' && ei == ci) { ed++, pos++, ci = 0; for (ei = 0; path[pos] != ']' && path[pos] != '\0'; pos++) { ei *= 10; ei += path[pos] - '0'; } if (path[pos] != 0) pos++; } nesting[depth++] = c; break; } else if (c == ']' && depth > 0) { // Empty array MG_EOO(']'); } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { i += 3; } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { i += 3; } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { i += 4; } else if (c == '-' || ((c >= '0' && c <= '9'))) { int numlen = 0; mg_atod(&s[i], len - i, &numlen); i += numlen - 1; } else if (c == '"') { int n = mg_pass_string(&s[i + 1], len - i - 1); if (n < 0) return n; i += n + 1; } else { return MG_JSON_INVALID; } MG_CHECKRET('V'); if (depth == ed && ei >= 0) ci++; expecting = S_COMMA_OR_EOO; break; case S_KEY: if (c == '"') { int n = mg_pass_string(&s[i + 1], len - i - 1); if (n < 0) return n; if (i + 1 + n >= len) return MG_JSON_NOT_FOUND; if (depth < ed) return MG_JSON_NOT_FOUND; if (depth == ed && path[pos - 1] != '.') return MG_JSON_NOT_FOUND; // printf("K %s [%.*s] [%.*s] %d %d %d\n", path, pos, path, n, // &s[i + 1], n, depth, ed); // NOTE(cpq): in the check sequence below is important. // strncmp() must go first: it fails fast if the remaining length of // the path is smaller than `n`. if (depth == ed && path[pos - 1] == '.' && strncmp(&s[i + 1], &path[pos], (size_t) n) == 0 && (path[pos + n] == '\0' || path[pos + n] == '.' || path[pos + n] == '[')) { pos += n; } i += n + 1; expecting = S_COLON; } else if (c == '}') { // Empty object MG_EOO('}'); expecting = S_COMMA_OR_EOO; } else { return MG_JSON_INVALID; } break; case S_COLON: if (c == ':') { expecting = S_VALUE; } else { return MG_JSON_INVALID; } break; case S_COMMA_OR_EOO: if (depth <= 0) { return MG_JSON_INVALID; } else if (c == ',') { expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; } else if (c == ']' || c == '}') { MG_EOO('O'); if (depth == ed && ei >= 0) ci++; } else { return MG_JSON_INVALID; } break; } } return MG_JSON_NOT_FOUND; } bool mg_json_get_num(struct mg_str json, const char *path, double *v) { int n, toklen, found = 0; if ((n = mg_json_get(json, path, &toklen)) >= 0 && (json.ptr[n] == '-' || (json.ptr[n] >= '0' && json.ptr[n] <= '9'))) { if (v != NULL) *v = mg_atod(json.ptr + n, toklen, NULL); found = 1; } return found; } bool mg_json_get_bool(struct mg_str json, const char *path, bool *v) { int found = 0, off = mg_json_get(json, path, NULL); if (off >= 0 && (json.ptr[off] == 't' || json.ptr[off] == 'f')) { if (v != NULL) *v = json.ptr[off] == 't'; found = 1; } return found; } static bool json_unescape(const char *s, size_t len, char *to, size_t n) { size_t i, j; for (i = 0, j = 0; i < len && j < n; i++, j++) { if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') { // \uXXXX escape. We could process a simple one-byte chars // \u00xx from the ASCII range. More complex chars would require // dragging in a UTF8 library, which is too much for us if (s[i + 2] != '0' || s[i + 3] != '0') return false; // Give up ((unsigned char *) to)[j] = (unsigned char) mg_unhexn(s + i + 4, 2); i += 5; } else if (s[i] == '\\' && i + 1 < len) { char c = json_esc(s[i + 1], 0); if (c == 0) return false; to[j] = c; i++; } else { to[j] = s[i]; } } if (j >= n) return false; if (n > 0) to[j] = '\0'; return true; } char *mg_json_get_str(struct mg_str json, const char *path) { char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); if (off >= 0 && len > 1 && json.ptr[off] == '"') { if ((result = (char *) calloc(1, (size_t) len)) != NULL && !json_unescape(json.ptr + off + 1, (size_t) (len - 2), result, (size_t) len)) { free(result); result = NULL; } } return result; } char *mg_json_get_b64(struct mg_str json, const char *path, int *slen) { char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); if (off >= 0 && json.ptr[off] == '"' && len > 1 && (result = (char *) calloc(1, (size_t) len)) != NULL) { int k = mg_base64_decode(json.ptr + off + 1, len - 2, result); if (slen != NULL) *slen = k; } return result; } char *mg_json_get_hex(struct mg_str json, const char *path, int *slen) { char *result = NULL; int len = 0, off = mg_json_get(json, path, &len); if (off >= 0 && json.ptr[off] == '"' && len > 1 && (result = (char *) calloc(1, (size_t) len / 2)) != NULL) { mg_unhex(json.ptr + off + 1, (size_t) (len - 2), (uint8_t *) result); result[len / 2 - 1] = '\0'; if (slen != NULL) *slen = len / 2 - 1; } return result; } long mg_json_get_long(struct mg_str json, const char *path, long dflt) { double dv; long result = dflt; if (mg_json_get_num(json, path, &dv)) result = (long) dv; return result; } #ifdef MG_ENABLE_LINES #line 1 "src/log.c" #endif static void default_logger(char c, void *param) { putchar(c); (void) c, (void) param; } static int s_level = MG_LL_INFO; static mg_pfn_t s_log_func = default_logger; static void *s_log_func_param = NULL; void mg_log_set_fn(mg_pfn_t fn, void *param) { s_log_func = fn; s_log_func_param = param; } static void logc(unsigned char c) { s_log_func((char) c, s_log_func_param); } static void logs(const char *buf, size_t len) { size_t i; for (i = 0; i < len; i++) logc(((unsigned char *) buf)[i]); } void mg_log_set(int log_level) { MG_DEBUG(("Setting log level to %d", log_level)); s_level = log_level; } bool mg_log_prefix(int level, const char *file, int line, const char *fname) { if (level <= s_level) { const char *p = strrchr(file, '/'); char buf[41]; size_t n; if (p == NULL) p = strrchr(file, '\\'); n = mg_snprintf(buf, sizeof(buf), "%llx %d %s:%d:%s", mg_millis(), level, p == NULL ? file : p + 1, line, fname); if (n > sizeof(buf) - 2) n = sizeof(buf) - 2; while (n < sizeof(buf)) buf[n++] = ' '; logs(buf, n - 1); return true; } else { return false; } } void mg_log(const char *fmt, ...) { va_list ap; va_start(ap, fmt); mg_vxprintf(s_log_func, s_log_func_param, fmt, &ap); va_end(ap); logc((unsigned char) '\n'); } static unsigned char nibble(unsigned c) { return (unsigned char) (c < 10 ? c + '0' : c + 'W'); } #define ISPRINT(x) ((x) >= ' ' && (x) <= '~') void mg_hexdump(const void *buf, size_t len) { const unsigned char *p = (const unsigned char *) buf; unsigned char ascii[16], alen = 0; size_t i; for (i = 0; i < len; i++) { if ((i % 16) == 0) { // Print buffered ascii chars if (i > 0) logs(" ", 2), logs((char *) ascii, 16), logc('\n'), alen = 0; // Print hex address, then \t logc(nibble((i >> 12) & 15)), logc(nibble((i >> 8) & 15)), logc(nibble((i >> 4) & 15)), logc('0'), logs(" ", 3); } logc(nibble(p[i] >> 4)), logc(nibble(p[i] & 15)); // Two nibbles, e.g. c5 logc(' '); // Space after hex number ascii[alen++] = ISPRINT(p[i]) ? p[i] : '.'; // Add to the ascii buf } while (alen < 16) logs(" ", 3), ascii[alen++] = ' '; logs(" ", 2), logs((char *) ascii, 16), logc('\n'); } #ifdef MG_ENABLE_LINES #line 1 "src/md5.c" #endif #if defined(MG_ENABLE_MD5) && 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 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 struct mg_mqtt_opts *opts) { char rnd[10], client_id[21], zero = 0; struct mg_str cid = opts->client_id; uint32_t total_len = 7 + 1 + 2 + 2; uint8_t hdr[8] = {0, 4, 'M', 'Q', 'T', 'T', opts->version, 0}; if (cid.len == 0) { mg_random(rnd, sizeof(rnd)); mg_hex(rnd, sizeof(rnd), client_id); client_id[sizeof(client_id) - 1] = '\0'; cid = mg_str(client_id); } if (hdr[6] == 0) hdr[6] = 4; // If version is not set, use 4 (3.1.1) c->is_mqtt5 = hdr[6] == 5; // Set version 5 flag hdr[7] = (uint8_t) ((opts->will_qos & 3) << 3); // Connection flags if (opts->user.len > 0) { total_len += 2 + (uint32_t) opts->user.len; hdr[7] |= MQTT_HAS_USER_NAME; } if (opts->pass.len > 0) { total_len += 2 + (uint32_t) opts->pass.len; hdr[7] |= 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; hdr[7] |= MQTT_HAS_WILL; } if (opts->clean || cid.len == 0) hdr[7] |= MQTT_CLEAN_SESSION; if (opts->will_retain) hdr[7] |= MQTT_WILL_RETAIN; total_len += (uint32_t) cid.len; if (c->is_mqtt5) total_len += 1U + (hdr[7] & MQTT_HAS_WILL ? 1U : 0); mg_mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len); mg_send(c, hdr, sizeof(hdr)); // keepalive == 0 means "do not disconnect us!" mg_send_u16(c, mg_htons((uint16_t) opts->keepalive)); if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // V5 properties mg_send_u16(c, mg_htons((uint16_t) cid.len)); mg_send(c, cid.ptr, cid.len); if (hdr[7] & MQTT_HAS_WILL) { if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); // will props 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 (opts->user.len > 0) { mg_send_u16(c, mg_htons((uint16_t) opts->user.len)); mg_send(c, opts->user.ptr, opts->user.len); } if (opts->pass.len > 0) { mg_send_u16(c, mg_htons((uint16_t) opts->pass.len)); mg_send(c, opts->pass.ptr, opts->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)), zero = 0; uint32_t len = 2 + (uint32_t) topic.len + (uint32_t) data.len; MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) topic.len, (char *) topic.ptr, (int) data.len, (char *) data.ptr)); if (qos > 0) len += 2; if (c->is_mqtt5) len++; mg_mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, len); mg_send_u16(c, mg_htons((uint16_t) topic.len)); mg_send(c, topic.ptr, topic.len); if (qos > 0) { if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); } if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); mg_send(c, data.ptr, data.len); } void mg_mqtt_sub(struct mg_connection *c, struct mg_str topic, int qos) { uint8_t qos_ = qos & 3, zero = 0; uint32_t len = 2 + (uint32_t) topic.len + 2 + 1 + (c->is_mqtt5 ? 1 : 0); mg_mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, 2, len); if (++c->mgr->mqtt_id == 0) ++c->mgr->mqtt_id; mg_send_u16(c, mg_htons(c->mgr->mqtt_id)); if (c->is_mqtt5) mg_send(c, &zero, sizeof(zero)); 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, uint8_t version, 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_SUBSCRIBE: case MQTT_CMD_SUBACK: case MQTT_CMD_UNSUBSCRIBE: case MQTT_CMD_UNSUBACK: 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; if (version == 5) p += 1 + p[0]; // Skip options 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 void mqtt_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_READ) { for (;;) { uint8_t version = c->is_mqtt5 ? 5 : 4; struct mg_mqtt_message mm; int rc = mg_mqtt_parse(c->recv.buf, c->recv.len, version, &mm); if (rc == MQTT_MALFORMED) { MG_ERROR(("%lu MQTT malformed message", c->id)); c->is_closing = 1; break; } else if (rc == MQTT_OK) { MG_VERBOSE(("%lu MQTT CMD %d len %d [%.*s]", c->id, 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) { MG_DEBUG(("%lu Connected", c->id)); } else { MG_ERROR(("%lu MQTT auth failed, code %d", c->id, mm.ack)); c->is_closing = 1; } break; case MQTT_CMD_PUBLISH: { MG_DEBUG(("%lu [%.*s] -> [%.*s]", c->id, (int) mm.topic.len, mm.topic.ptr, (int) mm.data.len, mm.data.ptr)); if (mm.qos > 0) { uint16_t id = mg_htons(mm.id); mg_mqtt_send_header(c, MQTT_CMD_PUBACK, 0, sizeof(id)); mg_send(c, &id, sizeof(id)); } 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, const 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, 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 size_t mg_vprintf(struct mg_connection *c, const char *fmt, va_list ap) { size_t old = c->send.len; va_list tmp; va_copy(tmp, ap); mg_vxprintf(mg_pfn_iobuf, &c->send, fmt, &tmp); return c->send.len - old; } size_t mg_printf(struct mg_connection *c, const char *fmt, ...) { size_t 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_addr *a, char *buf, size_t len) { char tmp[30]; const char *fmt = a->is_ip6 ? "[%s]:%d" : "%s:%d"; mg_ntoa(a, tmp, sizeof(tmp)); mg_snprintf(buf, len, fmt, tmp, (int) mg_ntohs(a->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; mg_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)); mg_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_atone(struct mg_str str, struct mg_addr *addr) { if (str.len > 0) return false; addr->ip = 0; 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; // MG_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) { // MG_INFO(("[%.*s]", (int) str.len, str.ptr)); return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) || mg_aton6(str, addr); } struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { struct mg_connection *c = (struct mg_connection *) calloc(1, sizeof(*c) + mgr->extraconnsize); if (c != NULL) { c->mgr = mgr; c->send.align = c->recv.align = MG_IO_SIZE; c->id = ++mgr->nextid; } return c; } void mg_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); MG_DEBUG(("%lu %p closed", c->id, c->fd)); mg_tls_free(c); mg_iobuf_free(&c->recv); mg_iobuf_free(&c->send); memset(c, 0, sizeof(*c)); free(c); } 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 (url == NULL || url[0] == '\0') { MG_ERROR(("null url")); } else if ((c = mg_alloc_conn(mgr)) == NULL) { MG_ERROR(("OOM")); } else { LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); c->is_udp = (strncmp(url, "udp:", 4) == 0); c->fn = fn; c->is_client = true; c->fn_data = fn_data; MG_DEBUG(("%lu %p %s", c->id, c->fd, url)); mg_call(c, MG_EV_OPEN, NULL); mg_resolve(c, url); } 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; if ((c = mg_alloc_conn(mgr)) == NULL) { MG_ERROR(("OOM %s", url)); } else if (!mg_open_listener(c, url)) { MG_ERROR(("Failed: %s, errno %d", url, errno)); free(c); c = NULL; } else { c->is_listening = 1; c->is_udp = strncmp(url, "udp:", 4) == 0; LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); c->fn = fn; c->fn_data = fn_data; mg_call(c, MG_EV_OPEN, NULL); MG_DEBUG(("%lu %p %s", c->id, c->fd, url)); } return c; } struct mg_connection *mg_wrapfd(struct mg_mgr *mgr, int fd, mg_event_handler_t fn, void *fn_data) { struct mg_connection *c = mg_alloc_conn(mgr); if (c != NULL) { c->fd = (void *) (size_t) fd; c->fn = fn; c->fn_data = fn_data; MG_EPOLL_ADD(c); mg_call(c, MG_EV_OPEN, NULL); LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); } return c; } struct mg_timer *mg_timer_add(struct mg_mgr *mgr, uint64_t milliseconds, unsigned flags, void (*fn)(void *), void *arg) { struct mg_timer *t = (struct mg_timer *) calloc(1, sizeof(*t)); mg_timer_init(&mgr->timers, t, milliseconds, flags, fn, arg); t->id = mgr->timerid++; return t; } void mg_mgr_free(struct mg_mgr *mgr) { struct mg_connection *c; struct mg_timer *tmp, *t = mgr->timers; while (t != NULL) tmp = t->next, free(t), t = tmp; mgr->timers = NULL; // Important. Next call to poll won't touch timers 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 MG_DEBUG(("All connections closed")); #if MG_ENABLE_EPOLL if (mgr->epoll_fd >= 0) close(mgr->epoll_fd), mgr->epoll_fd = -1; #endif free(mgr->priv); } void mg_mgr_init(struct mg_mgr *mgr) { memset(mgr, 0, sizeof(*mgr)); #if MG_ENABLE_EPOLL if ((mgr->epoll_fd = epoll_create1(0)) < 0) MG_ERROR(("epoll: %d", errno)); #else mgr->epoll_fd = -1; #endif #if MG_ARCH == MG_ARCH_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/rpc.c" #endif void mg_rpc_add(struct mg_rpc **head, struct mg_str method, void (*fn)(struct mg_rpc_req *), void *fn_data) { struct mg_rpc *rpc = (struct mg_rpc *) calloc(1, sizeof(*rpc)); rpc->method = mg_strdup(method), rpc->fn = fn, rpc->fn_data = fn_data; rpc->next = *head, *head = rpc; } void mg_rpc_del(struct mg_rpc **head, void (*fn)(struct mg_rpc_req *)) { struct mg_rpc *r; while ((r = *head) != NULL) { if (r->fn == fn || fn == NULL) { *head = r->next; free((void *) r->method.ptr); free(r); } else { head = &(*head)->next; } } } static void mg_rpc_call(struct mg_rpc_req *r, struct mg_str method) { struct mg_rpc *h = r->head == NULL ? NULL : *r->head; while (h != NULL && !mg_match(method, h->method, NULL)) h = h->next; if (h != NULL) { r->rpc = h; h->fn(r); } else { mg_rpc_err(r, -32601, "\"%.*s not found\"", (int) method.len, method.ptr); } } void mg_rpc_process(struct mg_rpc_req *r) { int len, off = mg_json_get(r->frame, "$.method", &len); if (off > 0 && r->frame.ptr[off] == '"') { struct mg_str method = mg_str_n(&r->frame.ptr[off + 1], (size_t) len - 2); mg_rpc_call(r, method); } else if ((off = mg_json_get(r->frame, "$.result", &len)) > 0 || (off = mg_json_get(r->frame, "$.error", &len)) > 0) { mg_rpc_call(r, mg_str("")); // JSON response! call "" method handler } else { mg_rpc_err(r, -32700, "%.*Q", (int) r->frame.len, r->frame.ptr); // Invalid } } void mg_rpc_vok(struct mg_rpc_req *r, const char *fmt, va_list *ap) { int len, off = mg_json_get(r->frame, "$.id", &len); if (off > 0) { mg_xprintf(r->pfn, r->pfn_data, "{%Q:%.*s,%Q:", "id", len, &r->frame.ptr[off], "result"); mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap); mg_xprintf(r->pfn, r->pfn_data, "}"); } } void mg_rpc_ok(struct mg_rpc_req *r, const char *fmt, ...) { va_list ap; va_start(ap, fmt); mg_rpc_vok(r, fmt, &ap); va_end(ap); } void mg_rpc_verr(struct mg_rpc_req *r, int code, const char *fmt, va_list *ap) { int len, off = mg_json_get(r->frame, "$.id", &len); mg_xprintf(r->pfn, r->pfn_data, "{"); if (off > 0) { mg_xprintf(r->pfn, r->pfn_data, "%Q:%.*s,", "id", len, &r->frame.ptr[off]); } mg_xprintf(r->pfn, r->pfn_data, "%Q:{%Q:%d,%Q:", "error", "code", code, "message"); mg_vxprintf(r->pfn, r->pfn_data, fmt == NULL ? "null" : fmt, ap); mg_xprintf(r->pfn, r->pfn_data, "}}"); } void mg_rpc_err(struct mg_rpc_req *r, int code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); mg_rpc_verr(r, code, fmt, &ap); va_end(ap); } static size_t print_methods(mg_pfn_t pfn, void *pfn_data, va_list *ap) { struct mg_rpc *h, **head = (struct mg_rpc **) va_arg(*ap, void **); size_t len = 0; for (h = *head; h != NULL; h = h->next) { len += mg_xprintf(pfn, pfn_data, "%s%.*Q", h == *head ? "" : ",", (int) h->method.len, h->method.ptr); } return len; } void mg_rpc_list(struct mg_rpc_req *r) { mg_rpc_ok(r, "[%M]", print_methods, r->head); } #ifdef MG_ENABLE_LINES #line 1 "src/sha1.c" #endif /* Copyright(c) By Steve Reid */ /* 100% Public Domain */ /* * 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); static 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)); } #ifdef MG_ENABLE_LINES #line 1 "src/sntp.c" #endif #define SNTP_TIME_OFFSET 2208988800U // (1970 - 1900) in seconds #define SNTP_MAX_FRAC 4294967295.0 // 2 ** 32 - 1 static int64_t gettimestamp(const uint32_t *data) { uint32_t sec = mg_ntohl(data[0]), frac = mg_ntohl(data[1]); if (sec) sec -= SNTP_TIME_OFFSET; return ((int64_t) sec) * 1000 + (int64_t) (frac / SNTP_MAX_FRAC * 1000.0); } int64_t mg_sntp_parse(const unsigned char *buf, size_t len) { int64_t res = -1; int mode = len > 0 ? buf[0] & 7 : 0; int version = len > 0 ? (buf[0] >> 3) & 7 : 0; if (len < 48) { MG_ERROR(("%s", "corrupt packet")); } else if (mode != 4 && mode != 5) { MG_ERROR(("%s", "not a server reply")); } else if (buf[1] == 0) { MG_ERROR(("%s", "server sent a kiss of death")); } else if (version == 4 || version == 3) { // int64_t ref = gettimestamp((uint32_t *) &buf[16]); int64_t t0 = gettimestamp((uint32_t *) &buf[24]); int64_t t1 = gettimestamp((uint32_t *) &buf[32]); int64_t t2 = gettimestamp((uint32_t *) &buf[40]); int64_t t3 = (int64_t) mg_millis(); int64_t delta = (t3 - t0) - (t2 - t1); MG_VERBOSE(("%lld %lld %lld %lld delta:%lld", t0, t1, t2, t3, delta)); res = t2 + delta / 2; } else { MG_ERROR(("unexpected version: %d", version)); } return res; } static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { if (ev == MG_EV_READ) { int64_t milliseconds = mg_sntp_parse(c->recv.buf, c->recv.len); if (milliseconds > 0) { MG_INFO(("%lu got time: %lld ms from epoch", c->id, milliseconds)); mg_call(c, MG_EV_SNTP_TIME, (uint64_t *) &milliseconds); MG_VERBOSE(("%u.%u", (unsigned) (milliseconds / 1000), (unsigned) (milliseconds % 1000))); } mg_iobuf_del(&c->recv, 0, c->recv.len); // Free receive buffer } else if (ev == MG_EV_CONNECT) { mg_sntp_request(c); } else if (ev == MG_EV_CLOSE) { } (void) fnd; (void) evd; } void mg_sntp_request(struct mg_connection *c) { if (c->is_resolving) { MG_ERROR(("%lu wait until resolved", c->id)); } else { int64_t now = (int64_t) mg_millis(); // Use int64_t, for vc98 uint8_t buf[48] = {0}; uint32_t *t = (uint32_t *) &buf[40]; double frac = ((double) (now % 1000)) / 1000.0 * SNTP_MAX_FRAC; buf[0] = (0 << 6) | (4 << 3) | 3; t[0] = mg_htonl((uint32_t) (now / 1000) + SNTP_TIME_OFFSET); t[1] = mg_htonl((uint32_t) frac); mg_send(c, buf, sizeof(buf)); } } 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 MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK typedef unsigned long nfds_t; #define MG_SOCK_ERRNO WSAGetLastError() #if defined(_MSC_VER) #pragma comment(lib, "ws2_32.lib") #define alloca(a) _alloca(a) #endif #define poll(a, b, c) WSAPoll((a), (b), (c)) #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 #elif MG_ARCH == MG_ARCH_TIRTOS #define MG_SOCK_ERRNO errno #define closesocket(x) close(x) #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 MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK || err == WSAEINTR || err == WSAEWOULDBLOCK #endif ; } static bool mg_sock_conn_reset(void) { int err = MG_SOCK_ERRNO; #if MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK return err == WSAECONNRESET; #else return err == EPIPE || err == ECONNRESET; #endif } static void setlocaddr(SOCKET fd, struct mg_addr *addr) { union usa usa; socklen_t n = sizeof(usa); if (getsockname(fd, &usa.sa, &n) == 0) { tomgaddr(&usa, addr, n != sizeof(usa.sin)); } } static void iolog(struct mg_connection *c, char *buf, long n, bool r) { if (n == MG_IO_WAIT) { // Do nothing } else if (n <= 0) { c->is_closing = 1; // Termination. Don't call mg_error(): #1529 } else if (n > 0) { if (c->is_hexdumping) { union usa usa; char t1[50], t2[50]; socklen_t slen = sizeof(usa.sin); struct mg_addr a; memset(&usa, 0, sizeof(usa)); memset(&a, 0, sizeof(a)); if (getsockname(FD(c), &usa.sa, &slen) < 0) (void) 0; // Ignore result tomgaddr(&usa, &a, c->rem.is_ip6); MG_INFO(("\n-- %lu %s %s %s %s %ld", c->id, mg_straddr(&a, t1, sizeof(t1)), r ? "<-" : "->", mg_straddr(&c->rem, t2, sizeof(t2)), c->label, n)); mg_hexdump(buf, (size_t) n); } if (r) { c->recv.len += (size_t) n; mg_call(c, MG_EV_READ, &n); } else { mg_iobuf_del(&c->send, 0, (size_t) n); // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); if (c->send.len == 0) { MG_EPOLL_MOD(c, 0); } mg_call(c, MG_EV_WRITE, &n); } } } long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { long n; if (c->is_udp) { union usa usa; socklen_t slen = tousa(&c->rem, &usa); n = sendto(FD(c), (char *) buf, len, 0, &usa.sa, slen); if (n > 0) setlocaddr(FD(c), &c->loc); } else { n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING); #if MG_ARCH == MG_ARCH_RTX if (n == BSD_EWOULDBLOCK) return MG_IO_WAIT; #endif } if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT; if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET; if (n <= 0) return MG_IO_ERR; return n; } bool mg_send(struct mg_connection *c, const void *buf, size_t len) { if (c->is_udp) { long n = mg_io_send(c, buf, len); MG_DEBUG(("%lu %p %d:%d %ld err %d", c->id, c->fd, (int) c->send.len, (int) c->recv.len, n, MG_SOCK_ERRNO)); iolog(c, (char *) buf, n, false); return n > 0; } else { return mg_iobuf_add(&c->send, c->send.len, buf, len); } } static void mg_set_non_blocking_mode(SOCKET fd) { #if defined(MG_CUSTOM_NONBLOCK) MG_CUSTOM_NONBLOCK(fd); #elif MG_ARCH == MG_ARCH_WIN32 && MG_ENABLE_WINSOCK unsigned long on = 1; ioctlsocket(fd, FIONBIO, &on); #elif MG_ARCH == MG_ARCH_RTX unsigned long on = 1; ioctlsocket(fd, FIONBIO, &on); #elif MG_ARCH == MG_ARCH_FREERTOS_TCP const BaseType_t off = 0; if (setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)) != 0) (void) 0; if (setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)) != 0) (void) 0; #elif MG_ARCH == MG_ARCH_FREERTOS_LWIP || MG_ARCH == MG_ARCH_RTX_LWIP lwip_fcntl(fd, F_SETFL, O_NONBLOCK); #elif MG_ARCH == MG_ARCH_AZURERTOS fcntl(fd, F_SETFL, O_NONBLOCK); #elif MG_ARCH == MG_ARCH_TIRTOS int val = 0; setsockopt(fd, 0, SO_BLOCKING, &val, sizeof(val)); int status = 0; int res = SockStatus(fd, FDSTATUS_SEND, &status); if (res == 0 && status > 0) { val = status / 2; int val_size = sizeof(val); res = SockSet(fd, SOL_SOCKET, SO_SNDLOWAT, &val, val_size); } #else fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // Non-blocking mode fcntl(fd, F_SETFD, FD_CLOEXEC); // Set close-on-exec #endif } bool mg_open_listener(struct mg_connection *c, const char *url) { SOCKET fd = INVALID_SOCKET; bool success = false; c->loc.port = mg_htons(mg_url_port(url)); if (!mg_aton(mg_url_host(url), &c->loc)) { MG_ERROR(("invalid listening URL: %s", url)); } else { union usa usa; socklen_t slen = tousa(&c->loc, &usa); int on = 1, af = c->loc.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) { MG_ERROR(("socket: %d", MG_SOCK_ERRNO)); #if ((MG_ARCH == MG_ARCH_WIN32) || (MG_ARCH == MG_ARCH_UNIX) || \ (defined(LWIP_SOCKET) && SO_REUSE == 1)) } else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) != 0) { // 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) MG_ERROR(("reuseaddr: %d", MG_SOCK_ERRNO)); #endif #if MG_ARCH == MG_ARCH_WIN32 && !defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE) } else if (setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on, sizeof(on)) != 0) { // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" MG_ERROR(("exclusiveaddruse: %d", MG_SOCK_ERRNO)); #endif } else if (bind(fd, &usa.sa, slen) != 0) { MG_ERROR(("bind: %d", MG_SOCK_ERRNO)); } else if ((type == SOCK_STREAM && listen(fd, MG_SOCK_LISTEN_BACKLOG_SIZE) != 0)) { // NOTE(lsm): FreeRTOS uses backlog value as a connection limit // In case port was set to 0, get the real port number MG_ERROR(("listen: %d", MG_SOCK_ERRNO)); } else { setlocaddr(fd, &c->loc); mg_set_non_blocking_mode(fd); c->fd = S2PTR(fd); MG_EPOLL_ADD(c); success = true; } } if (success == false && fd != INVALID_SOCKET) closesocket(fd); return success; } long mg_io_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->rem, &usa); n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); if (n > 0) tomgaddr(&usa, &c->rem, slen != sizeof(usa.sin)); } else { n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); } if (n < 0 && mg_sock_would_block()) return MG_IO_WAIT; if (n < 0 && mg_sock_conn_reset()) return MG_IO_RESET; if (n <= 0) return MG_IO_ERR; return 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) { long n = -1; if (c->recv.len >= MG_MAX_RECV_SIZE) { mg_error(c, "max_recv_buf_size reached"); } else if (c->recv.size <= c->recv.len && !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; n = c->is_tls ? mg_tls_recv(c, buf, len) : mg_io_recv(c, buf, len); MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd, (long) c->send.len, (long) c->send.size, (long) c->recv.len, (long) c->recv.size, n, MG_SOCK_ERRNO)); iolog(c, buf, n, true); } } 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_io_send(c, buf, len); MG_DEBUG(("%lu %p snd %ld/%ld rcv %ld/%ld n=%ld err=%d", c->id, c->fd, (long) c->send.len, (long) c->send.size, (long) c->recv.len, (long) c->recv.size, n, MG_SOCK_ERRNO)); iolog(c, buf, n, false); } static void close_conn(struct mg_connection *c) { if (FD(c) != INVALID_SOCKET) { #if MG_ENABLE_EPOLL epoll_ctl(c->mgr->epoll_fd, EPOLL_CTL_DEL, FD(c), NULL); #endif closesocket(FD(c)); #if MG_ARCH == MG_ARCH_FREERTOS_TCP FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); #endif } mg_close_conn(c); } static void connect_conn(struct mg_connection *c) { union usa usa; socklen_t n = sizeof(usa); // Use getpeername() to test whether we have connected if (getpeername(FD(c), &usa.sa, &n) == 0) { c->is_connecting = 0; mg_call(c, MG_EV_CONNECT, NULL); MG_EPOLL_MOD(c, 0); if (c->is_tls_hs) mg_tls_handshake(c); } else { mg_error(c, "socket error"); } } static void setsockopts(struct mg_connection *c) { #if MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_AZURERTOS || \ MG_ARCH == MG_ARCH_TIRTOS (void) c; #else int on = 1; #if !defined(SOL_TCP) #define SOL_TCP IPPROTO_TCP #endif if (setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)) != 0) (void) 0; if (setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) != 0) (void) 0; #endif } void mg_connect_resolved(struct mg_connection *c) { int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; int rc, af = c->rem.is_ip6 ? AF_INET6 : AF_INET; // c->rem has resolved IP c->fd = S2PTR(socket(af, type, 0)); // Create outbound socket c->is_resolving = 0; // Clear resolving flag if (FD(c) == INVALID_SOCKET) { mg_error(c, "socket(): %d", MG_SOCK_ERRNO); } else if (c->is_udp) { MG_EPOLL_ADD(c); mg_call(c, MG_EV_RESOLVE, NULL); mg_call(c, MG_EV_CONNECT, NULL); } else { union usa usa; socklen_t slen = tousa(&c->rem, &usa); mg_set_non_blocking_mode(FD(c)); setsockopts(c); MG_EPOLL_ADD(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()) { MG_DEBUG(("%lu %p -> %x:%hu pend", c->id, c->fd, mg_ntohl(c->rem.ip), mg_ntohs(c->rem.port))); c->is_connecting = 1; } else { mg_error(c, "connect: %d", MG_SOCK_ERRNO); } } } static SOCKET raccept(SOCKET sock, union usa *usa, socklen_t len) { SOCKET s = INVALID_SOCKET; do { memset(usa, 0, sizeof(*usa)); s = accept(sock, &usa->sa, &len); } while (s == INVALID_SOCKET && errno == EINTR); return s; } 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 = raccept(FD(lsn), &usa, 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 MG_ERROR(("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERRNO)); #if (MG_ARCH != MG_ARCH_WIN32) && (MG_ARCH != MG_ARCH_FREERTOS_TCP) && \ (MG_ARCH != MG_ARCH_TIRTOS) && !(MG_ENABLE_POLL) } else if ((long) fd >= FD_SETSIZE) { MG_ERROR(("%ld > %ld", (long) fd, (long) FD_SETSIZE)); closesocket(fd); #endif } else if ((c = mg_alloc_conn(mgr)) == NULL) { MG_ERROR(("%lu OOM", lsn->id)); closesocket(fd); } else { // char buf[40]; tomgaddr(&usa, &c->rem, sa_len != sizeof(usa.sin)); // mg_straddr(&c->rem, buf, sizeof(buf)); LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); c->fd = S2PTR(fd); MG_EPOLL_ADD(c); mg_set_non_blocking_mode(FD(c)); setsockopts(c); c->is_accepted = 1; c->is_hexdumping = lsn->is_hexdumping; c->loc = lsn->loc; c->pfn = lsn->pfn; c->pfn_data = lsn->pfn_data; c->fn = lsn->fn; c->fn_data = lsn->fn_data; MG_DEBUG(("%lu %p accepted %x.%hu -> %x.%hu", c->id, c->fd, mg_ntohl(c->rem.ip), mg_ntohs(c->rem.port), mg_ntohl(c->loc.ip), mg_ntohs(c->loc.port))); mg_call(c, MG_EV_OPEN, NULL); mg_call(c, MG_EV_ACCEPT, NULL); } } static bool mg_socketpair(SOCKET sp[2], union usa usa[2], bool udp) { SOCKET sock; socklen_t n = sizeof(usa[0].sin); bool success = false; sock = sp[0] = sp[1] = INVALID_SOCKET; (void) memset(&usa[0], 0, sizeof(usa[0])); usa[0].sin.sin_family = AF_INET; *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001U); // 127.0.0.1 usa[1] = usa[0]; if (udp && (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) { success = true; } else if (!udp && (sock = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET && bind(sock, &usa[0].sa, n) == 0 && listen(sock, MG_SOCK_LISTEN_BACKLOG_SIZE) == 0 && getsockname(sock, &usa[0].sa, &n) == 0 && (sp[0] = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET && connect(sp[0], &usa[0].sa, n) == 0 && (sp[1] = raccept(sock, &usa[1], n)) != INVALID_SOCKET) { success = true; } if (success) { mg_set_non_blocking_mode(sp[1]); } else { if (sp[0] != INVALID_SOCKET) closesocket(sp[0]); if (sp[1] != INVALID_SOCKET) closesocket(sp[1]); sp[0] = sp[1] = INVALID_SOCKET; } if (sock != INVALID_SOCKET) closesocket(sock); return success; } int mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data, bool udp) { union usa usa[2]; SOCKET sp[2] = {INVALID_SOCKET, INVALID_SOCKET}; struct mg_connection *c = NULL; if (!mg_socketpair(sp, usa, udp)) { MG_ERROR(("Cannot create socket pair")); } else if ((c = mg_wrapfd(mgr, (int) sp[1], fn, fn_data)) == NULL) { closesocket(sp[0]); closesocket(sp[1]); sp[0] = sp[1] = INVALID_SOCKET; } else { tomgaddr(&usa[0], &c->rem, false); MG_DEBUG(("%lu %p pipe %lu", c->id, c->fd, (unsigned long) sp[0])); } return (int) sp[0]; } static bool can_read(const struct mg_connection *c) { return c->is_full == false; } static bool can_write(const struct mg_connection *c) { return c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0); } static bool skip_iotest(const struct mg_connection *c) { return (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) || (can_read(c) == false && can_write(c) == false); } 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) { c->is_readable = c->is_writable = 0; if (skip_iotest(c)) continue; if (can_read(c)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT); if (can_write(c)) 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); } #elif MG_ENABLE_EPOLL size_t max = 1; for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { c->is_readable = c->is_writable = 0; if (mg_tls_pending(c) > 0) ms = 1, c->is_readable = 1; if (can_write(c)) MG_EPOLL_MOD(c, 1); max++; } struct epoll_event *evs = (struct epoll_event *) alloca(max * sizeof(evs[0])); int n = epoll_wait(mgr->epoll_fd, evs, (int) max, ms); for (int i = 0; i < n; i++) { struct mg_connection *c = (struct mg_connection *) evs[i].data.ptr; if (evs[i].events & EPOLLERR) { mg_error(c, "socket error"); } else if (c->is_readable == 0) { bool rd = evs[i].events & (EPOLLIN | EPOLLHUP); bool wr = evs[i].events & EPOLLOUT; c->is_readable = can_read(c) && rd ? 1U : 0; c->is_writable = can_write(c) && wr ? 1U : 0; } } (void) skip_iotest; #elif MG_ENABLE_POLL nfds_t n = 0; for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) n++; struct pollfd *fds = (struct pollfd *) alloca(n * sizeof(fds[0])); memset(fds, 0, n * sizeof(fds[0])); n = 0; for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { c->is_readable = c->is_writable = 0; if (skip_iotest(c)) { // Socket not valid, ignore } else if (mg_tls_pending(c) > 0) { ms = 1; // Don't wait if TLS is ready } else { fds[n].fd = FD(c); if (can_read(c)) fds[n].events |= POLLIN; if (can_write(c)) fds[n].events |= POLLOUT; n++; } } // MG_INFO(("poll n=%d ms=%d", (int) n, ms)); if (poll(fds, n, ms) < 0) { #if MG_ARCH == MG_ARCH_WIN32 if (n == 0) Sleep(ms); // On Windows, poll fails if no sockets #endif memset(fds, 0, n * sizeof(fds[0])); } n = 0; for (struct mg_connection *c = mgr->conns; c != NULL; c = c->next) { if (skip_iotest(c)) { // Socket not valid, ignore } else if (mg_tls_pending(c) > 0) { c->is_readable = 1; } else { if (fds[n].revents & POLLERR) { mg_error(c, "socket error"); } else { c->is_readable = (unsigned) (fds[n].revents & (POLLIN | POLLHUP) ? 1 : 0); c->is_writable = (unsigned) (fds[n].revents & POLLOUT ? 1 : 0); } n++; } } #else struct timeval tv = {ms / 1000, (ms % 1000) * 1000}, tv_zero = {0, 0}; struct mg_connection *c; fd_set rset, wset, eset; SOCKET maxfd = 0; int rc; FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset); for (c = mgr->conns; c != NULL; c = c->next) { c->is_readable = c->is_writable = 0; if (skip_iotest(c)) continue; FD_SET(FD(c), &eset); if (can_read(c)) FD_SET(FD(c), &rset); if (can_write(c)) FD_SET(FD(c), &wset); if (mg_tls_pending(c) > 0) tv = tv_zero; if (FD(c) > maxfd) maxfd = FD(c); } if ((rc = select((int) maxfd + 1, &rset, &wset, &eset, &tv)) < 0) { #if MG_ARCH == MG_ARCH_WIN32 if (maxfd == 0) Sleep(ms); // On Windows, select fails if no sockets #else MG_ERROR(("select: %d %d", rc, MG_SOCK_ERRNO)); #endif FD_ZERO(&rset); FD_ZERO(&wset); FD_ZERO(&eset); } for (c = mgr->conns; c != NULL; c = c->next) { if (FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &eset)) { mg_error(c, "socket error"); } else { c->is_readable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &rset); c->is_writable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &wset); if (mg_tls_pending(c) > 0) c->is_readable = 1; } } #endif } void mg_mgr_poll(struct mg_mgr *mgr, int ms) { struct mg_connection *c, *tmp; uint64_t now; mg_iotest(mgr, ms); now = mg_millis(); mg_timer_poll(&mgr->timers, now); for (c = mgr->conns; c != NULL; c = tmp) { bool is_resp = c->is_resp; tmp = c->next; mg_call(c, MG_EV_POLL, &now); if (is_resp && !c->is_resp) { long n = 0; mg_call(c, MG_EV_READ, &n); } MG_VERBOSE(("%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 #ifndef MG_SSI_BUFSIZ #define MG_SSI_BUFSIZ 1024 #endif #if MG_ENABLE_SSI static char *mg_ssi(const char *path, const char *root, int depth) { struct mg_iobuf b = {NULL, 0, 0, MG_IO_SIZE}; FILE *fp = fopen(path, "rb"); if (fp != NULL) { char buf[MG_SSI_BUFSIZ], arg[sizeof(buf)]; int ch, intag = 0; size_t len = 0; buf[0] = arg[0] = '\0'; while ((ch = fgetc(fp)) != EOF) { if (intag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { buf[len++] = (char) (ch & 0xff); buf[len] = '\0'; if (sscanf(buf, "