mirror of
https://github.com/cesanta/mongoose.git
synced 2024-11-28 21:59:02 +08:00
3838 lines
114 KiB
C
3838 lines
114 KiB
C
// Copyright (c) 2004-2013 Sergey Lyubka
|
|
// Copyright (c) 2013-2020 Cesanta Software Limited
|
|
// All rights reserved
|
|
//
|
|
// This software is dual-licensed: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2 as
|
|
// published by the Free Software Foundation. For the terms of this
|
|
// license, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
// You are free to use this software under the terms of the GNU General
|
|
// Public License, but WITHOUT ANY WARRANTY; without even the implied
|
|
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU General Public License for more details.
|
|
//
|
|
// Alternatively, you can license this software under a commercial
|
|
// license, as set out in <https://www.cesanta.com/license>.
|
|
|
|
|
|
#include "mongoose.h"
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/private.h"
|
|
#endif
|
|
void mg_connect_resolved(struct mg_connection *);
|
|
|
|
#if MG_ARCH == MG_ARCH_FREERTOS
|
|
static inline void *mg_calloc(int cnt, size_t size) {
|
|
void *p = pvPortMalloc(size);
|
|
if (p != NULL) memset(p, 0, size);
|
|
return p;
|
|
}
|
|
#define calloc(a, b) mg_calloc((a), (b))
|
|
#define malloc(a) pvPortMalloc(a)
|
|
#define free(a) vPortFree(a)
|
|
#endif
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/base64.c"
|
|
#endif
|
|
#include <string.h>
|
|
|
|
static int mg_b64idx(int c) {
|
|
if (c < 26) {
|
|
return c + 'A';
|
|
} else if (c < 52) {
|
|
return c - 26 + 'a';
|
|
} else if (c < 62) {
|
|
return c - 52 + '0';
|
|
} else {
|
|
return c == 62 ? '+' : '/';
|
|
}
|
|
}
|
|
|
|
static int mg_b64rev(int c) {
|
|
if (c >= 'A' && c <= 'Z') {
|
|
return c - 'A';
|
|
} else if (c >= 'a' && c <= 'z') {
|
|
return c + 26 - 'a';
|
|
} else if (c >= '0' && c <= '9') {
|
|
return c + 52 - '0';
|
|
} else if (c == '+') {
|
|
return 62;
|
|
} else if (c == '/') {
|
|
return 63;
|
|
} else if (c == '=') {
|
|
return 64;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int mg_base64_update(unsigned char ch, char *to, int n) {
|
|
unsigned char rem = (n & 3) % 3;
|
|
if (rem == 0) {
|
|
to[n] = mg_b64idx(ch >> 2);
|
|
to[++n] = (ch & 3) << 4;
|
|
} else if (rem == 1) {
|
|
to[n] = mg_b64idx(to[n] | (ch >> 4));
|
|
to[++n] = (ch & 15) << 2;
|
|
} else {
|
|
to[n] = mg_b64idx(to[n] | (ch >> 6));
|
|
to[++n] = 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++] = (a << 2) | (b >> 4);
|
|
if (src[2] != '=') {
|
|
dst[len++] = (b << 4) | (c >> 2);
|
|
if (src[3] != '=') dst[len++] = (c << 6) | d;
|
|
}
|
|
src += 4;
|
|
}
|
|
dst[len] = '\0';
|
|
return len;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/dns.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct mg_dns_header {
|
|
uint16_t transaction_id;
|
|
uint16_t flags;
|
|
uint16_t num_questions;
|
|
uint16_t num_answers;
|
|
uint16_t num_authority_prs;
|
|
uint16_t num_other_prs;
|
|
};
|
|
|
|
struct dns_data {
|
|
struct dns_data *next;
|
|
struct mg_connection *c;
|
|
unsigned long expire;
|
|
uint16_t txnid;
|
|
};
|
|
|
|
static void mg_dns_free(struct dns_data **head, struct dns_data *d) {
|
|
LIST_DELETE(struct dns_data, head, d);
|
|
// LOG(LL_INFO, ("--> %p %p %d", d, d->c, d->c->fd));
|
|
free(d);
|
|
}
|
|
|
|
void mg_resolve_cancel(struct mg_mgr *mgr, struct mg_connection *c) {
|
|
struct dns_data *tmp, *d, **head;
|
|
head = mgr->dnsc == NULL ? NULL : (struct dns_data **) &mgr->dnsc->pfn_data;
|
|
for (d = head == NULL ? NULL : *head; d != NULL; d = tmp) {
|
|
tmp = d->next;
|
|
if (d->c == c) mg_dns_free(head, d);
|
|
}
|
|
}
|
|
|
|
static size_t mg_dns_parse_name(const uint8_t *s, const uint8_t *e, size_t off,
|
|
char *to, size_t tolen, int depth) {
|
|
size_t i = 0, j = 0;
|
|
if (depth > 5) return 0;
|
|
while (&s[off + i + 1] < e && s[off + i] > 0) {
|
|
size_t n = s[off + i];
|
|
if (n & 0xc0) {
|
|
size_t ptr = (((n & 0x3f) << 8) | s[off + i + 1]) - 12; // 12 is hdr len
|
|
if (&s[ptr + 1] < e && (s[ptr] & 0xc0) == 0) {
|
|
j = mg_dns_parse_name(s, e, ptr, to, tolen, depth + 1);
|
|
}
|
|
i++;
|
|
break;
|
|
}
|
|
if (&s[off + i + n + 1] >= e) break;
|
|
if (j > 0 && j < tolen) to[j++] = '.';
|
|
if (j + n < tolen) memcpy(&to[j], &s[off + i + 1], n);
|
|
j += n;
|
|
i += n + 1;
|
|
}
|
|
if (j < tolen) to[j] = '\0'; // Zero-terminate the name
|
|
to[tolen - 1] = '\0'; // Just in case
|
|
return i;
|
|
}
|
|
|
|
// txid flags numQ numA numAP numOP
|
|
// 0000 00 01 81 80 00 01 00 01 00 00 00 00 07 63 65 73 .............ces
|
|
// 0010 61 6e 74 61 03 63 6f 6d 00 00 01 00 01 c0 0c 00 anta.com........
|
|
// 0020 01 00 01 00 00 02 57 00 04 94 fb 36 ec ......W....6.
|
|
int mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *dm) {
|
|
struct mg_dns_header *h = (struct mg_dns_header *) buf;
|
|
const uint8_t *s = buf + sizeof(*h), *e = &buf[len];
|
|
size_t i, j = 0, n, ok = 0;
|
|
if (len < sizeof(*h)) return ok;
|
|
for (i = 0; i < mg_ntohs(h->num_questions); i++) {
|
|
j += mg_dns_parse_name(s, e, j, dm->name, sizeof(dm->name), 0) + 5;
|
|
// LOG(LL_INFO, ("QUE [%s]", name));
|
|
}
|
|
for (i = 0; i < mg_ntohs(h->num_answers); i++) {
|
|
j += mg_dns_parse_name(s, e, j, dm->name, sizeof(dm->name), 0) + 9;
|
|
// LOG(LL_DEBUG, ("NAME %s", name));
|
|
if (&s[j] + 2 > e) break;
|
|
n = ((int) s[j] << 8) | s[j + 1];
|
|
if (&s[j] + 2 + n > e) break;
|
|
if (n == 4) {
|
|
dm->txnid = mg_ntohs(h->transaction_id);
|
|
memcpy(&dm->ipaddr, &s[j + 2], 4);
|
|
ok = 1;
|
|
break;
|
|
}
|
|
j += 2 + n;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static void dns_cb(struct mg_connection *c, int ev, void *ev_data,
|
|
void *fn_data) {
|
|
struct dns_data *d, *tmp, **head = (struct dns_data **) &c->pfn_data;
|
|
if (ev == MG_EV_POLL) {
|
|
unsigned long now = *(unsigned long *) ev_data;
|
|
for (d = (struct dns_data *) fn_data; d != NULL; d = tmp) {
|
|
tmp = d->next;
|
|
// LOG(LL_DEBUG, ("%lu %lu dns poll", d->expire, now));
|
|
if (now > d->expire) mg_error(d->c, "DNS timeout");
|
|
}
|
|
} else if (ev == MG_EV_READ) {
|
|
struct mg_dns_message dm;
|
|
int resolved = 0;
|
|
if (mg_dns_parse(c->recv.buf, c->recv.len, &dm)) {
|
|
for (d = (struct dns_data *) c->pfn_data; d != NULL; d = tmp) {
|
|
tmp = d->next;
|
|
// LOG(LL_INFO, ("d %p %p", d, tmp));
|
|
if (dm.txnid != d->txnid) continue;
|
|
if (d->c->is_resolving) {
|
|
d->c->is_resolving = 0;
|
|
d->c->peer.ip = dm.ipaddr;
|
|
mg_connect_resolved(d->c);
|
|
mg_dns_free(head, d);
|
|
} else {
|
|
LOG(LL_ERROR, ("%p already resolved", d->c->fd));
|
|
}
|
|
resolved = 1;
|
|
}
|
|
}
|
|
if (!resolved) LOG(LL_ERROR, ("stray DNS reply"));
|
|
c->recv.len = 0;
|
|
} else if (ev == MG_EV_CLOSE) {
|
|
for (d = *head; d != NULL; d = tmp) {
|
|
tmp = d->next;
|
|
mg_dns_free(head, d);
|
|
}
|
|
}
|
|
}
|
|
|
|
void mg_dns_send(struct mg_connection *c, const struct mg_str *name,
|
|
uint16_t txnid) {
|
|
struct {
|
|
struct mg_dns_header header;
|
|
uint8_t data[256];
|
|
} pkt;
|
|
size_t i, n;
|
|
memset(&pkt, 0, sizeof(pkt));
|
|
pkt.header.transaction_id = 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;
|
|
mg_send(c, &pkt, sizeof(pkt.header) + n);
|
|
#if 0
|
|
// Immediately after A query, send AAAA query. Whatever reply comes first,
|
|
// we'll use it. Note: we cannot send two queries in a single packet.
|
|
// https://stackoverflow.com/questions/4082081/requesting-a-and-aaaa-records-in-single-dns-query
|
|
pkt.data[n - 3] = 0x1c; // AAAA query
|
|
mg_send(c, &pkt, sizeof(pkt.header) + n);
|
|
#endif
|
|
}
|
|
|
|
void mg_resolve(struct mg_mgr *mgr, struct mg_connection *c,
|
|
struct mg_str *name, int ms) {
|
|
struct dns_data *d = NULL;
|
|
int resolved = mg_aton(name->ptr, &c->peer.ip);
|
|
|
|
// Try to parse name as IP address
|
|
if (mg_vcmp(name, "localhost") == 0) {
|
|
resolved = 1;
|
|
c->peer.ip = mg_htonl(0x7f000001);
|
|
}
|
|
|
|
if (resolved) {
|
|
// name is an IP address, do not fire name resolution
|
|
mg_connect_resolved(c);
|
|
} else {
|
|
// name is not an IP, send DNS resolution request
|
|
if (mgr->dnsc == NULL) {
|
|
const char *srv = mgr->dnsserver ? mgr->dnsserver : "udp://8.8.8.8:53";
|
|
mgr->dnsc = mg_connect(mgr, srv, NULL, NULL);
|
|
if (mgr->dnsc != NULL) {
|
|
mgr->dnsc->pfn = dns_cb;
|
|
// mgr->dnsc->is_hexdumping = 1;
|
|
snprintf(mgr->dnsc->label, sizeof(mgr->dnsc->label), "%s", "RESOLVER");
|
|
}
|
|
}
|
|
if (mgr->dnsc == 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 **head = (struct dns_data **) &mgr->dnsc->pfn_data;
|
|
d->txnid = *head ? (*head)->txnid + 1 : 1;
|
|
d->next = *head;
|
|
*head = d;
|
|
d->expire = mg_millis() + ms;
|
|
d->c = c;
|
|
c->is_resolving = 1;
|
|
LOG(LL_DEBUG, ("%p resolving %.*s, txnid %hu", c->fd, (int) name->len,
|
|
name->ptr, d->txnid));
|
|
mg_dns_send(mgr->dnsc, name, d->txnid);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/event.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
void mg_call(struct mg_connection *c, int ev, void *ev_data) {
|
|
if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
|
|
if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
|
|
}
|
|
|
|
void mg_error(struct mg_connection *c, const char *fmt, ...) {
|
|
char mem[256], *buf = mem;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
|
va_end(ap);
|
|
LOG(LL_ERROR, ("%p %s", c->fd, buf));
|
|
mg_call(c, MG_EV_ERROR, buf);
|
|
if (buf != mem) free(buf);
|
|
c->is_closing = 1;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/http.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct http_data {
|
|
void *old_pfn_data; // Previous pfn_data
|
|
FILE *fp; // For static file serving
|
|
};
|
|
|
|
// Multipart POST example:
|
|
// https://gist.github.com/cpq/b8dd247571e6ee9c54ef7e8dfcfecf48
|
|
|
|
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) {
|
|
size_t 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 < u.len; i++) n = mg_base64_update(u.ptr[i], buf, n);
|
|
if (p.len > 0) {
|
|
n = mg_base64_update(':', buf, n);
|
|
for (i = 0; i < p.len; i++) n = mg_base64_update(p.ptr[i], buf, n);
|
|
}
|
|
n = mg_base64_final(buf, n);
|
|
c->send.len += 21 + n + 2;
|
|
memcpy(&c->send.buf[c->send.len - 2], "\r\n", 2);
|
|
} else {
|
|
LOG(LL_ERROR, ("%p %s cannot resize iobuf %d->%d ", c->fd, c->label,
|
|
(int) c->send.size, (int) need));
|
|
}
|
|
}
|
|
|
|
int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst,
|
|
int dst_len) {
|
|
const char *p, *e, *s;
|
|
size_t name_len;
|
|
int len;
|
|
|
|
if (dst == NULL || dst_len == 0) {
|
|
len = -2; // Bad destination
|
|
} else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
|
|
len = -1; // Bad source
|
|
dst[0] = '\0';
|
|
} else {
|
|
name_len = strlen(name);
|
|
e = buf->ptr + buf->len;
|
|
len = -4; // Name does not exist
|
|
dst[0] = '\0';
|
|
for (p = buf->ptr; p + name_len < e; p++) {
|
|
if ((p == buf->ptr || p[-1] == '&') && p[name_len] == '=' &&
|
|
!mg_ncasecmp(name, p, name_len)) {
|
|
p += name_len + 1;
|
|
s = (const char *) memchr(p, '&', (size_t)(e - p));
|
|
if (s == NULL) s = e;
|
|
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
|
|
if (len == -1) len = -3; // Failed to decode
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
|
|
int is_form_url_encoded) {
|
|
size_t i, j;
|
|
for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
|
|
if (src[i] == '%') {
|
|
// Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
|
|
if (i + 2 < src_len && isxdigit(*(const unsigned char *) (src + i + 1)) &&
|
|
isxdigit(*(const unsigned char *) (src + i + 2))) {
|
|
mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
|
|
i += 2;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (is_form_url_encoded && src[i] == '+') {
|
|
dst[j] = ' ';
|
|
} else {
|
|
dst[j] = src[i];
|
|
}
|
|
}
|
|
if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
|
|
return i >= src_len ? (int) j : -1;
|
|
}
|
|
|
|
int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
|
|
size_t i;
|
|
for (i = 0; i < buf_len; i++) {
|
|
if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128)
|
|
return -1;
|
|
if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
|
|
(i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
|
|
return (int) i + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const char *skip(const char *s, const char *end, const char *delims,
|
|
struct mg_str *v) {
|
|
v->ptr = s;
|
|
while (s < end && strchr(delims, *(unsigned char *) s) == NULL) s++;
|
|
v->len = s - v->ptr;
|
|
while (s < end && strchr(delims, *(unsigned char *) s) != NULL) s++;
|
|
return s;
|
|
}
|
|
|
|
struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
|
|
size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
|
|
for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
|
|
struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
|
|
if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void mg_http_parse_headers(const char *s, const char *end,
|
|
struct mg_http_header *h, int max_headers) {
|
|
int i;
|
|
for (i = 0; i < max_headers; i++) {
|
|
struct mg_str k, v, tmp;
|
|
const char *he = skip(s, end, "\n", &tmp);
|
|
s = skip(s, he, ": \r\n", &k);
|
|
s = skip(s, he, "\r\n", &v);
|
|
if (k.len == tmp.len) continue;
|
|
while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces
|
|
if (k.len == 0) break;
|
|
// LOG(LL_INFO, ("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr,
|
|
//(int) k.len, k.ptr, (int) v.len, v.ptr));
|
|
h[i].name = k;
|
|
h[i].value = v;
|
|
}
|
|
}
|
|
|
|
int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
|
|
int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
|
|
const char *end = s + req_len, *qs;
|
|
struct mg_str *cl;
|
|
|
|
memset(hm, 0, sizeof(*hm));
|
|
if (req_len <= 0) return req_len;
|
|
|
|
hm->message.ptr = s;
|
|
hm->body.ptr = s + req_len;
|
|
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);
|
|
if (hm->uri.ptr <= hm->method.ptr || hm->proto.ptr <= hm->uri.ptr) 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 = &hm->uri.ptr[hm->uri.len] - (qs + 1);
|
|
hm->uri.len = 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->ptr);
|
|
hm->message.len = 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 = req_len;
|
|
}
|
|
|
|
return req_len;
|
|
}
|
|
|
|
static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
|
|
va_list ap) {
|
|
char mem[256], *buf = mem;
|
|
int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
|
mg_printf(c, "%X\r\n", len);
|
|
mg_send(c, buf, len);
|
|
mg_send(c, "\r\n", 2);
|
|
if (buf != mem) free(buf);
|
|
}
|
|
|
|
void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
mg_http_vprintf_chunk(c, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
|
|
mg_printf(c, "%X\r\n", len);
|
|
mg_send(c, buf, len);
|
|
mg_send(c, "\r\n", 2);
|
|
}
|
|
|
|
void mg_http_reply(struct mg_connection *c, int code, const char *fmt, ...) {
|
|
char mem[100], *buf = mem;
|
|
va_list ap;
|
|
int len;
|
|
va_start(ap, fmt);
|
|
len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
|
va_end(ap);
|
|
mg_printf(c,
|
|
"HTTP/1.1 %d OK\r\nContent-Type: text/plain\r\n"
|
|
"Content-Length: %d\r\n\r\n",
|
|
code, len);
|
|
mg_send(c, buf, len);
|
|
if (buf != mem) free(buf);
|
|
}
|
|
|
|
static void http_cb(struct mg_connection *, int, void *, void *);
|
|
static void restore_http_cb(struct mg_connection *c) {
|
|
struct http_data *d = (struct http_data *) c->pfn_data;
|
|
if (d->fp != NULL) fclose(d->fp);
|
|
c->pfn_data = d->old_pfn_data;
|
|
c->pfn = http_cb;
|
|
free(d);
|
|
}
|
|
|
|
#if MG_ENABLE_FS
|
|
char *mg_http_etag(char *buf, size_t len, struct stat *st) {
|
|
snprintf(buf, len, "\"%lx.%lu\"", (unsigned long) st->st_mtime,
|
|
(unsigned long) st->st_size);
|
|
return buf;
|
|
}
|
|
|
|
int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
|
|
const char *dir) {
|
|
char offset[40] = "", name[200] = "", path[256];
|
|
mg_http_get_var(&hm->query, "offset", offset, sizeof(offset));
|
|
mg_http_get_var(&hm->query, "name", name, sizeof(name));
|
|
if (name[0] == '\0') {
|
|
mg_http_reply(c, 400, "%s", "name required");
|
|
return -1;
|
|
} else {
|
|
FILE *fp;
|
|
size_t oft = strtoul(offset, NULL, 0);
|
|
snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, name);
|
|
LOG(LL_DEBUG,
|
|
("%p %d bytes @ %d [%s]", c->fd, (int) hm->body.len, (int) oft, name));
|
|
if ((fp = fopen(path, oft == 0 ? "wb" : "a")) == NULL) {
|
|
mg_http_reply(c, 400, "fopen(%s): %d", name, errno);
|
|
return -2;
|
|
} else {
|
|
fwrite(hm->body.ptr, 1, hm->body.len, fp);
|
|
fclose(fp);
|
|
mg_http_reply(c, 200, "");
|
|
return hm->body.len;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void static_cb(struct mg_connection *c, int ev, void *ev_data,
|
|
void *fn_data) {
|
|
if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
|
|
struct http_data *d = (struct http_data *) fn_data;
|
|
// Read to send IO buffer directly, avoid extra on-stack buffer
|
|
size_t n, max = 2 * MG_IO_SIZE;
|
|
if (c->send.size < max) mg_iobuf_resize(&c->send, max);
|
|
if (c->send.len >= c->send.size) return; // Rate limit
|
|
n = fread(c->send.buf + c->send.len, 1, c->send.size - c->send.len, d->fp);
|
|
if (n > 0) c->send.len += n;
|
|
if (c->send.len < c->send.size) restore_http_cb(c);
|
|
} else if (ev == MG_EV_CLOSE) {
|
|
restore_http_cb(c);
|
|
}
|
|
(void) ev_data;
|
|
}
|
|
|
|
static const char *guess_content_type(const char *filename) {
|
|
int n = (int) strlen(filename);
|
|
if (mg_globmatch("#.html", 6, filename, n)) return "text/html";
|
|
if (mg_globmatch("#.css", 5, filename, n)) return "text/css";
|
|
if (mg_globmatch("#.js", 4, filename, n)) return "text/javascript";
|
|
return "text/plain";
|
|
}
|
|
|
|
void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
|
|
const char *path, const char *mime) {
|
|
struct mg_str *inm = mg_http_get_header(hm, "If-None-Match");
|
|
struct stat st;
|
|
FILE *fp = fopen(path, "rb");
|
|
char etag[64];
|
|
if (fp == NULL || stat(path, &st) != 0 ||
|
|
mg_http_etag(etag, sizeof(etag), &st) != etag) {
|
|
mg_http_reply(c, 404, "%s", "Not found\n");
|
|
} else if (inm != NULL && mg_vcasecmp(inm, etag) == 0) {
|
|
mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n");
|
|
} else {
|
|
mg_printf(c,
|
|
"HTTP/1.1 200 OK\r\nContent-Type: %s\r\n"
|
|
"Etag: %s\r\nContent-Length: %lu\r\n\r\n",
|
|
mime, etag, (unsigned long) st.st_size);
|
|
if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
|
|
fclose(fp);
|
|
} else {
|
|
struct http_data *d = (struct http_data *) calloc(1, sizeof(*d));
|
|
d->fp = fp;
|
|
d->old_pfn_data = c->pfn_data;
|
|
c->pfn = static_cb;
|
|
c->pfn_data = d;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if MG_ARCH == MG_ARCH_ESP32 || MG_ARCH == MG_ARCH_ESP8266 || \
|
|
MG_ARCH == MG_ARCH_FREERTOS
|
|
char *realpath(const char *src, char *dst) {
|
|
int len = strlen(src);
|
|
if (len > PATH_MAX - 1) len = PATH_MAX - 1;
|
|
strncpy(dst, src, len);
|
|
dst[len] = '\0';
|
|
return dst;
|
|
}
|
|
#endif
|
|
|
|
// Try to avoid dirent API
|
|
static int mg_is_dir(const char *path) {
|
|
#if MG_ARCH == MG_ARCH_FREERTOS
|
|
struct FF_STAT st;
|
|
return (ff_stat(path, &st) == 0) && (st.st_mode & FF_IFDIR);
|
|
#else
|
|
struct stat st;
|
|
return (stat(path, &st) == 0) && (st.st_mode & S_IFDIR);
|
|
#endif
|
|
}
|
|
|
|
#if MG_ENABLE_DIRECTORY_LISTING
|
|
|
|
#ifdef _WIN32
|
|
struct dirent {
|
|
char d_name[MAX_PATH];
|
|
};
|
|
|
|
typedef struct win32_dir {
|
|
HANDLE handle;
|
|
WIN32_FIND_DATAW info;
|
|
struct dirent result;
|
|
} DIR;
|
|
|
|
int gettimeofday(struct timeval *tv, void *tz) {
|
|
FILETIME ft;
|
|
unsigned __int64 tmpres = 0;
|
|
static int tzflag = 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);
|
|
}
|
|
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 = malloc(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 printdirentry(struct mg_connection *c, struct mg_http_message *hm,
|
|
const char *name, struct stat *stp) {
|
|
char size[64], mod[64]; //, path[PATH_MAX];
|
|
int is_dir = S_ISDIR(stp->st_mode);
|
|
const char *slash = is_dir ? "/" : "";
|
|
|
|
if (is_dir) {
|
|
snprintf(size, sizeof(size), "%s", "[DIR]");
|
|
} else {
|
|
if (stp->st_size < 1024) {
|
|
snprintf(size, sizeof(size), "%d", (int) stp->st_size);
|
|
} else if (stp->st_size < 0x100000) {
|
|
snprintf(size, sizeof(size), "%.1fk", (double) stp->st_size / 1024.0);
|
|
} else if (stp->st_size < 0x40000000) {
|
|
snprintf(size, sizeof(size), "%.1fM", (double) stp->st_size / 1048576);
|
|
} else {
|
|
snprintf(size, sizeof(size), "%.1fG", (double) stp->st_size / 1073741824);
|
|
}
|
|
}
|
|
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&stp->st_mtime));
|
|
// mg_escape(file_name, path, sizeof(path));
|
|
// href = mg_url_encode(mg_mk_str(file_name));
|
|
mg_http_printf_chunk(
|
|
c,
|
|
"<tr><td><a href=\"%.*s%s%s\">%s%s</a></td><td>%s</td><td>%s</td></tr>",
|
|
(int) hm->uri.len, hm->uri.ptr, name, slash, name, slash, mod, size);
|
|
// free((void *) href.p);
|
|
}
|
|
|
|
static void listdir(struct mg_connection *c, struct mg_http_message *hm,
|
|
char *dir) {
|
|
char path[PATH_MAX + 1], *p = &dir[strlen(dir) - 1];
|
|
struct dirent *dp;
|
|
DIR *dirp;
|
|
|
|
while (p > dir && *p != '/') *p-- = '\0';
|
|
// LOG(LL_DEBUG, ("%p [%s]", c->fd, dir));
|
|
if ((dirp = (opendir(dir))) != NULL) {
|
|
mg_printf(c, "%s\r\n", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n");
|
|
mg_http_printf_chunk(
|
|
c,
|
|
"<!DOCTYPE html><html><head><title>Index of %.*s</title>"
|
|
"<style>th,td {text-align: left; padding-right: 1em; "
|
|
"font-family: monospace; }</style></head>"
|
|
"<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
|
|
"<tr><th>Name</th><th>Modified</th><th>Size</th></tr>"
|
|
"<tr><td colspan=\"3\"><hr></td></tr></thead><tbody>",
|
|
(int) hm->uri.len, hm->uri.ptr, (int) hm->uri.len, hm->uri.ptr);
|
|
while ((dp = readdir(dirp)) != NULL) {
|
|
struct stat st;
|
|
// Do not show current dir and hidden files
|
|
if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
|
|
snprintf(path, sizeof(path), "%s/%s", dir, dp->d_name);
|
|
if (stat(path, &st) != 0) {
|
|
LOG(LL_ERROR, ("%p stat(%s): %d", c->fd, path, errno));
|
|
continue;
|
|
}
|
|
printdirentry(c, hm, dp->d_name, &st);
|
|
}
|
|
closedir(dirp);
|
|
mg_http_printf_chunk(
|
|
c,
|
|
"</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
|
|
"</table><address>Mongoose v.%s</address></body></html>",
|
|
MG_VERSION);
|
|
mg_http_write_chunk(c, "", 0);
|
|
} else {
|
|
mg_http_reply(c, 400, "Cannot open dir");
|
|
LOG(LL_DEBUG, ("%p opendir(%s) -> %d", c->fd, dir, errno));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
|
|
const char *dir) {
|
|
char path[PATH_MAX + 2], root[sizeof(path) - 2], real[sizeof(path) - 2];
|
|
path[0] = root[0] = real[0] = '\0';
|
|
if (realpath(dir, root) == NULL)
|
|
LOG(LL_DEBUG, ("realpath(%s): %d", dir, errno));
|
|
if (!mg_is_dir(root)) {
|
|
mg_http_reply(c, 400, "Bad web root [%s]\n", root);
|
|
} else {
|
|
// NOTE(lsm): Xilinx snprintf does not 0-terminate the detination for
|
|
// the %.*s specifier, if the length is zero. Make sure hm->uri.len > 0
|
|
bool is_index = false;
|
|
size_t n = snprintf(path, sizeof(path), "%s%.*s", root, (int) hm->uri.len,
|
|
hm->uri.ptr);
|
|
while (n > 0 && n < sizeof(path) && path[n - 1] == '/') path[--n] = 0;
|
|
if (realpath(path, real) == NULL)
|
|
LOG(LL_DEBUG, ("realpath(%s): %d", path, errno));
|
|
// LOG(LL_INFO, ("[%s] [%s] [%s] [%s]", dir, root, path, real));
|
|
if (mg_is_dir(real)) {
|
|
strncat(real, "/index.html", sizeof(real) - strlen(real) - 1);
|
|
is_index = true;
|
|
}
|
|
if (strlen(real) < strlen(root) || memcmp(real, root, strlen(root)) != 0) {
|
|
mg_http_reply(c, 404, "Not found %.*s\n", hm->uri.len, hm->uri.ptr);
|
|
} else {
|
|
FILE *fp = fopen(real, "r");
|
|
#if MG_ENABLE_HTTP_DEBUG_ENDPOINT
|
|
snprintf(c->label, sizeof(c->label) - 1, "<-F %s", real);
|
|
#endif
|
|
if (is_index && fp == NULL) {
|
|
#if MG_ENABLE_DIRECTORY_LISTING
|
|
listdir(c, hm, real);
|
|
#else
|
|
mg_http_reply(c, 403, "%s", "Directory listing not supported");
|
|
#endif
|
|
} else {
|
|
mg_http_serve_file(c, hm, real, guess_content_type(real));
|
|
}
|
|
if (fp != NULL) fclose(fp);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void mg_http_creds(struct mg_http_message *hm, char *user, int userlen,
|
|
char *pass, int 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);
|
|
if (p != NULL) {
|
|
snprintf(user, userlen, "%.*s", (int) (p - buf), buf);
|
|
snprintf(pass, passlen, "%.*s", n - (int) (p - buf) - 1, p + 1);
|
|
}
|
|
} else if (v != NULL && v->len > 7 && memcmp(v->ptr, "Bearer ", 7) == 0) {
|
|
snprintf(pass, passlen, "%.*s", (int) v->len - 7, v->ptr + 7);
|
|
} else if ((v = mg_http_get_header(hm, "Cookie")) != NULL) {
|
|
size_t i;
|
|
for (i = 0; i < v->len - 13; i++) {
|
|
if (memcmp(&v->ptr[i], "access_token=", 13) == 0) {
|
|
const char *p2 = v->ptr + i + 13, *p3 = p2;
|
|
while (p2 < &v->ptr[v->len] && p2[0] != ';' && p2[0] != ' ') p2++;
|
|
snprintf(pass, passlen, "%.*s", (int) (p2 - p3), p3);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
mg_http_get_var(&hm->query, "access_token", pass, passlen);
|
|
}
|
|
}
|
|
|
|
bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) {
|
|
return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len);
|
|
}
|
|
|
|
static void http_cb(struct mg_connection *c, int ev, void *ev_data,
|
|
void *fn_data) {
|
|
if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
|
|
struct mg_http_message hm;
|
|
for (;;) {
|
|
int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
|
|
if (ev == MG_EV_CLOSE) {
|
|
hm.message.len = c->recv.len;
|
|
hm.body.len = hm.message.len - (hm.body.ptr - hm.message.ptr);
|
|
}
|
|
if (n < 0 && ev == MG_EV_READ) {
|
|
LOG(LL_ERROR, ("%p HTTP parse error", c->fd));
|
|
c->is_closing = 1;
|
|
break;
|
|
} else if (n > 0 && (size_t) c->recv.len >= hm.message.len) {
|
|
#if MG_ENABLE_HTTP_DEBUG_ENDPOINT
|
|
snprintf(c->label, sizeof(c->label) - 1, "<-[%.*s]", (int) hm.uri.len,
|
|
hm.uri.ptr);
|
|
if (mg_http_match_uri(&hm, "/debug/info")) {
|
|
struct mg_mgr *mgr = (struct mg_mgr *) fn_data;
|
|
struct mg_connection *x;
|
|
mg_printf(c, "%s\r\n",
|
|
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n");
|
|
for (x = mgr->conns; x != NULL; x = x->next) {
|
|
// LOG(LL_INFO, ("%p %s", x->fd, x->label));
|
|
mg_http_printf_chunk(
|
|
c,
|
|
"%-4p %-12s %04d.%04d/%04d.%04d"
|
|
" %d%d%d%d%d%d%d%d%d%d%d%d%d%d\n",
|
|
x->fd, x->label, x->recv.len, x->recv.size, x->send.len,
|
|
x->send.size, c->is_listening, c->is_client, c->is_accepted,
|
|
c->is_resolving, c->is_connecting, c->is_tls, c->is_tls_hs,
|
|
c->is_udp, c->is_websocket, c->is_hexdumping, c->is_draining,
|
|
c->is_closing, c->is_readable, c->is_writable);
|
|
}
|
|
mg_http_write_chunk(c, "", 0);
|
|
mg_iobuf_delete(&c->recv, hm.message.len);
|
|
continue;
|
|
}
|
|
#endif
|
|
mg_call(c, MG_EV_HTTP_MSG, &hm);
|
|
mg_iobuf_delete(&c->recv, hm.message.len);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
(void) fn_data;
|
|
(void) ev_data;
|
|
}
|
|
|
|
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, c->pfn_data = mgr;
|
|
#if MG_ENABLE_HTTP_DEBUG_ENDPOINT
|
|
snprintf(c->label, sizeof(c->label) - 1, "->%s", url);
|
|
#endif
|
|
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, c->pfn_data = mgr;
|
|
#if MG_ENABLE_HTTP_DEBUG_ENDPOINT
|
|
snprintf(c->label, sizeof(c->label) - 1, "<-LSN");
|
|
#endif
|
|
return c;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/iobuf.c"
|
|
#endif
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
void mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
|
|
if (new_size == 0) {
|
|
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 malloc/free only, to ease the
|
|
// porting to some obscure platforms like FreeRTOS
|
|
void *p = malloc(new_size);
|
|
if (p != NULL) {
|
|
memcpy(p, io->buf, io->size < new_size ? io->size : new_size);
|
|
free(io->buf);
|
|
io->buf = (unsigned char *) p;
|
|
io->size = new_size;
|
|
} else {
|
|
LOG(LL_ERROR,
|
|
("%lu->%lu", (unsigned long) io->size, (unsigned long) new_size));
|
|
}
|
|
}
|
|
}
|
|
|
|
void mg_iobuf_init(struct mg_iobuf *io, size_t size) {
|
|
io->buf = NULL;
|
|
io->len = io->size = 0;
|
|
if (size > 0) mg_iobuf_resize(io, size);
|
|
}
|
|
|
|
size_t mg_iobuf_append(struct mg_iobuf *io, const void *buf, size_t len,
|
|
size_t chunk_size) {
|
|
size_t new_size = io->len + len + chunk_size;
|
|
new_size -= new_size % chunk_size;
|
|
if (new_size != io->size) mg_iobuf_resize(io, new_size);
|
|
if (new_size != io->size) len = 0; // Realloc failure, append nothing
|
|
if (buf != NULL) memmove(io->buf + io->len, buf, len);
|
|
io->len += len;
|
|
return len;
|
|
}
|
|
|
|
size_t mg_iobuf_delete(struct mg_iobuf *io, size_t len) {
|
|
if (len > io->len) len = 0;
|
|
memmove(io->buf, io->buf + len, io->len - len);
|
|
io->len -= len;
|
|
return len;
|
|
}
|
|
|
|
void mg_iobuf_free(struct mg_iobuf *io) {
|
|
free(io->buf);
|
|
mg_iobuf_init(io, 0);
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/log.c"
|
|
#endif
|
|
|
|
|
|
|
|
#if MG_ENABLE_MGOS
|
|
#else
|
|
#if MG_ENABLE_LOG
|
|
static void mg_log_stdout(const void *buf, int len, void *userdata) {
|
|
(void) userdata;
|
|
fwrite(buf, 1, len, stdout);
|
|
}
|
|
|
|
static const char *s_spec = "2";
|
|
static void (*s_fn)(const void *, int, void *) = mg_log_stdout;
|
|
static void *s_fn_param = NULL;
|
|
|
|
void mg_log_set(const char *spec) {
|
|
LOG(LL_INFO, ("Setting log level to %s", spec));
|
|
s_spec = spec;
|
|
}
|
|
|
|
bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
|
|
// static unsigned long seq;
|
|
int max = LL_INFO;
|
|
struct mg_str k, v, s = mg_str(s_spec);
|
|
const char *p = strrchr(file, '/');
|
|
p = p == NULL ? file : p + 1;
|
|
|
|
if (s_fn == NULL) return false;
|
|
while (mg_next_comma_entry(&s, &k, &v)) {
|
|
if (v.len == 0) max = atoi(k.ptr);
|
|
if (v.len > 0 && strncmp(p, k.ptr, k.len) == 0) max = atoi(v.ptr);
|
|
}
|
|
|
|
if (level <= max) {
|
|
char timebuf[21], buf[50] = "";
|
|
time_t t = time(NULL);
|
|
struct tm *tm = gmtime(&t);
|
|
int n, tag;
|
|
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm);
|
|
tag = level == LL_ERROR ? 'E' : level == LL_INFO ? 'I' : ' ';
|
|
n = snprintf(buf, sizeof(buf), "%s %c %s:%d:%s", timebuf, tag, p, line,
|
|
fname);
|
|
if (n < 0 || n > (int) sizeof(buf) - 2) n = sizeof(buf) - 2;
|
|
while (n < (int) sizeof(buf) - 1) buf[n++] = ' ';
|
|
s_fn(buf, sizeof(buf) - 1, s_fn_param);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void mg_log(const char *fmt, ...) {
|
|
char mem[256], *buf = mem;
|
|
va_list ap;
|
|
int len = 0;
|
|
va_start(ap, fmt);
|
|
len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
|
va_end(ap);
|
|
s_fn(buf, len, s_fn_param);
|
|
s_fn("\n", 1, s_fn_param);
|
|
if (buf != mem) free(buf);
|
|
}
|
|
|
|
void mg_log_set_callback(void (*fn)(const void *, int, void *), void *param) {
|
|
s_fn = fn;
|
|
s_fn_param = param;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/lwip.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if MG_ENABLE_LWIP
|
|
#include <lwip/tcp.h>
|
|
#include <lwip/udp.h>
|
|
|
|
static void tcp_error_cb(void *arg, err_t err) {
|
|
struct mg_connection *c = (struct mg_connection *) arg;
|
|
mg_error(c, "%p err %ld", c->fd, err);
|
|
}
|
|
|
|
static err_t connect_cb(void *arg, struct tcp_pcb *pcb, err_t err) {
|
|
struct mg_connection *c = (struct mg_connection *) arg;
|
|
LOG(LL_DEBUG, ("err %ld, arg %p, pcb %p", err, arg, pcb));
|
|
c->is_connecting = 0;
|
|
if (err != ERR_OK) mg_error(c, "%p err %d", c->fd, err);
|
|
return err;
|
|
}
|
|
|
|
static err_t tcp_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p,
|
|
err_t err) {
|
|
struct mg_connection *c = (struct mg_connection *) arg;
|
|
LOG(LL_DEBUG,
|
|
("err %ld, pbuf %p/%d, io.len %d, io.size %d", err, p,
|
|
p == NULL ? 0 : (int) p->len, (int) c->recv.len, (int) c->recv.size));
|
|
if (err == ERR_OK && p != NULL) {
|
|
#if 0
|
|
if (s->io.size < s->io.len + p->len) {
|
|
char *buf = realloc(s->io.buf, s->io.len + p->len);
|
|
if (buf != NULL) {
|
|
s->io.buf = buf;
|
|
s->io.size = s->io.len + p->len;
|
|
} else {
|
|
return ERR_MEM;
|
|
}
|
|
}
|
|
// MLOG(LL_DEBUG, " --> cpy, %p %p", s->io.buf, p->payload);
|
|
memcpy(s->io.buf + s->io.len, p->payload, p->len);
|
|
s->io.len += p->len;
|
|
#endif
|
|
tcp_recved(pcb, p->len);
|
|
pbuf_free(p);
|
|
return err;
|
|
} else {
|
|
// rmsock(s);
|
|
return ERR_ABRT;
|
|
}
|
|
}
|
|
|
|
static void udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,
|
|
const ip_addr_t *addr, uint16_t port) {
|
|
LOG(LL_DEBUG,
|
|
("%p %p pbuf %p/%d port %hu", arg, pcb, p, p == NULL ? 0 : p->len, port));
|
|
}
|
|
|
|
static err_t tcp_sent_cb(void *arg, struct tcp_pcb *pcb, uint16_t len) {
|
|
LOG(LL_DEBUG, ("%p %d", pcb, (int) len));
|
|
return ERR_OK;
|
|
}
|
|
|
|
#if 0
|
|
static int ll_write(struct mg_connection *c, const void *buf, int len,
|
|
int *fail) {
|
|
int n = c->is_tls ? mg_tls_send(c, buf, len, fail)
|
|
: mg_sock_send(c, buf, len, fail);
|
|
LOG(*fail ? LL_ERROR : LL_VERBOSE_DEBUG,
|
|
("%p %c%c%c %d/%d %d", c->fd, c->is_tls ? 'T' : 't',
|
|
c->is_udp ? 'U' : 'u', c->is_connecting ? 'C' : 'c', n, len,
|
|
MG_SOCK_ERRNO));
|
|
if (n > 0 && c->is_hexdumping) mg_hexdump(c, "->", buf, n);
|
|
return n;
|
|
}
|
|
#endif
|
|
|
|
int mg_send(struct mg_connection *c, const void *buf, size_t len) {
|
|
if (c->is_udp) {
|
|
struct udp_pcb *pcb = (struct udp_pcb *) c->fd;
|
|
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
|
|
if (p != NULL) {
|
|
// p->payload = (void *) buf;
|
|
memcpy(p->payload, buf, len);
|
|
err_t err = udp_send(pcb, p);
|
|
pbuf_free(p);
|
|
LOG(LL_DEBUG,
|
|
("%p UDP %d bytes -> %x:%hu, err %ld", c->fd, (int) len,
|
|
(unsigned) *(uint32_t *) &pcb->remote_ip, pcb->remote_port, err));
|
|
if (err != ERR_OK) mg_error(c, "%p err %d", c->fd, err);
|
|
} else {
|
|
mg_error(c, "%p pbuf OOM", c->fd);
|
|
}
|
|
} else if (c->is_connecting) {
|
|
mg_iobuf_append(&c->send, buf, len, MG_IO_SIZE);
|
|
} else {
|
|
}
|
|
#if 0
|
|
int fail, n = c->is_udp
|
|
? ll_write(c, buf, (SOCKET) len, &fail)
|
|
: mg_iobuf_append(&c->send, buf, len, MG_IO_SIZE);
|
|
return n;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static struct mg_connection *mg_mkconn(const char *url) {
|
|
struct mg_connection *c = (struct mg_connection *) calloc(1, sizeof(*c));
|
|
typedef void *(*new_t)(void);
|
|
int is_udp = strncmp(url, "udp:", 4) == 0;
|
|
if (c == NULL) {
|
|
LOG(LL_ERROR, ("%s %s", url, "OOM"));
|
|
} else if ((c->fd = (is_udp ? (new_t) udp_new : (new_t) tcp_new)()) == NULL) {
|
|
LOG(LL_ERROR, ("%s new", url));
|
|
free(c);
|
|
c = NULL;
|
|
} else {
|
|
c->is_udp = is_udp;
|
|
}
|
|
return 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 = mg_mkconn(url);
|
|
struct mg_str host = mg_url_host(url);
|
|
if (c == NULL) return c;
|
|
c->next = mgr->conns;
|
|
mgr->conns = c;
|
|
// mg_set_non_blocking_mode((SOCKET) c->fd);
|
|
c->is_client = 1;
|
|
c->peer.port = mg_htons(mg_url_port(url));
|
|
c->fn = fn;
|
|
c->fn_data = fn_data;
|
|
c->is_hexdumping = 1;
|
|
if (c->is_udp) {
|
|
udp_bind(c->fd, IP_ADDR_ANY, 0);
|
|
udp_recv(c->fd, udp_recv_cb, c);
|
|
} else {
|
|
tcp_arg(c->fd, c);
|
|
tcp_err(c->fd, tcp_error_cb);
|
|
tcp_sent(c->fd, tcp_sent_cb);
|
|
tcp_recv(c->fd, tcp_recv_cb);
|
|
tcp_bind(c->fd, IP_ADDR_ANY, 0);
|
|
tcp_nagle_disable((struct tcp_pcb *) c->fd);
|
|
}
|
|
LOG(LL_DEBUG, ("%p -> %s %s", c->fd, url, c->is_udp ? "UDP" : "TCP"));
|
|
mg_resolve(mgr, c, &host, mgr->dnstimeout);
|
|
return c;
|
|
}
|
|
|
|
void mg_connect_resolved(struct mg_connection *c) {
|
|
char buf[40];
|
|
ip_addr_t ipaddr;
|
|
memcpy(&ipaddr, &c->peer.ip, sizeof(ipaddr));
|
|
mg_call(c, MG_EV_RESOLVE, NULL);
|
|
LOG(LL_DEBUG, ("%p resolved to %s", c->fd, mg_straddr(c, buf, sizeof(buf))));
|
|
err_t err = c->is_udp ? udp_connect((struct udp_pcb *) c->fd, &ipaddr,
|
|
mg_ntohs(c->peer.port))
|
|
: tcp_connect((struct tcp_pcb *) c->fd, &ipaddr,
|
|
mg_ntohs(c->peer.port), connect_cb);
|
|
if (c->is_udp) c->is_connecting = 0;
|
|
if (err != ERR_OK) mg_error(c, "%p failed, err %d", c->fd, err);
|
|
}
|
|
|
|
static err_t accept_cb(void *arg, struct tcp_pcb *pcb, err_t e) {
|
|
LOG(LL_DEBUG, ("%p err %ld, pcb %p", arg, e, pcb));
|
|
return ERR_OK;
|
|
}
|
|
|
|
struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url,
|
|
mg_event_handler_t fn, void *fn_data) {
|
|
struct mg_connection *c = mg_mkconn(url);
|
|
struct mg_str host = mg_url_host(url);
|
|
uint16_t port = mg_url_port(url);
|
|
uint32_t ipaddr;
|
|
err_t err;
|
|
if (c == NULL) return c;
|
|
mg_aton(host.ptr, &ipaddr);
|
|
if (!mg_vcasecmp(&host, "localhost")) ipaddr = mg_htonl(0x7f000001);
|
|
if ((err = tcp_bind(c->fd, (ip_addr_t *) &ipaddr, port)) != ERR_OK) {
|
|
mg_error(c, "%p tcp_bind(%x:%hu) -> %ld", c->fd, ipaddr, port, err);
|
|
} else {
|
|
tcp_listen(c->fd);
|
|
tcp_accept(c->fd, accept_cb);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
|
|
LOG(LL_DEBUG, ("%p %d", mgr, ms));
|
|
mg_usleep(200 * 1000);
|
|
mg_timer_poll(mg_millis());
|
|
}
|
|
|
|
void mg_mgr_init(struct mg_mgr *mgr) {
|
|
(void) mgr;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/md5.c"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#if MG_ENABLE_MD5
|
|
#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER)
|
|
#define BYTE_ORDER __BYTE_ORDER
|
|
#ifndef LITTLE_ENDIAN
|
|
#define LITTLE_ENDIAN __LITTLE_ENDIAN
|
|
#endif /* LITTLE_ENDIAN */
|
|
#ifndef BIG_ENDIAN
|
|
#define BIG_ENDIAN __LITTLE_ENDIAN
|
|
#endif /* BIG_ENDIAN */
|
|
#endif /* BYTE_ORDER */
|
|
|
|
static void mg_byte_reverse(unsigned char *buf, unsigned longs) {
|
|
/* Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN */
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
do {
|
|
uint32_t t = (uint32_t)((unsigned) buf[3] << 8 | buf[2]) << 16 |
|
|
((unsigned) buf[1] << 8 | buf[0]);
|
|
*(uint32_t *) buf = t;
|
|
buf += 4;
|
|
} while (--longs);
|
|
#else
|
|
(void) buf;
|
|
(void) longs;
|
|
#endif
|
|
}
|
|
|
|
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
|
#define F2(x, y, z) F1(z, x, y)
|
|
#define F3(x, y, z) (x ^ y ^ z)
|
|
#define F4(x, y, z) (y ^ (x | ~z))
|
|
|
|
#define MD5STEP(f, w, x, y, z, data, s) \
|
|
(w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
|
|
|
|
/*
|
|
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
|
|
* initialization constants.
|
|
*/
|
|
void mg_md5_init(mg_md5_ctx *ctx) {
|
|
ctx->buf[0] = 0x67452301;
|
|
ctx->buf[1] = 0xefcdab89;
|
|
ctx->buf[2] = 0x98badcfe;
|
|
ctx->buf[3] = 0x10325476;
|
|
|
|
ctx->bits[0] = 0;
|
|
ctx->bits[1] = 0;
|
|
}
|
|
|
|
static void mg_md5_transform(uint32_t buf[4], uint32_t const in[16]) {
|
|
register 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_CMD_CONNECT 1
|
|
#define MQTT_CMD_CONNACK 2
|
|
#define MQTT_CMD_PUBLISH 3
|
|
#define MQTT_CMD_PUBACK 4
|
|
#define MQTT_CMD_PUBREC 5
|
|
#define MQTT_CMD_PUBREL 6
|
|
#define MQTT_CMD_PUBCOMP 7
|
|
#define MQTT_CMD_SUBSCRIBE 8
|
|
#define MQTT_CMD_SUBACK 9
|
|
#define MQTT_CMD_UNSUBSCRIBE 10
|
|
#define MQTT_CMD_UNSUBACK 11
|
|
#define MQTT_CMD_PINGREQ 12
|
|
#define MQTT_CMD_PINGRESP 13
|
|
#define MQTT_CMD_DISCONNECT 14
|
|
|
|
#define MQTT_CLEAN_SESSION 0x02
|
|
#define MQTT_HAS_WILL 0x04
|
|
#define MQTT_WILL_RETAIN 0x20
|
|
#define MQTT_HAS_PASSWORD 0x40
|
|
#define MQTT_HAS_USER_NAME 0x80
|
|
#define MQTT_GET_WILL_QOS(flags) (((flags) &0x18) >> 3)
|
|
#define MQTT_SET_WILL_QOS(flags, qos) (flags) = ((flags) & ~0x18) | ((qos) << 3)
|
|
#define MQTT_QOS(qos) ((qos) << 1)
|
|
#define MQTT_GET_QOS(flags) (((flags) &0x6) >> 1)
|
|
#define MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1)
|
|
|
|
enum { MQTT_OK, MQTT_INCOMPLETE, MQTT_MALFORMED };
|
|
|
|
struct mqtt_message {
|
|
size_t len, topic_offset, topic_len, data_offset, data_len;
|
|
uint16_t id;
|
|
uint8_t cmd, qos, connack_ret_code;
|
|
};
|
|
|
|
static void 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] = (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, vlen - buf);
|
|
}
|
|
|
|
static void mg_send_u16(struct mg_connection *c, uint16_t value) {
|
|
mg_send(c, &value, sizeof(value));
|
|
}
|
|
|
|
static void mqtt_login(struct mg_connection *c, const char *url,
|
|
struct mg_mqtt_opts *opts) {
|
|
uint32_t total_len = 7 + 1 + 2 + 2;
|
|
uint16_t flags = (opts->qos & 3) << 3;
|
|
struct mg_str user = mg_url_user(url);
|
|
struct mg_str pass = mg_url_pass(url);
|
|
|
|
if (user.len > 0) {
|
|
total_len += 2 + (uint32_t) user.len;
|
|
flags |= MQTT_HAS_USER_NAME;
|
|
}
|
|
if (pass.len > 0) {
|
|
total_len += 2 + (uint32_t) pass.len;
|
|
flags |= MQTT_HAS_PASSWORD;
|
|
}
|
|
if (opts->will_topic.len > 0 && opts->will_message.len > 0) {
|
|
total_len +=
|
|
4 + (uint32_t) opts->will_topic.len + (uint32_t) opts->will_message.len;
|
|
flags |= MQTT_HAS_WILL;
|
|
}
|
|
if (opts->clean || opts->client_id.len == 0) flags |= MQTT_CLEAN_SESSION;
|
|
if (opts->will_retain) flags |= MQTT_WILL_RETAIN;
|
|
total_len += (uint32_t) opts->client_id.len;
|
|
|
|
mqtt_send_header(c, MQTT_CMD_CONNECT, 0, total_len);
|
|
mg_send(c, "\00\04MQTT\04", 7);
|
|
mg_send(c, &flags, 1);
|
|
// keepalive == 0 means "do not disconnect us!"
|
|
mg_send_u16(c, mg_htons((uint16_t) opts->keepalive));
|
|
mg_send_u16(c, mg_htons((uint16_t) opts->client_id.len));
|
|
mg_send(c, opts->client_id.ptr, opts->client_id.len);
|
|
if (flags & MQTT_HAS_WILL) {
|
|
mg_send_u16(c, mg_htons((uint16_t) opts->will_topic.len));
|
|
mg_send(c, opts->will_topic.ptr, opts->will_topic.len);
|
|
mg_send_u16(c, mg_htons((uint16_t) opts->will_message.len));
|
|
mg_send(c, opts->will_message.ptr, opts->will_message.len);
|
|
}
|
|
if (user.len > 0) {
|
|
mg_send_u16(c, mg_htons((uint16_t) user.len));
|
|
mg_send(c, user.ptr, user.len);
|
|
}
|
|
if (pass.len > 0) {
|
|
mg_send_u16(c, mg_htons((uint16_t) pass.len));
|
|
mg_send(c, pass.ptr, pass.len);
|
|
}
|
|
}
|
|
|
|
void mg_mqtt_pub(struct mg_connection *c, struct mg_str *topic,
|
|
struct mg_str *data) {
|
|
uint8_t flags = MQTT_QOS(1);
|
|
uint32_t total_len = 2 + (uint32_t) topic->len + (uint32_t) data->len;
|
|
LOG(LL_DEBUG, ("%p [%.*s] -> [%.*s]", c->fd, (int) topic->len,
|
|
(char *) topic->ptr, (int) data->len, (char *) data->ptr));
|
|
if (MQTT_GET_QOS(flags) > 0) total_len += 2;
|
|
mqtt_send_header(c, MQTT_CMD_PUBLISH, flags, total_len);
|
|
mg_send_u16(c, mg_htons((uint16_t) topic->len));
|
|
mg_send(c, topic->ptr, topic->len);
|
|
if (MQTT_GET_QOS(flags) > 0) {
|
|
static uint16_t s_id;
|
|
if (++s_id == 0) s_id++;
|
|
mg_send_u16(c, mg_htons(s_id));
|
|
}
|
|
mg_send(c, data->ptr, data->len);
|
|
}
|
|
|
|
void mg_mqtt_sub(struct mg_connection *c, struct mg_str *topic) {
|
|
static uint16_t s_id;
|
|
uint8_t qos = 1;
|
|
uint32_t total_len = 2 + (uint32_t) topic->len + 2 + 1;
|
|
mqtt_send_header(c, MQTT_CMD_SUBSCRIBE, (uint8_t) MQTT_QOS(qos), total_len);
|
|
if (++s_id == 0) ++s_id;
|
|
mg_send_u16(c, mg_htons(s_id));
|
|
mg_send_u16(c, mg_htons((uint16_t) topic->len));
|
|
mg_send(c, topic->ptr, topic->len);
|
|
mg_send(c, &qos, sizeof(qos));
|
|
}
|
|
|
|
static int parse(const uint8_t *in, size_t inlen, struct mqtt_message *m) {
|
|
uint8_t lc = 0, *p, *end;
|
|
uint32_t len = 0, len_len = 0;
|
|
|
|
if (inlen < 2) return MQTT_INCOMPLETE;
|
|
m->cmd = in[0] >> 4;
|
|
m->qos = (in[0] >> 1) & 3;
|
|
|
|
len = len_len = 0;
|
|
p = (uint8_t *) in + 1;
|
|
while ((size_t)(p - in) < inlen) {
|
|
lc = *((uint8_t *) p++);
|
|
len += (lc & 0x7f) << 7 * len_len;
|
|
len_len++;
|
|
if (!(lc & 0x80)) break;
|
|
if (len_len >= 4) return MQTT_MALFORMED;
|
|
}
|
|
|
|
end = p + len;
|
|
if (lc & 0x80 || end > in + inlen) return MQTT_INCOMPLETE;
|
|
m->len = (int) (end - in);
|
|
|
|
switch (m->cmd) {
|
|
case MQTT_CMD_CONNACK:
|
|
if (end - p < 2) return MQTT_MALFORMED;
|
|
m->connack_ret_code = p[1];
|
|
break;
|
|
case MQTT_CMD_PUBACK:
|
|
case MQTT_CMD_PUBREC:
|
|
case MQTT_CMD_PUBREL:
|
|
case MQTT_CMD_PUBCOMP:
|
|
case MQTT_CMD_SUBACK:
|
|
if (p + 2 > end) return MQTT_MALFORMED;
|
|
m->id = (p[0] << 8) | p[1];
|
|
break;
|
|
case MQTT_CMD_PUBLISH: {
|
|
if (p + 2 > end) return MQTT_MALFORMED;
|
|
m->topic_len = (p[0] << 8) | p[1];
|
|
m->topic_offset = p + 2 - in;
|
|
p += 2 + m->topic_len;
|
|
if (p > end) return MQTT_MALFORMED;
|
|
if (m->qos > 0) {
|
|
if (p + 2 > end) return MQTT_MALFORMED;
|
|
m->id = (p[0] << 8) | p[1];
|
|
p += 2;
|
|
}
|
|
if (p > end) return MQTT_MALFORMED;
|
|
m->data_offset = p - in;
|
|
m->data_len = end - p;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return MQTT_OK;
|
|
}
|
|
|
|
int mg_mqtt_parse(const unsigned char *s, int n, struct mg_mqtt_message *mm) {
|
|
struct mqtt_message m;
|
|
int rc = parse(s, n, &m);
|
|
if (rc == MQTT_OK) {
|
|
mm->topic.ptr = (char *) s + m.topic_offset;
|
|
mm->topic.len = m.topic_len;
|
|
mm->data.ptr = (char *) s + m.data_offset;
|
|
mm->data.len = m.data_len;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void mqtt_cb(struct mg_connection *c, int ev, void *ev_data,
|
|
void *fn_data) {
|
|
if (ev == MG_EV_READ) {
|
|
struct mqtt_message m;
|
|
memset(&m, 0, sizeof(m));
|
|
for (;;) {
|
|
int rc = parse(c->recv.buf, c->recv.len, &m);
|
|
if (rc == MQTT_MALFORMED) {
|
|
LOG(LL_ERROR, ("%p MQTT malformed message", c->fd));
|
|
c->is_closing = 1;
|
|
break;
|
|
} else if (rc == MQTT_OK) {
|
|
LOG(LL_VERBOSE_DEBUG,
|
|
("%p MQTT CMD %d len %d [%.*s]", c->fd, m.cmd, (int) m.len,
|
|
(int) m.data_len, (char *) c->recv.buf + m.data_offset));
|
|
switch (m.cmd) {
|
|
case MQTT_CMD_CONNACK:
|
|
mg_call(c, MG_EV_MQTT_OPEN, &m.connack_ret_code);
|
|
if (m.connack_ret_code == 0) {
|
|
LOG(LL_INFO, ("%p Connected", c->fd));
|
|
} else {
|
|
LOG(LL_ERROR,
|
|
("%p MQTT auth failed, code %d", c->fd, m.connack_ret_code));
|
|
c->is_closing = 1;
|
|
}
|
|
break;
|
|
case MQTT_CMD_PUBLISH: {
|
|
struct mg_mqtt_message evd = {
|
|
{(char *) c->recv.buf + m.topic_offset, m.topic_len},
|
|
{(char *) c->recv.buf + m.data_offset, m.data_len}};
|
|
LOG(LL_DEBUG, ("%p [%.*s] -> [%.*s]", c->fd, (int) evd.topic.len,
|
|
evd.topic.ptr, (int) evd.data.len, evd.data.ptr));
|
|
mg_call(c, MG_EV_MQTT_MSG, &evd);
|
|
break;
|
|
}
|
|
}
|
|
mg_iobuf_delete(&c->recv, m.len);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
(void) ev_data;
|
|
(void) fn_data;
|
|
}
|
|
|
|
struct mg_connection *mg_mqtt_connect(struct mg_mgr *mgr, const char *url,
|
|
struct mg_mqtt_opts *opts,
|
|
mg_event_handler_t fn, void *fn_data) {
|
|
struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
|
|
if (c != NULL) {
|
|
struct mg_mqtt_opts empty;
|
|
memset(&empty, 0, sizeof(empty));
|
|
mqtt_login(c, url, opts == NULL ? &empty : opts);
|
|
c->pfn = mqtt_cb;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/net.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
int mg_vprintf(struct mg_connection *c, const char *fmt, va_list ap) {
|
|
char mem[256], *buf = mem;
|
|
int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
|
len = mg_send(c, buf, len);
|
|
if (buf != mem) free(buf);
|
|
return len;
|
|
}
|
|
|
|
int mg_printf(struct mg_connection *c, const char *fmt, ...) {
|
|
int len = 0;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
len = mg_vprintf(c, fmt, ap);
|
|
va_end(ap);
|
|
return len;
|
|
}
|
|
|
|
char *mg_straddr(struct mg_connection *c, char *buf, size_t len) {
|
|
unsigned char *p = (unsigned char *) &c->peer.ip;
|
|
snprintf(buf, len, "%d.%d.%d.%d:%hu", p[0], p[1], p[2], p[3],
|
|
mg_ntohs(c->peer.port));
|
|
return buf;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/sha1.c"
|
|
#endif
|
|
/* Copyright(c) By Steve Reid <steve@edmweb.com> */
|
|
/* 100% Public Domain */
|
|
|
|
#include <string.h>
|
|
|
|
/*
|
|
* clang with std=-c99 uses __LITTLE_ENDIAN, by default
|
|
* while for ex, RTOS gcc - LITTLE_ENDIAN, by default
|
|
* it depends on __USE_BSD, but let's have everything
|
|
*/
|
|
#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER)
|
|
#define BYTE_ORDER __BYTE_ORDER
|
|
#ifndef LITTLE_ENDIAN
|
|
#define LITTLE_ENDIAN __LITTLE_ENDIAN
|
|
#endif /* LITTLE_ENDIAN */
|
|
#ifndef BIG_ENDIAN
|
|
#define BIG_ENDIAN __LITTLE_ENDIAN
|
|
#endif /* BIG_ENDIAN */
|
|
#endif /* BYTE_ORDER */
|
|
|
|
union char64long16 {
|
|
unsigned char c[64];
|
|
uint32_t l[16];
|
|
};
|
|
|
|
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
|
|
|
static uint32_t blk0(union char64long16 *block, int i) {
|
|
/* Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN */
|
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
|
block->l[i] =
|
|
(rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF);
|
|
#endif
|
|
return block->l[i];
|
|
}
|
|
|
|
/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
|
|
#undef blk
|
|
#undef R0
|
|
#undef R1
|
|
#undef R2
|
|
#undef R3
|
|
#undef R4
|
|
|
|
#define blk(i) \
|
|
(block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
|
|
block->l[(i + 2) & 15] ^ block->l[i & 15], \
|
|
1))
|
|
#define R0(v, w, x, y, z, i) \
|
|
z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
|
|
w = rol(w, 30);
|
|
#define R1(v, w, x, y, z, i) \
|
|
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
|
|
w = rol(w, 30);
|
|
#define R2(v, w, x, y, z, i) \
|
|
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
|
|
w = rol(w, 30);
|
|
#define R3(v, w, x, y, z, i) \
|
|
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
|
|
w = rol(w, 30);
|
|
#define R4(v, w, x, y, z, i) \
|
|
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
|
|
w = rol(w, 30);
|
|
|
|
void mg_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {
|
|
uint32_t a, b, c, d, e;
|
|
union char64long16 block[1];
|
|
|
|
memcpy(block, buffer, 64);
|
|
a = state[0];
|
|
b = state[1];
|
|
c = state[2];
|
|
d = state[3];
|
|
e = state[4];
|
|
R0(a, b, c, d, e, 0);
|
|
R0(e, a, b, c, d, 1);
|
|
R0(d, e, a, b, c, 2);
|
|
R0(c, d, e, a, b, 3);
|
|
R0(b, c, d, e, a, 4);
|
|
R0(a, b, c, d, e, 5);
|
|
R0(e, a, b, c, d, 6);
|
|
R0(d, e, a, b, c, 7);
|
|
R0(c, d, e, a, b, 8);
|
|
R0(b, c, d, e, a, 9);
|
|
R0(a, b, c, d, e, 10);
|
|
R0(e, a, b, c, d, 11);
|
|
R0(d, e, a, b, c, 12);
|
|
R0(c, d, e, a, b, 13);
|
|
R0(b, c, d, e, a, 14);
|
|
R0(a, b, c, d, e, 15);
|
|
R1(e, a, b, c, d, 16);
|
|
R1(d, e, a, b, c, 17);
|
|
R1(c, d, e, a, b, 18);
|
|
R1(b, c, d, e, a, 19);
|
|
R2(a, b, c, d, e, 20);
|
|
R2(e, a, b, c, d, 21);
|
|
R2(d, e, a, b, c, 22);
|
|
R2(c, d, e, a, b, 23);
|
|
R2(b, c, d, e, a, 24);
|
|
R2(a, b, c, d, e, 25);
|
|
R2(e, a, b, c, d, 26);
|
|
R2(d, e, a, b, c, 27);
|
|
R2(c, d, e, a, b, 28);
|
|
R2(b, c, d, e, a, 29);
|
|
R2(a, b, c, d, e, 30);
|
|
R2(e, a, b, c, d, 31);
|
|
R2(d, e, a, b, c, 32);
|
|
R2(c, d, e, a, b, 33);
|
|
R2(b, c, d, e, a, 34);
|
|
R2(a, b, c, d, e, 35);
|
|
R2(e, a, b, c, d, 36);
|
|
R2(d, e, a, b, c, 37);
|
|
R2(c, d, e, a, b, 38);
|
|
R2(b, c, d, e, a, 39);
|
|
R3(a, b, c, d, e, 40);
|
|
R3(e, a, b, c, d, 41);
|
|
R3(d, e, a, b, c, 42);
|
|
R3(c, d, e, a, b, 43);
|
|
R3(b, c, d, e, a, 44);
|
|
R3(a, b, c, d, e, 45);
|
|
R3(e, a, b, c, d, 46);
|
|
R3(d, e, a, b, c, 47);
|
|
R3(c, d, e, a, b, 48);
|
|
R3(b, c, d, e, a, 49);
|
|
R3(a, b, c, d, e, 50);
|
|
R3(e, a, b, c, d, 51);
|
|
R3(d, e, a, b, c, 52);
|
|
R3(c, d, e, a, b, 53);
|
|
R3(b, c, d, e, a, 54);
|
|
R3(a, b, c, d, e, 55);
|
|
R3(e, a, b, c, d, 56);
|
|
R3(d, e, a, b, c, 57);
|
|
R3(c, d, e, a, b, 58);
|
|
R3(b, c, d, e, a, 59);
|
|
R4(a, b, c, d, e, 60);
|
|
R4(e, a, b, c, d, 61);
|
|
R4(d, e, a, b, c, 62);
|
|
R4(c, d, e, a, b, 63);
|
|
R4(b, c, d, e, a, 64);
|
|
R4(a, b, c, d, e, 65);
|
|
R4(e, a, b, c, d, 66);
|
|
R4(d, e, a, b, c, 67);
|
|
R4(c, d, e, a, b, 68);
|
|
R4(b, c, d, e, a, 69);
|
|
R4(a, b, c, d, e, 70);
|
|
R4(e, a, b, c, d, 71);
|
|
R4(d, e, a, b, c, 72);
|
|
R4(c, d, e, a, b, 73);
|
|
R4(b, c, d, e, a, 74);
|
|
R4(a, b, c, d, e, 75);
|
|
R4(e, a, b, c, d, 76);
|
|
R4(d, e, a, b, c, 77);
|
|
R4(c, d, e, a, b, 78);
|
|
R4(b, c, d, e, a, 79);
|
|
state[0] += a;
|
|
state[1] += b;
|
|
state[2] += c;
|
|
state[3] += d;
|
|
state[4] += e;
|
|
/* Erase working structures. The order of operations is important,
|
|
* used to ensure that compiler doesn't optimize those out. */
|
|
memset(block, 0, sizeof(block));
|
|
a = b = c = d = e = 0;
|
|
(void) a;
|
|
(void) b;
|
|
(void) c;
|
|
(void) d;
|
|
(void) e;
|
|
}
|
|
|
|
void mg_sha1_init(mg_sha1_ctx *context) {
|
|
context->state[0] = 0x67452301;
|
|
context->state[1] = 0xEFCDAB89;
|
|
context->state[2] = 0x98BADCFE;
|
|
context->state[3] = 0x10325476;
|
|
context->state[4] = 0xC3D2E1F0;
|
|
context->count[0] = context->count[1] = 0;
|
|
}
|
|
|
|
void mg_sha1_update(mg_sha1_ctx *context, const unsigned char *data,
|
|
size_t len) {
|
|
size_t i, j;
|
|
|
|
j = context->count[0];
|
|
if ((context->count[0] += (uint32_t) len << 3) < j) context->count[1]++;
|
|
context->count[1] += (uint32_t)(len >> 29);
|
|
j = (j >> 3) & 63;
|
|
if ((j + len) > 63) {
|
|
memcpy(&context->buffer[j], data, (i = 64 - j));
|
|
mg_sha1_transform(context->state, context->buffer);
|
|
for (; i + 63 < len; i += 64) {
|
|
mg_sha1_transform(context->state, &data[i]);
|
|
}
|
|
j = 0;
|
|
} else
|
|
i = 0;
|
|
memcpy(&context->buffer[j], &data[i], len - i);
|
|
}
|
|
|
|
void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *context) {
|
|
unsigned i;
|
|
unsigned char finalcount[8], c;
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
|
|
((3 - (i & 3)) * 8)) &
|
|
255);
|
|
}
|
|
c = 0200;
|
|
mg_sha1_update(context, &c, 1);
|
|
while ((context->count[0] & 504) != 448) {
|
|
c = 0000;
|
|
mg_sha1_update(context, &c, 1);
|
|
}
|
|
mg_sha1_update(context, finalcount, 8);
|
|
for (i = 0; i < 20; i++) {
|
|
digest[i] =
|
|
(unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
|
|
}
|
|
memset(context, '\0', sizeof(*context));
|
|
memset(&finalcount, '\0', sizeof(finalcount));
|
|
}
|
|
|
|
void mg_hmac_sha1(const unsigned char *key, size_t keylen,
|
|
const unsigned char *data, size_t datalen,
|
|
unsigned char out[20]) {
|
|
mg_sha1_ctx ctx;
|
|
unsigned char buf1[64], buf2[64], tmp_key[20], i;
|
|
|
|
if (keylen > sizeof(buf1)) {
|
|
mg_sha1_init(&ctx);
|
|
mg_sha1_update(&ctx, key, keylen);
|
|
mg_sha1_final(tmp_key, &ctx);
|
|
key = tmp_key;
|
|
keylen = sizeof(tmp_key);
|
|
}
|
|
|
|
memset(buf1, 0, sizeof(buf1));
|
|
memset(buf2, 0, sizeof(buf2));
|
|
memcpy(buf1, key, keylen);
|
|
memcpy(buf2, key, keylen);
|
|
|
|
for (i = 0; i < sizeof(buf1); i++) {
|
|
buf1[i] ^= 0x36;
|
|
buf2[i] ^= 0x5c;
|
|
}
|
|
|
|
mg_sha1_init(&ctx);
|
|
mg_sha1_update(&ctx, buf1, sizeof(buf1));
|
|
mg_sha1_update(&ctx, data, datalen);
|
|
mg_sha1_final(out, &ctx);
|
|
|
|
mg_sha1_init(&ctx);
|
|
mg_sha1_update(&ctx, buf2, sizeof(buf2));
|
|
mg_sha1_update(&ctx, out, 20);
|
|
mg_sha1_final(out, &ctx);
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/sntp.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define SNTP_INTERVAL_SEC (3600)
|
|
#define SNTP_TIME_OFFSET 2208988800
|
|
|
|
static unsigned long s_sntmp_next;
|
|
|
|
int mg_sntp_parse(const unsigned char *buf, size_t len, struct timeval *tv) {
|
|
int mode = len > 0 ? buf[0] & 7 : 0, res = -1;
|
|
if (len < 48) {
|
|
LOG(LL_ERROR, ("%s", "corrupt packet"));
|
|
} else if ((buf[0] & 0x38) >> 3 != 4) {
|
|
LOG(LL_ERROR, ("%s", "wrong version"));
|
|
} else if (mode != 4 && mode != 5) {
|
|
LOG(LL_ERROR, ("%s", "not a server reply"));
|
|
} else if (buf[1] == 0) {
|
|
LOG(LL_ERROR, ("%s", "server sent a kiss of death"));
|
|
} else {
|
|
uint32_t *data = (uint32_t *) &buf[40];
|
|
tv->tv_sec = mg_ntohl(data[0]) - SNTP_TIME_OFFSET;
|
|
tv->tv_usec = mg_ntohl(data[1]);
|
|
s_sntmp_next = tv->tv_sec + SNTP_INTERVAL_SEC;
|
|
res = 0;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
|
|
if (ev == MG_EV_READ) {
|
|
struct timeval tv = {0, 0};
|
|
if (mg_sntp_parse(c->recv.buf, c->recv.len, &tv) == 0) {
|
|
mg_call(c, MG_EV_SNTP_TIME, &tv);
|
|
LOG(LL_DEBUG, ("%u.%u, next at %lu", (unsigned) tv.tv_sec,
|
|
(unsigned) tv.tv_usec, s_sntmp_next));
|
|
}
|
|
c->recv.len = 0; // Clear receive buffer
|
|
} else if (ev == MG_EV_RESOLVE) {
|
|
mg_sntp_send(c, time(NULL));
|
|
} else if (ev == MG_EV_CLOSE) {
|
|
// mg_fn_del(c, sntp_cb);
|
|
}
|
|
(void) fnd;
|
|
(void) evd;
|
|
}
|
|
|
|
void mg_sntp_send(struct mg_connection *c, unsigned long utc) {
|
|
if (c->is_resolving) {
|
|
LOG(LL_ERROR, ("%p wait until resolved", c->fd));
|
|
} else if (utc > s_sntmp_next) {
|
|
uint8_t buf[48] = {0};
|
|
s_sntmp_next = utc + SNTP_INTERVAL_SEC;
|
|
buf[0] = (3 << 6) | (4 << 3) | 3;
|
|
mg_send(c, buf, sizeof(buf));
|
|
LOG(LL_DEBUG,
|
|
("%p request sent, ct %lu, next at %lu", c->fd, utc, s_sntmp_next));
|
|
}
|
|
}
|
|
|
|
struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url,
|
|
mg_event_handler_t fn, void *fnd) {
|
|
struct mg_connection *c = NULL;
|
|
if (url == NULL) url = "udp://time.google.com:123";
|
|
if ((c = mg_connect(mgr, url, fn, fnd)) != NULL) c->pfn = sntp_cb;
|
|
return c;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/sock.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if MG_ENABLE_SOCKET
|
|
#if defined(_WIN32)
|
|
#define MG_SOCK_ERRNO WSAGetLastError()
|
|
#define FD(C_) ((SOCKET)(C_)->fd)
|
|
#elif MG_ARCH == MG_ARCH_FREERTOS
|
|
#define MG_SOCK_ERRNO errno
|
|
typedef Socket_t SOCKET;
|
|
#define FD(C_) ((long) (C_)->fd)
|
|
#define INVALID_SOCKET FREERTOS_INVALID_SOCKET
|
|
#else
|
|
#define MG_SOCK_ERRNO errno
|
|
#define closesocket(x) close(x)
|
|
#define INVALID_SOCKET (-1)
|
|
typedef int SOCKET;
|
|
#define FD(C_) ((SOCKET)(long) (C_)->fd)
|
|
#endif
|
|
|
|
#ifndef MSG_NONBLOCKING
|
|
#define MSG_NONBLOCKING 0
|
|
#endif
|
|
|
|
union usa {
|
|
struct sockaddr sa;
|
|
struct sockaddr_in sin;
|
|
#if MG_ENABLE_IPV6
|
|
struct sockaddr_in6 sin6;
|
|
#endif
|
|
};
|
|
|
|
static union usa tousa(struct mg_addr *a) {
|
|
union usa usa;
|
|
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;
|
|
return usa;
|
|
}
|
|
|
|
static int mg_sock_failed(void) {
|
|
int err = MG_SOCK_ERRNO;
|
|
return err != EINPROGRESS && err != EWOULDBLOCK
|
|
#ifndef WINCE
|
|
&& err != EAGAIN && err != EINTR
|
|
#endif
|
|
#ifdef _WIN32
|
|
&& err != WSAEINTR && err != WSAEWOULDBLOCK
|
|
#endif
|
|
;
|
|
}
|
|
|
|
static struct mg_connection *alloc_conn(struct mg_mgr *mgr, int is_client,
|
|
SOCKET fd) {
|
|
struct mg_connection *c = (struct mg_connection *) calloc(1, sizeof(*c));
|
|
if (c != NULL) {
|
|
c->is_client = is_client;
|
|
c->fd = (void *) (long) fd;
|
|
c->mgr = mgr;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static int mg_sock_recv(struct mg_connection *c, void *buf, int len,
|
|
int *fail) {
|
|
int n = 0;
|
|
if (c->is_udp) {
|
|
union usa usa;
|
|
socklen_t slen = sizeof(usa.sin);
|
|
n = recvfrom(FD(c), buf, len, 0, &usa.sa, &slen);
|
|
if (n > 0) {
|
|
c->peer.ip = *(uint32_t *) &usa.sin.sin_addr;
|
|
c->peer.port = usa.sin.sin_port;
|
|
}
|
|
} else {
|
|
n = recv(FD(c), buf, len, MSG_NONBLOCKING);
|
|
}
|
|
*fail = (n == 0) || (n < 0 && mg_sock_failed());
|
|
return n;
|
|
}
|
|
|
|
static int mg_sock_send(struct mg_connection *c, const void *buf, int len,
|
|
int *fail) {
|
|
int n = 0;
|
|
if (c->is_udp) {
|
|
union usa usa = tousa(&c->peer);
|
|
n = sendto(FD(c), buf, len, 0, &usa.sa, sizeof(usa.sin));
|
|
} else {
|
|
n = send(FD(c), buf, len, MSG_NONBLOCKING);
|
|
}
|
|
*fail = (n == 0) || (n < 0 && mg_sock_failed());
|
|
return n;
|
|
}
|
|
|
|
static int ll_read(struct mg_connection *c, void *buf, int len, int *fail) {
|
|
int n = c->is_tls ? mg_tls_recv(c, buf, len, fail)
|
|
: mg_sock_recv(c, buf, len, fail);
|
|
LOG(*fail ? LL_DEBUG : LL_VERBOSE_DEBUG,
|
|
("%p %c%c%c %d/%d %d %d", c->fd, c->is_tls ? 'T' : 't',
|
|
c->is_udp ? 'U' : 'u', c->is_connecting ? 'C' : 'c', n, len,
|
|
MG_SOCK_ERRNO, *fail));
|
|
if (n > 0 && c->is_hexdumping) {
|
|
char *s = mg_hexdump(buf, len);
|
|
LOG(LL_INFO, ("\n-- %p %s %s %d\n%s--", c->fd, c->label, "<-", len, s));
|
|
free(s);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static int ll_write(struct mg_connection *c, const void *buf, int len,
|
|
int *fail) {
|
|
int n = c->is_tls ? mg_tls_send(c, buf, len, fail)
|
|
: mg_sock_send(c, buf, len, fail);
|
|
LOG(*fail ? LL_ERROR : LL_VERBOSE_DEBUG,
|
|
("%p %c%c%c %d/%d %d", c->fd, c->is_tls ? 'T' : 't',
|
|
c->is_udp ? 'U' : 'u', c->is_connecting ? 'C' : 'c', n, len,
|
|
MG_SOCK_ERRNO));
|
|
if (n > 0 && c->is_hexdumping) {
|
|
char *s = mg_hexdump(buf, len);
|
|
LOG(LL_INFO, ("\n-- %p %s %s %d\n%s--", c->fd, c->label, "->", len, s));
|
|
free(s);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int mg_send(struct mg_connection *c, const void *buf, size_t len) {
|
|
int fail, n = c->is_udp
|
|
? ll_write(c, buf, (SOCKET) len, &fail)
|
|
: (int) mg_iobuf_append(&c->send, buf, len, MG_IO_SIZE);
|
|
return n;
|
|
}
|
|
|
|
static void mg_set_non_blocking_mode(SOCKET fd) {
|
|
#ifdef _WIN32
|
|
unsigned long on = 1;
|
|
ioctlsocket(fd, FIONBIO, &on);
|
|
#elif MG_ARCH == MG_ARCH_FREERTOS
|
|
const BaseType_t off = 0;
|
|
setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off));
|
|
setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off));
|
|
#else
|
|
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
|
|
#endif
|
|
}
|
|
|
|
SOCKET mg_open_listener(const char *ip, uint16_t port, int is_udp) {
|
|
union usa usa;
|
|
int on = 1;
|
|
int proto = is_udp ? IPPROTO_UDP : IPPROTO_TCP;
|
|
int type = is_udp ? SOCK_DGRAM : SOCK_STREAM;
|
|
SOCKET fd;
|
|
|
|
memset(&usa, 0, sizeof(usa));
|
|
usa.sin.sin_family = AF_INET;
|
|
usa.sin.sin_port = mg_htons(port);
|
|
mg_aton(ip, (uint32_t *) &usa.sin.sin_addr);
|
|
if (!mg_casecmp(ip, "localhost")) {
|
|
*(uint32_t *) &usa.sin.sin_addr = mg_htonl(0x7f000001);
|
|
}
|
|
|
|
if ((fd = socket(AF_INET, type, proto)) != INVALID_SOCKET &&
|
|
#if !defined(_WIN32) || !defined(SO_EXCLUSIVEADDRUSE)
|
|
// 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.
|
|
!setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) &&
|
|
#endif
|
|
#if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE)
|
|
// "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE"
|
|
!setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *) &on,
|
|
sizeof(on)) &&
|
|
#endif
|
|
bind(fd, &usa.sa, sizeof(usa.sin)) == 0 &&
|
|
// NOTE(lsm): FreeRTOS uses backlog value as a connection limit
|
|
(type == SOCK_DGRAM || listen(fd, 128) == 0)) {
|
|
mg_set_non_blocking_mode(fd);
|
|
} else if (fd >= 0) {
|
|
LOG(LL_ERROR,
|
|
("Failed to listen on %s:%hu, errno %d", ip, port, MG_SOCK_ERRNO));
|
|
closesocket(fd);
|
|
fd = INVALID_SOCKET;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void read_conn(struct mg_connection *c,
|
|
int (*fn)(struct mg_connection *, void *, int, int *)) {
|
|
unsigned char *buf;
|
|
int rc, len, fail;
|
|
|
|
// NOTE(lsm): do only one iteration of reads, cause some systems
|
|
// (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data
|
|
if (c->recv.size - c->recv.len < MG_IO_SIZE &&
|
|
c->recv.size < MG_MAX_RECV_BUF_SIZE) {
|
|
mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE);
|
|
}
|
|
buf = c->recv.buf + c->recv.len;
|
|
len = (int) (c->recv.size - c->recv.len);
|
|
rc = fn(c, buf, len, &fail);
|
|
if (rc > 0) {
|
|
struct mg_str evd = mg_str_n((char *) buf, rc);
|
|
c->recv.len += rc;
|
|
mg_call(c, MG_EV_READ, &evd);
|
|
} else {
|
|
if (fail) c->is_closing = 1;
|
|
}
|
|
}
|
|
|
|
static int write_conn(struct mg_connection *c) {
|
|
int fail, rc = ll_write(c, c->send.buf, (SOCKET) c->send.len, &fail);
|
|
if (rc > 0) {
|
|
mg_iobuf_delete(&c->send, rc);
|
|
if (c->send.len == 0) mg_iobuf_resize(&c->send, 0);
|
|
mg_call(c, MG_EV_WRITE, &rc);
|
|
} else if (fail) {
|
|
c->is_closing = 1;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static void close_conn(struct mg_mgr *mgr, struct mg_connection *c) {
|
|
// Unlink this connection from the list
|
|
LIST_DELETE(struct mg_connection, &mgr->conns, c);
|
|
#if 0
|
|
struct mg_connection **head = &mgr->conns;
|
|
while (*head != c) head = &(*head)->next;
|
|
*head = c->next;
|
|
#endif
|
|
mg_resolve_cancel(mgr, c);
|
|
mg_call(c, MG_EV_CLOSE, NULL);
|
|
// while (c->callbacks != NULL) mg_fn_del(c, c->callbacks->fn);
|
|
LOG(LL_DEBUG, ("%p closed", c->fd));
|
|
if (FD(c) != INVALID_SOCKET) {
|
|
closesocket(FD(c));
|
|
#if MG_ARCH == MG_ARCH_FREERTOS
|
|
FreeRTOS_FD_CLR(c->fd, mgr->ss, eSELECT_ALL);
|
|
#endif
|
|
}
|
|
mg_tls_free(c);
|
|
free(c->recv.buf);
|
|
free(c->send.buf);
|
|
memset(c, 0, sizeof(*c));
|
|
free(c);
|
|
if (c == mgr->dnsc) mgr->dnsc = NULL;
|
|
}
|
|
|
|
static void setsockopts(struct mg_connection *c) {
|
|
#if MG_ARCH == MG_ARCH_FREERTOS
|
|
FreeRTOS_FD_SET(c->fd, c->mgr->ss, eSELECT_READ | eSELECT_EXCEPT);
|
|
#else
|
|
int on = 1, cnt = 3, intvl = 20;
|
|
#if !defined(SOL_TCP)
|
|
#define SOL_TCP IPPROTO_TCP
|
|
#endif
|
|
setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (void *) &on, sizeof(on));
|
|
#if defined(TCP_QUICKACK)
|
|
setsockopt(FD(c), SOL_TCP, TCP_QUICKACK, (void *) &on, sizeof(on));
|
|
#endif
|
|
setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (void *) &on, sizeof(on));
|
|
#if ESP32 || ESP8266 || defined(__linux__)
|
|
int idle = 60;
|
|
setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
|
|
#endif
|
|
#ifndef _WIN32
|
|
setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
|
|
setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl));
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void mg_connect_resolved(struct mg_connection *c) {
|
|
char buf[40];
|
|
int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM;
|
|
mg_call(c, MG_EV_RESOLVE, NULL);
|
|
mg_straddr(c, buf, sizeof(buf));
|
|
LOG(LL_DEBUG, ("%p resolved: %s", c->fd, buf));
|
|
if (type == SOCK_STREAM) {
|
|
union usa usa = tousa(&c->peer);
|
|
int rc = connect(FD(c), &usa.sa, sizeof(usa.sin));
|
|
int fail = rc < 0 && mg_sock_failed() ? MG_SOCK_ERRNO : 0;
|
|
if (fail) {
|
|
mg_error(c, "connect: %d", MG_SOCK_ERRNO);
|
|
} else {
|
|
setsockopts(c);
|
|
}
|
|
if (rc < 0) c->is_connecting = 1;
|
|
}
|
|
}
|
|
|
|
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;
|
|
int fd, type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM;
|
|
if ((fd = socket(AF_INET, type, 0)) == INVALID_SOCKET) {
|
|
LOG(LL_ERROR, ("socket(): %d", MG_SOCK_ERRNO));
|
|
} else if ((c = alloc_conn(mgr, 1, fd)) == NULL) {
|
|
LOG(LL_ERROR, ("%p OOM", c->fd));
|
|
} else {
|
|
struct mg_str host = mg_url_host(url);
|
|
LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
|
|
mg_set_non_blocking_mode(FD(c));
|
|
c->is_udp = type == SOCK_DGRAM;
|
|
c->peer.port = mg_htons(mg_url_port(url));
|
|
c->fn = fn;
|
|
c->fn_data = fn_data;
|
|
LOG(LL_DEBUG, ("%p -> %s", c->fd, url));
|
|
mg_resolve(mgr, c, &host, mgr->dnstimeout);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) {
|
|
struct mg_connection *c = NULL;
|
|
union usa usa;
|
|
socklen_t sa_len = sizeof(usa.sin);
|
|
SOCKET fd = accept(FD(lsn), &usa.sa, &sa_len);
|
|
if (fd == INVALID_SOCKET) {
|
|
LOG(LL_ERROR,
|
|
("%p accept(%d) failed, errno %d", lsn->fd, FD(lsn), MG_SOCK_ERRNO));
|
|
#if !defined(_WIN32)
|
|
} else if (fd >= FD_SETSIZE) {
|
|
LOG(LL_ERROR, ("%ld > %ld", (long) fd, (long) FD_SETSIZE));
|
|
closesocket(fd);
|
|
#endif
|
|
} else if ((c = alloc_conn(mgr, 0, fd)) == NULL) {
|
|
LOG(LL_ERROR, ("%p OOM", lsn->fd));
|
|
closesocket(fd);
|
|
} else {
|
|
char buf[40];
|
|
c->peer.port = usa.sin.sin_port;
|
|
memcpy(&c->peer.ip, &usa.sin.sin_addr, sizeof(c->peer.ip));
|
|
mg_straddr(c, buf, sizeof(buf));
|
|
LOG(LL_DEBUG, ("%p accepted %s", c->fd, buf));
|
|
mg_set_non_blocking_mode(FD(c));
|
|
setsockopts(c);
|
|
LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
|
|
c->is_accepted = 1;
|
|
c->is_hexdumping = lsn->is_hexdumping;
|
|
c->pfn = lsn->pfn;
|
|
c->pfn_data = lsn->pfn_data;
|
|
c->fn = lsn->fn;
|
|
c->fn_data = lsn->fn_data;
|
|
mg_call(c, MG_EV_ACCEPT, NULL);
|
|
}
|
|
}
|
|
|
|
#if MG_ENABLE_SOCKETPAIR
|
|
bool mg_socketpair(int *s1, int *s2) {
|
|
#if MG_ARCH == MG_ARCH_UNIX
|
|
int sp[2], ret = 0;
|
|
if (socketpair(AF_INET, SOCK_DGRAM, 0, sp) == 0) {
|
|
*s1 = sp[0], *s2 = sp[1], ret = 1;
|
|
}
|
|
return ret;
|
|
#else
|
|
union usa sa, sa2;
|
|
SOCKET sp[2] = {INVALID_SOCKET, INVALID_SOCKET};
|
|
socklen_t len = sizeof(sa.sin);
|
|
int ret = 0, res[2] = {-1, -1};
|
|
|
|
(void) memset(&sa, 0, sizeof(sa));
|
|
sa.sin.sin_family = AF_INET;
|
|
sa.sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
|
|
sa2 = sa;
|
|
|
|
if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET &&
|
|
(sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET &&
|
|
bind(sp[0], &sa.sa, len) == 0 && bind(sp[1], &sa2.sa, len) == 0 &&
|
|
getsockname(sp[0], &sa.sa, &len) == 0 &&
|
|
getsockname(sp[1], &sa2.sa, &len) == 0 &&
|
|
connect(sp[0], &sa2.sa, len) == 0 && connect(sp[1], &sa.sa, len) == 0) {
|
|
mg_set_non_blocking_mode(sp[1]);
|
|
*s1 = sp[0];
|
|
*s2 = sp[1];
|
|
ret = 1;
|
|
} else {
|
|
if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
|
|
if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
|
|
}
|
|
|
|
return ret;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
int is_udp = strncmp(url, "udp:", 4) == 0;
|
|
struct mg_str host = mg_url_host(url);
|
|
SOCKET fd = mg_open_listener(host.ptr, mg_url_port(url), is_udp);
|
|
if (fd == INVALID_SOCKET) {
|
|
} else if ((c = alloc_conn(mgr, 0, fd)) == NULL) {
|
|
LOG(LL_ERROR, ("OOM %s", url));
|
|
closesocket(fd);
|
|
} else {
|
|
c->fd = (void *) (long) fd;
|
|
c->is_listening = 1;
|
|
c->is_udp = is_udp;
|
|
setsockopts(c);
|
|
LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c);
|
|
c->fn = fn;
|
|
c->fn_data = fn_data;
|
|
LOG(LL_INFO, ("%p accepting on %s", c->fd, url));
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static void mg_iotest(struct mg_mgr *mgr, int ms) {
|
|
#if MG_ARCH == MG_ARCH_FREERTOS
|
|
struct mg_connection *c;
|
|
for (c = mgr->conns; c != NULL; c = c->next) {
|
|
FreeRTOS_FD_CLR(c->fd, mgr->ss, eSELECT_WRITE);
|
|
if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0))
|
|
FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE);
|
|
}
|
|
FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms));
|
|
for (c = mgr->conns; c != NULL; c = c->next) {
|
|
EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss);
|
|
c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1 : 0;
|
|
c->is_writable = bits & eSELECT_WRITE ? 1 : 0;
|
|
}
|
|
#else
|
|
struct timeval tv = {ms / 1000, (ms % 1000) * 1000};
|
|
struct mg_connection *c;
|
|
fd_set rset, wset;
|
|
SOCKET maxfd = 0;
|
|
int rc;
|
|
|
|
FD_ZERO(&rset);
|
|
FD_ZERO(&wset);
|
|
|
|
for (c = mgr->conns; c != NULL; c = c->next) {
|
|
// c->is_writable = 0;
|
|
// TLS might have stuff buffered, so dig everything
|
|
// c->is_readable = c->is_tls && c->is_readable ? 1 : 0;
|
|
if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue;
|
|
FD_SET(FD(c), &rset);
|
|
if (FD(c) > maxfd) maxfd = FD(c);
|
|
if (c->is_connecting || (c->send.len > 0 && c->is_tls_hs == 0))
|
|
FD_SET(FD(c), &wset);
|
|
// LOG(LL_INFO, ("%d %d", c->fd, FD_ISSET(c->fd, &wset)));
|
|
}
|
|
|
|
if ((rc = select(maxfd + 1, &rset, &wset, NULL, &tv)) < 0) {
|
|
LOG(LL_DEBUG, ("select: %d %d", rc, MG_SOCK_ERRNO));
|
|
FD_ZERO(&rset);
|
|
FD_ZERO(&wset);
|
|
}
|
|
|
|
for (c = mgr->conns; c != NULL; c = c->next) {
|
|
// TLS might have stuff buffered, so dig everything
|
|
c->is_readable = c->is_tls && c->is_readable
|
|
? 1
|
|
: FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &rset);
|
|
c->is_writable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &wset);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void connect_conn(struct mg_connection *c) {
|
|
int rc = 0;
|
|
socklen_t len = sizeof(rc);
|
|
if (getsockopt(FD(c), SOL_SOCKET, SO_ERROR, (char *) &rc, &len)) rc = 1;
|
|
if (rc == EAGAIN || rc == EWOULDBLOCK) rc = 0;
|
|
c->is_connecting = 0;
|
|
if (rc) {
|
|
char buf[40];
|
|
mg_error(c, "error connecting to %s", mg_straddr(c, buf, sizeof(buf)));
|
|
} else {
|
|
if (c->is_tls_hs && mg_tls_handshake(c)) {
|
|
c->is_tls_hs = 0;
|
|
mg_call(c, MG_EV_CONNECT, NULL);
|
|
}
|
|
if (c->is_tls == 0) mg_call(c, MG_EV_CONNECT, NULL);
|
|
}
|
|
}
|
|
|
|
void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
|
|
struct mg_connection *c, *tmp;
|
|
unsigned long now;
|
|
|
|
mg_iotest(mgr, ms);
|
|
now = mg_millis();
|
|
mg_timer_poll(now);
|
|
|
|
for (c = mgr->conns; c != NULL; c = tmp) {
|
|
tmp = c->next;
|
|
mg_call(c, MG_EV_POLL, &now);
|
|
LOG(LL_VERBOSE_DEBUG,
|
|
("%p %c%c %c%c%c%c%c", c->fd, 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) {
|
|
if (c->is_readable) accept_conn(mgr, c);
|
|
#if 0
|
|
} else if (c->is_accepted && c->is_tls_hs) {
|
|
if (mg_tls_handshake(c)) c->is_tls_hs = 0;
|
|
#endif
|
|
} 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)) {
|
|
c->is_tls_hs = 0;
|
|
mg_call(c, MG_EV_CONNECT, NULL);
|
|
}
|
|
} else {
|
|
if (c->is_readable) read_conn(c, ll_read);
|
|
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(mgr, c);
|
|
}
|
|
}
|
|
|
|
void mg_mgr_free(struct mg_mgr *mgr) {
|
|
struct mg_connection *c;
|
|
for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1;
|
|
mg_mgr_poll(mgr, 0);
|
|
#if MG_ARCH == MG_ARCH_FREERTOS
|
|
FreeRTOS_DeleteSocketSet(mgr->ss);
|
|
#endif
|
|
LOG(LL_INFO, ("All connections closed"));
|
|
}
|
|
|
|
void mg_mgr_init(struct mg_mgr *mgr) {
|
|
#ifdef _WIN32
|
|
WSADATA data;
|
|
WSAStartup(MAKEWORD(2, 2), &data);
|
|
#elif MG_ARCH == MG_ARCH_FREERTOS
|
|
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
|
|
memset(mgr, 0, sizeof(*mgr));
|
|
mgr->dnstimeout = 3000;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/str.c"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
|
|
struct mg_str mg_str(const char *s) {
|
|
struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
|
|
return str;
|
|
}
|
|
|
|
struct mg_str mg_str_n(const char *s, size_t n) {
|
|
struct mg_str str = {s, n};
|
|
return str;
|
|
}
|
|
|
|
int mg_lower(const char *s) {
|
|
return tolower(*(const unsigned char *) s);
|
|
}
|
|
|
|
int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
|
|
int diff = 0;
|
|
if (len > 0) do {
|
|
diff = mg_lower(s1++) - mg_lower(s2++);
|
|
} while (diff == 0 && s1[-1] != '\0' && --len > 0);
|
|
return diff;
|
|
}
|
|
|
|
int mg_casecmp(const char *s1, const char *s2) {
|
|
return mg_ncasecmp(s1, s2, (size_t) ~0);
|
|
}
|
|
|
|
int mg_vcmp(const struct mg_str *s1, const char *s2) {
|
|
size_t n2 = strlen(s2), n1 = s1->len;
|
|
int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
|
|
if (r == 0) return (int) (n1 - n2);
|
|
return r;
|
|
}
|
|
|
|
int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
|
|
size_t n2 = strlen(str2), n1 = str1->len;
|
|
int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
|
|
if (r == 0) return (int) (n1 - n2);
|
|
return r;
|
|
}
|
|
|
|
struct mg_str mg_strdup(const struct mg_str s) {
|
|
struct mg_str r = {NULL, 0};
|
|
if (s.len > 0 && s.ptr != NULL) {
|
|
char *sc = (char *) malloc(s.len + 1);
|
|
if (sc != NULL) {
|
|
memcpy(sc, s.ptr, s.len);
|
|
sc[s.len] = '\0';
|
|
r.ptr = sc;
|
|
r.len = s.len;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
|
|
size_t i = 0;
|
|
while (i < str1.len && i < str2.len) {
|
|
int c1 = str1.ptr[i];
|
|
int c2 = str2.ptr[i];
|
|
if (c1 < c2) return -1;
|
|
if (c1 > c2) return 1;
|
|
i++;
|
|
}
|
|
if (i < str1.len) return 1;
|
|
if (i < str2.len) return -1;
|
|
return 0;
|
|
}
|
|
|
|
const char *mg_strstr(const struct mg_str haystack,
|
|
const struct mg_str needle) {
|
|
size_t i;
|
|
if (needle.len > haystack.len) return NULL;
|
|
for (i = 0; i <= haystack.len - needle.len; i++) {
|
|
if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
|
|
return haystack.ptr + i;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct mg_str mg_strstrip(struct mg_str s) {
|
|
while (s.len > 0 && isspace((int) *s.ptr)) s.ptr++, s.len--;
|
|
while (s.len > 0 && isspace((int) *(s.ptr + s.len - 1))) s.len--;
|
|
return s;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/timer.c"
|
|
#endif
|
|
// Copyright (c) Cesanta Software Limited
|
|
// All rights reserved
|
|
|
|
|
|
|
|
|
|
struct mg_timer *g_timers;
|
|
|
|
void mg_timer_init(struct mg_timer *t, int ms, int flags, void (*fn)(void *),
|
|
void *arg) {
|
|
struct mg_timer tmp = {ms, flags, fn, arg, 0UL, g_timers};
|
|
*t = tmp;
|
|
g_timers = t;
|
|
if (flags & MG_TIMER_RUN_NOW) fn(arg);
|
|
}
|
|
|
|
void mg_timer_free(struct mg_timer *t) {
|
|
struct mg_timer **head = &g_timers;
|
|
while (*head && *head != t) head = &(*head)->next;
|
|
if (*head) *head = t->next;
|
|
}
|
|
|
|
void mg_timer_poll(unsigned long now_ms) {
|
|
// If time goes back (wrapped around), reset timers
|
|
struct mg_timer *t, *tmp;
|
|
static unsigned long oldnow; // Timestamp in a previous invocation
|
|
if (oldnow > now_ms) { // If it is wrapped, reset timers
|
|
for (t = g_timers; t != NULL; t = t->next) t->expire = 0;
|
|
}
|
|
oldnow = now_ms;
|
|
|
|
for (t = g_timers; t != NULL; t = tmp) {
|
|
tmp = t->next;
|
|
if (t->expire == 0) t->expire = now_ms + t->period_ms;
|
|
if (t->expire > now_ms) continue;
|
|
t->fn(t->arg);
|
|
// Try to tick timers with the given period as accurate as possible,
|
|
// even if this polling function is called with some random period.
|
|
t->expire = now_ms - t->expire > (unsigned long) t->period_ms
|
|
? now_ms + t->period_ms
|
|
: t->expire + t->period_ms;
|
|
if (!(t->flags & MG_TIMER_REPEAT)) mg_timer_free(t);
|
|
}
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/tls.c"
|
|
#endif
|
|
|
|
|
|
#if MG_ENABLE_MBEDTLS ///////////////////////////////////////// MBEDTLS
|
|
|
|
|
|
|
|
|
|
#include <mbedtls/debug.h>
|
|
#include <mbedtls/ssl.h>
|
|
|
|
// Different versions have those in different files, so declare here
|
|
#ifdef __cplusplus
|
|
#define EXTERN_C extern "C"
|
|
#else
|
|
#define EXTERN_C
|
|
#endif
|
|
|
|
EXTERN_C int mbedtls_net_recv(void *, unsigned char *, size_t);
|
|
EXTERN_C int mbedtls_net_send(void *, const unsigned char *, size_t);
|
|
|
|
struct mg_tls {
|
|
char *cafile; // CA certificate path
|
|
mbedtls_x509_crt ca; // Parsed CA certificate
|
|
mbedtls_x509_crt cert; // Parsed certificate
|
|
mbedtls_ssl_context ssl; // SSL/TLS context
|
|
mbedtls_ssl_config conf; // SSL-TLS config
|
|
mbedtls_pk_context pk; // Private key context
|
|
};
|
|
|
|
int mg_tls_handshake(struct mg_connection *c) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
int rc;
|
|
mbedtls_ssl_set_bio(&tls->ssl, &c->fd, mbedtls_net_send, mbedtls_net_recv, 0);
|
|
rc = mbedtls_ssl_handshake(&tls->ssl);
|
|
if (rc == 0) { // Success
|
|
LOG(LL_DEBUG, ("%p OK", c->fd));
|
|
} else if (rc == MBEDTLS_ERR_SSL_WANT_READ ||
|
|
rc == MBEDTLS_ERR_SSL_WANT_WRITE) { // Still pending
|
|
LOG(LL_VERBOSE_DEBUG, ("%p pending, %d%d %d (-%#x)", c->fd, c->is_connecting,
|
|
c->is_tls_hs, rc, -rc));
|
|
} else {
|
|
mg_error(c, "TLS handshake: -%#x", -rc); // Error
|
|
}
|
|
return rc == 0;
|
|
}
|
|
|
|
static int mbed_rng(void *ctx, unsigned char *buf, size_t len) {
|
|
mg_random(buf, len);
|
|
(void) ctx;
|
|
return 0;
|
|
}
|
|
|
|
static void debug_cb(void *c, int lev, const char *s, int n, const char *s2) {
|
|
n = strlen(s2) - 1;
|
|
LOG(LL_VERBOSE_DEBUG, ("%p %.*s", ((struct mg_connection *) c)->fd, n, s2));
|
|
(void) s;
|
|
(void) c;
|
|
(void) lev;
|
|
}
|
|
|
|
int mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
|
|
struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
|
|
int rc = 0;
|
|
LOG(LL_DEBUG, ("%p Setting TLS, CA: %s, cert: %s, key: %s", c->fd,
|
|
opts->ca == NULL ? "null" : opts->ca,
|
|
opts->cert == NULL ? "null" : opts->cert,
|
|
opts->certkey == NULL ? "null" : opts->certkey));
|
|
mbedtls_ssl_init(&tls->ssl);
|
|
mbedtls_ssl_config_init(&tls->conf);
|
|
mbedtls_ssl_conf_dbg(&tls->conf, debug_cb, c);
|
|
#if !defined(ESP_PLATFORM)
|
|
mbedtls_debug_set_threshold(5);
|
|
#endif
|
|
if ((rc = mbedtls_ssl_config_defaults(
|
|
&tls->conf,
|
|
c->is_client ? MBEDTLS_SSL_IS_CLIENT : MBEDTLS_SSL_IS_SERVER,
|
|
MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
|
|
mg_error(c, "tls defaults %#x", -rc);
|
|
goto fail;
|
|
}
|
|
mbedtls_ssl_conf_rng(&tls->conf, mbed_rng, c);
|
|
if (opts->ca == NULL || strcmp(opts->ca, "*") == 0) {
|
|
mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_NONE);
|
|
}
|
|
if (opts->ca != NULL && opts->ca[0] != '\0') {
|
|
#if defined(MBEDTLS_X509_CA_CHAIN_ON_DISK)
|
|
tls->cafile = strdup(opts->ca);
|
|
rc = mbedtls_ssl_conf_ca_chain_file(&tls->conf, tls->cafile, NULL);
|
|
if (rc != 0) {
|
|
mg_error(c, "parse on-disk chain(%s) err %#x", opts->ca, -rc);
|
|
goto fail;
|
|
}
|
|
#else
|
|
mbedtls_x509_crt_init(&tls->ca);
|
|
if ((rc = mbedtls_x509_crt_parse_file(&tls->ca, opts->ca)) != 0) {
|
|
mg_error(c, "parse(%s) err %#x", opts->ca, -rc);
|
|
goto fail;
|
|
}
|
|
mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->ca, NULL);
|
|
#endif
|
|
mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
|
|
}
|
|
if (opts->cert != NULL && opts->cert[0] != '\0') {
|
|
const char *key = opts->certkey;
|
|
if (key == NULL) key = opts->cert;
|
|
mbedtls_x509_crt_init(&tls->cert);
|
|
mbedtls_pk_init(&tls->pk);
|
|
if ((rc = mbedtls_x509_crt_parse_file(&tls->cert, opts->cert)) != 0) {
|
|
mg_error(c, "parse(%s) err %#x", opts->cert, -rc);
|
|
goto fail;
|
|
}
|
|
if ((rc = mbedtls_pk_parse_keyfile(&tls->pk, key, NULL)) != 0) {
|
|
mg_error(c, "tls key(%s) %#x", key, -rc);
|
|
goto fail;
|
|
}
|
|
rc = mbedtls_ssl_conf_own_cert(&tls->conf, &tls->cert, &tls->pk);
|
|
if (rc != 0) {
|
|
mg_error(c, "own cert %#x", -rc);
|
|
goto fail;
|
|
}
|
|
}
|
|
if ((rc = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) {
|
|
mg_error(c, "setup err %#x", -rc);
|
|
goto fail;
|
|
}
|
|
c->tls = tls;
|
|
c->is_tls = 1;
|
|
c->is_tls_hs = 1;
|
|
return 1;
|
|
fail:
|
|
c->is_closing = 1;
|
|
free(tls);
|
|
return 0;
|
|
}
|
|
|
|
int mg_tls_recv(struct mg_connection *c, void *buf, size_t len, int *fail) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
int n = mbedtls_ssl_read(&tls->ssl, (unsigned char *) buf, len);
|
|
*fail = (n == 0) || (n < 0 && n != MBEDTLS_ERR_SSL_WANT_READ);
|
|
return n;
|
|
}
|
|
|
|
int mg_tls_send(struct mg_connection *c, const void *buf, size_t len,
|
|
int *fail) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
int n = mbedtls_ssl_write(&tls->ssl, (unsigned char *) buf, len);
|
|
*fail = (n == 0) || (n < 0 && n != MBEDTLS_ERR_SSL_WANT_WRITE);
|
|
return n;
|
|
}
|
|
|
|
int mg_tls_free(struct mg_connection *c) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
if (tls == NULL) return 0;
|
|
free(tls->cafile);
|
|
mbedtls_x509_crt_free(&tls->ca);
|
|
mbedtls_x509_crt_free(&tls->cert);
|
|
mbedtls_ssl_free(&tls->ssl);
|
|
mbedtls_pk_free(&tls->pk);
|
|
mbedtls_ssl_config_free(&tls->conf);
|
|
free(tls);
|
|
return 1;
|
|
}
|
|
#elif MG_ENABLE_OPENSSL ///////////////////////////////////////// OPENSSL
|
|
|
|
#include <openssl/bio.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/ssl.h>
|
|
|
|
struct mg_tls {
|
|
SSL_CTX *ctx;
|
|
SSL *ssl;
|
|
};
|
|
|
|
static int mg_tls_err(struct mg_tls *tls, int res) {
|
|
int err = SSL_get_error(tls->ssl, res);
|
|
// We've just fetched the last error from the queue.
|
|
// Now we need to clear the error queue. If we do not, then the following
|
|
// can happen (actually reported):
|
|
// - A new connection is accept()-ed with cert error (e.g. self-signed cert)
|
|
// - Since all accept()-ed connections share listener's context,
|
|
// - *ALL* SSL accepted connection report read error on the next poll cycle.
|
|
// Thus a single errored connection can close all the rest, unrelated ones.
|
|
// Clearing the error keeps the shared SSL_CTX in an OK state.
|
|
ERR_clear_error();
|
|
if (err == SSL_ERROR_WANT_READ) return 0;
|
|
if (err == SSL_ERROR_WANT_WRITE) return 0;
|
|
return err;
|
|
}
|
|
|
|
int mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
|
|
struct mg_tls *tls = (struct mg_tls *) calloc(1, sizeof(*tls));
|
|
const char *id = "mongoose";
|
|
static unsigned char s_initialised = 0;
|
|
int rc;
|
|
if (!s_initialised) {
|
|
SSL_library_init();
|
|
s_initialised++;
|
|
}
|
|
LOG(LL_DEBUG, ("%p Setting TLS, CA: %s, cert: %s, key: %s", c->fd,
|
|
opts->ca == NULL ? "null" : opts->ca,
|
|
opts->cert == NULL ? "null" : opts->cert,
|
|
opts->certkey == NULL ? "null" : opts->certkey));
|
|
tls->ctx = c->is_client ? SSL_CTX_new(SSLv23_client_method())
|
|
: SSL_CTX_new(SSLv23_server_method());
|
|
if ((tls->ssl = SSL_new(tls->ctx)) == NULL) {
|
|
mg_error(c, "SSL_new");
|
|
goto fail;
|
|
}
|
|
SSL_set_session_id_context(tls->ssl, (const uint8_t *) id, strlen(id));
|
|
// Disable deprecated protocols
|
|
SSL_set_options(tls->ssl, SSL_OP_NO_SSLv2);
|
|
SSL_set_options(tls->ssl, SSL_OP_NO_SSLv3);
|
|
SSL_set_options(tls->ssl, SSL_OP_NO_TLSv1);
|
|
#ifdef MG_ENABLE_OPENSSL_NO_COMPRESSION
|
|
SSL_set_options(tls->ssl, SSL_OP_NO_COMPRESSION);
|
|
#endif
|
|
#ifdef MG_ENABLE_OPENSSL_CIPHER_SERVER_PREFERENCE
|
|
SSL_set_options(tls->ssl, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
|
#endif
|
|
|
|
if (opts->ca != NULL && opts->ca[0] != '\0') {
|
|
if ((rc = SSL_CTX_load_verify_locations(tls->ctx, opts->ca, NULL)) != 1) {
|
|
mg_error(c, "parse(%s): err %d", opts->ca, mg_tls_err(tls, rc));
|
|
goto fail;
|
|
}
|
|
SSL_set_verify(tls->ssl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
0);
|
|
}
|
|
if (opts->cert != NULL && opts->cert[0] != '\0') {
|
|
const char *key = opts->certkey;
|
|
if (key == NULL) key = opts->cert;
|
|
if ((rc = SSL_use_certificate_file(tls->ssl, opts->cert, 1)) != 1) {
|
|
mg_error(c, "Invalid SSL cert, err %d", mg_tls_err(tls, rc));
|
|
goto fail;
|
|
} else if ((rc = SSL_use_PrivateKey_file(tls->ssl, key, 1)) != 1) {
|
|
mg_error(c, "Invalid SSL key, err %d", mg_tls_err(tls, rc));
|
|
goto fail;
|
|
} else if ((rc = SSL_use_certificate_chain_file(tls->ssl, opts->cert)) !=
|
|
1) {
|
|
mg_error(c, "Invalid CA, err %d", mg_tls_err(tls, rc));
|
|
goto fail;
|
|
} else {
|
|
SSL_set_mode(tls->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
|
#if OPENSSL_VERSION_NUMBER > 0x10002000L
|
|
SSL_set_ecdh_auto(tls->ssl, 1);
|
|
#endif
|
|
}
|
|
}
|
|
if (opts->ciphers != NULL) SSL_set_cipher_list(tls->ssl, opts->ciphers);
|
|
if (opts->srvname != NULL) SSL_set_tlsext_host_name(tls->ssl, opts->srvname);
|
|
c->tls = tls;
|
|
c->is_tls = 1;
|
|
c->is_tls_hs = 1;
|
|
return 1;
|
|
fail:
|
|
c->is_closing = 1;
|
|
free(tls);
|
|
return 0;
|
|
}
|
|
|
|
int mg_tls_handshake(struct mg_connection *c) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
int rc;
|
|
// if (SSL_get_fd(tls->ssl) == -1)
|
|
SSL_set_fd(tls->ssl, (int) c->fd);
|
|
rc = c->is_client ? SSL_connect(tls->ssl) : SSL_accept(tls->ssl);
|
|
if (rc == 1) {
|
|
return 1;
|
|
} else {
|
|
ERR_print_errors_fp(stderr);
|
|
int code = mg_tls_err(tls, rc);
|
|
if (code != 0) mg_error(c, "tls hs: rc %d, err %d", rc, code);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int mg_tls_free(struct mg_connection *c) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
if (tls == NULL) return 0;
|
|
SSL_free(tls->ssl);
|
|
SSL_CTX_free(tls->ctx);
|
|
free(tls);
|
|
return 1;
|
|
}
|
|
|
|
int mg_tls_recv(struct mg_connection *c, void *buf, size_t len, int *fail) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
int n = SSL_read(tls->ssl, buf, len);
|
|
*fail = (n == 0) || (n < 0 && mg_tls_err(tls, n) != 0);
|
|
return n;
|
|
}
|
|
|
|
int mg_tls_send(struct mg_connection *c, const void *buf, size_t len,
|
|
int *fail) {
|
|
struct mg_tls *tls = (struct mg_tls *) c->tls;
|
|
int n = SSL_write(tls->ssl, buf, len);
|
|
*fail = (n == 0) || (n < 0 && mg_tls_err(tls, n) != 0);
|
|
return n;
|
|
}
|
|
|
|
#else ////////////////////////////////////////// NO TLS
|
|
|
|
int mg_tls_init(struct mg_connection *c, struct mg_tls_opts *opts) {
|
|
(void) opts;
|
|
mg_error(c, "TLS is not enabled");
|
|
return 0;
|
|
}
|
|
int mg_tls_handshake(struct mg_connection *c) {
|
|
return c != NULL;
|
|
}
|
|
int mg_tls_free(struct mg_connection *c) {
|
|
return c != NULL;
|
|
}
|
|
int mg_tls_recv(struct mg_connection *c, void *buf, size_t len, int *fail) {
|
|
*fail = 1;
|
|
return c == NULL || buf == NULL || len == 0 ? 0 : -1;
|
|
}
|
|
int mg_tls_send(struct mg_connection *c, const void *buf, size_t len,
|
|
int *fail) {
|
|
*fail = 1;
|
|
return c == NULL || buf == NULL || len == 0 ? 0 : -1;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/url.c"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
|
|
struct url {
|
|
int key, user, pass, host, port, uri, end;
|
|
};
|
|
|
|
int mg_url_is_ssl(const char *url) {
|
|
return strncmp(url, "wss:", 4) == 0 || strncmp(url, "https:", 6) == 0 ||
|
|
strncmp(url, "mqtts:", 6) == 0 || strncmp(url, "ssl:", 4) == 0 ||
|
|
strncmp(url, "tls:", 4) == 0;
|
|
}
|
|
|
|
static struct url urlparse(const char *url) {
|
|
int i;
|
|
struct url u;
|
|
memset(&u, 0, sizeof(u));
|
|
for (i = 0; url[i] != '\0'; i++) {
|
|
if (i > 0 && url[i - 1] == '/' && url[i] == '/') {
|
|
u.host = i + 1;
|
|
u.port = 0;
|
|
} else if (url[i] == ':') {
|
|
u.port = i + 1;
|
|
} else if (url[i] == '@') {
|
|
u.user = u.host;
|
|
u.pass = u.port;
|
|
u.host = i + 1;
|
|
u.port = 0;
|
|
} else if (u.host && !u.uri && url[i] == '/') {
|
|
u.uri = i;
|
|
}
|
|
}
|
|
u.end = i;
|
|
#if 0
|
|
printf("[%s] %d %d %d %d %d\n", url, u.user, u.pass, u.host, u.port, u.uri);
|
|
#endif
|
|
return u;
|
|
}
|
|
|
|
struct mg_str mg_url_host(const char *url) {
|
|
struct url u = urlparse(url);
|
|
int n =
|
|
u.port ? u.port - u.host - 1 : u.uri ? u.uri - u.host : u.end - u.host;
|
|
return mg_str_n(url + u.host, n);
|
|
}
|
|
|
|
const char *mg_url_uri(const char *url) {
|
|
struct url u = urlparse(url);
|
|
return u.uri ? url + u.uri : "/";
|
|
}
|
|
|
|
unsigned short mg_url_port(const char *url) {
|
|
struct url u = urlparse(url);
|
|
unsigned short port = 0;
|
|
if (memcmp(url, "http:", 5) == 0) port = 80;
|
|
if (memcmp(url, "wss:", 4) == 0 || memcmp(url, "https:", 6) == 0) port = 443;
|
|
if (memcmp(url, "mqtt:", 5) == 0) port = 1883;
|
|
if (memcmp(url, "mqtts:", 6) == 0) port = 8883;
|
|
if (u.port) port = atoi(url + u.port);
|
|
return port;
|
|
}
|
|
|
|
struct mg_str mg_url_user(const char *url) {
|
|
struct url u = urlparse(url);
|
|
struct mg_str s = mg_str("");
|
|
if (u.user && (u.pass || u.host)) {
|
|
int n = u.pass ? u.pass - u.user - 1 : u.host - u.user - 1;
|
|
s = mg_str_n(url + u.user, n);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
struct mg_str mg_url_pass(const char *url) {
|
|
struct url u = urlparse(url);
|
|
struct mg_str s = mg_str("");
|
|
if (u.pass && u.host) {
|
|
int n = u.host - u.pass - 1;
|
|
s = mg_str_n(url + u.pass, n);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/util.c"
|
|
#endif
|
|
|
|
|
|
size_t mg_file_size(const char *path) {
|
|
#if MG_ARCH == MG_ARCH_FREERTOS
|
|
struct FF_STAT st;
|
|
return ff_stat(path, &st) == 0 ? st.st_size : 0;
|
|
#else
|
|
struct stat st;
|
|
return stat(path, &st) == 0 ? st.st_size : 0;
|
|
#endif
|
|
}
|
|
|
|
char *mg_file_read(const char *path) {
|
|
FILE *fp;
|
|
char *data = NULL;
|
|
size_t size = mg_file_size(path);
|
|
if ((fp = fopen(path, "rb")) != NULL) {
|
|
data = (char *) malloc(size + 1);
|
|
if (data != NULL) {
|
|
if (fread(data, 1, size, fp) != size) {
|
|
free(data);
|
|
data = NULL;
|
|
} else {
|
|
data[size] = '\0';
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
int mg_file_write(const char *path, const char *fmt, ...) {
|
|
char tmp[100];
|
|
int written = 0;
|
|
FILE *fp;
|
|
snprintf(tmp, sizeof(tmp), "%s.%d", path, rand());
|
|
fp = fopen(tmp, "wb");
|
|
if (fp != NULL) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
written = vfprintf(fp, fmt, ap);
|
|
va_end(ap);
|
|
fclose(fp);
|
|
remove(path);
|
|
rename(tmp, path);
|
|
}
|
|
return written;
|
|
}
|
|
|
|
void mg_random(void *buf, size_t len) {
|
|
FILE *fp = fopen("/dev/urandom", "rb");
|
|
size_t i, n = 0;
|
|
if (fp != NULL) n = fread(buf, 1, len, fp);
|
|
if (fp == NULL || n <= 0) {
|
|
for (i = 0; i < len; i++) ((unsigned char *) buf)[i] = rand() % 0xff;
|
|
}
|
|
if (fp != NULL) fclose(fp);
|
|
}
|
|
|
|
bool mg_globmatch(const char *s1, int n1, const char *s2, int n2) {
|
|
int i = 0, j = 0, ni = 0, nj = 0;
|
|
while (i < n1 || j < n2) {
|
|
if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) {
|
|
i++, j++;
|
|
} else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
|
|
ni = i, nj = j + 1, i++;
|
|
} else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
|
|
i = ni, j = nj;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int mg_nextcommaentry(const char *s, int slen, int ofs, int *koff,
|
|
int *klen, int *voff, int *vlen) {
|
|
int kvlen, kl;
|
|
for (kvlen = 0; ofs + kvlen < slen && s[ofs + kvlen] != ',';) kvlen++;
|
|
for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++;
|
|
if (koff != NULL) *koff = ofs;
|
|
if (klen != NULL) *klen = kl;
|
|
if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0;
|
|
if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0;
|
|
return ofs >= slen ? slen : ofs + kvlen + 1;
|
|
}
|
|
|
|
bool mg_next_comma_entry(struct mg_str *s, struct mg_str *k, struct mg_str *v) {
|
|
int koff, klen, voff, vlen;
|
|
int off = mg_nextcommaentry(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen);
|
|
if (k != NULL) *k = mg_str_n(s->ptr + koff, klen);
|
|
if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen);
|
|
*s = mg_str_n(s->ptr + off, s->len - off);
|
|
return off > 0;
|
|
}
|
|
|
|
uint32_t mg_ntohl(uint32_t net) {
|
|
uint8_t data[4] = {0, 0, 0, 0};
|
|
memcpy(&data, &net, sizeof(data));
|
|
return ((uint32_t) data[3] << 0) | ((uint32_t) data[2] << 8) |
|
|
((uint32_t) data[1] << 16) | ((uint32_t) data[0] << 24);
|
|
}
|
|
|
|
uint16_t mg_ntohs(uint16_t net) {
|
|
uint8_t data[2] = {0, 0};
|
|
memcpy(&data, &net, sizeof(data));
|
|
return ((uint16_t) data[1] << 0) | ((uint32_t) data[0] << 8);
|
|
}
|
|
|
|
char *mg_hexdump(const void *buf, int len) {
|
|
const unsigned char *p = (const unsigned char *) buf;
|
|
int i, idx, n = 0, ofs = 0, dlen = len * 5 + 100;
|
|
char ascii[17] = "", *dst = (char *) malloc(dlen);
|
|
if (dst == NULL) return dst;
|
|
for (i = 0; i < len; i++) {
|
|
idx = i % 16;
|
|
if (idx == 0) {
|
|
if (i > 0 && dlen > n) n += snprintf(dst + n, dlen - n, " %s\n", ascii);
|
|
if (dlen > n) n += snprintf(dst + n, dlen - n, "%04x ", i + ofs);
|
|
}
|
|
if (dlen < n) break;
|
|
n += snprintf(dst + n, dlen - n, " %02x", p[i]);
|
|
ascii[idx] = p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i];
|
|
ascii[idx + 1] = '\0';
|
|
}
|
|
while (i++ % 16) {
|
|
if (n < dlen) n += snprintf(dst + n, dlen - n, "%s", " ");
|
|
}
|
|
if (n < dlen) n += snprintf(dst + n, dlen - n, " %s\n", ascii);
|
|
if (n > dlen - 1) n = dlen - 1;
|
|
dst[n] = '\0';
|
|
return dst;
|
|
}
|
|
|
|
char *mg_hex(const void *buf, int len, char *to) {
|
|
const unsigned char *p = (const unsigned char *) buf;
|
|
static const char *hex = "0123456789abcdef";
|
|
int i = 0;
|
|
for (; len--; p++) {
|
|
to[i++] = hex[p[0] >> 4];
|
|
to[i++] = hex[p[0] & 0x0f];
|
|
}
|
|
to[i] = '\0';
|
|
return to;
|
|
}
|
|
|
|
unsigned long mg_unhexn(const char *s, int len) {
|
|
unsigned long i = 0, v = 0;
|
|
for (i = 0; i < (unsigned long) len; i++) {
|
|
int c = s[i];
|
|
if (i > 0) v <<= 4;
|
|
v |= (c >= '0' && c <= '9') ? c - '0'
|
|
: (c >= 'A' && c <= 'F') ? c - '7' : c - 'W';
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void mg_unhex(const char *buf, int len, unsigned char *to) {
|
|
int i;
|
|
for (i = 0; i < len; i += 2) {
|
|
to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2);
|
|
}
|
|
}
|
|
|
|
int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap) {
|
|
va_list ap_copy;
|
|
int len;
|
|
|
|
va_copy(ap_copy, ap);
|
|
len = vsnprintf(*buf, size, fmt, ap_copy);
|
|
va_end(ap_copy);
|
|
|
|
if (len < 0) {
|
|
// eCos and Windows are not standard-compliant and return -1 when
|
|
// the buffer is too small. Keep allocating larger buffers until we
|
|
// succeed or out of memory.
|
|
// LCOV_EXCL_START
|
|
*buf = NULL;
|
|
while (len < 0) {
|
|
free(*buf);
|
|
if (size == 0) size = 5;
|
|
size *= 2;
|
|
if ((*buf = (char *) malloc(size)) == NULL) {
|
|
len = -1;
|
|
break;
|
|
}
|
|
va_copy(ap_copy, ap);
|
|
len = vsnprintf(*buf, size - 1, fmt, ap_copy);
|
|
va_end(ap_copy);
|
|
}
|
|
// Microsoft version of vsnprintf() is not always null-terminated, so put
|
|
// the terminator manually
|
|
(*buf)[len] = 0;
|
|
// LCOV_EXCL_STOP
|
|
} else if (len >= (int) size) {
|
|
/// Standard-compliant code path. Allocate a buffer that is large enough
|
|
if ((*buf = (char *) malloc(len + 1)) == NULL) {
|
|
len = -1; // LCOV_EXCL_LINE
|
|
} else { // LCOV_EXCL_LINE
|
|
va_copy(ap_copy, ap);
|
|
len = vsnprintf(*buf, len + 1, fmt, ap_copy);
|
|
va_end(ap_copy);
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
int mg_asprintf(char **buf, size_t size, const char *fmt, ...) {
|
|
int ret;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
ret = mg_vasprintf(buf, size, fmt, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
int64_t mg_to64(const char *s) {
|
|
int64_t result = 0, neg = 1;
|
|
while (*s && isspace((unsigned char) *s)) s++;
|
|
if (*s == '-') neg = -1, s++;
|
|
while (isdigit((unsigned char) *s)) {
|
|
result *= 10;
|
|
result += (*s - '0');
|
|
s++;
|
|
}
|
|
return result * neg;
|
|
}
|
|
|
|
bool mg_aton(const char *s, uint32_t *ip) {
|
|
uint8_t data[4] = {0, 0, 0, 0}, ok = 0;
|
|
if (s != NULL && sscanf(s, "%hhu.%hhu.%hhu.%hhu", &data[0], &data[1],
|
|
&data[2], &data[3]) == 4) {
|
|
memcpy(ip, data, sizeof(data));
|
|
ok = 1;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
char *mg_ntoa(uint32_t ipaddr, char *buf, size_t len) {
|
|
uint8_t p[4];
|
|
memcpy(p, &ipaddr, sizeof(p));
|
|
snprintf(buf, len, "%hhu.%hhu.%hhu.%hhu", p[0], p[1], p[2], p[3]);
|
|
return buf;
|
|
}
|
|
|
|
double mg_time(void) {
|
|
#if MG_ARCH == MG_ARCH_WIN32
|
|
SYSTEMTIME sysnow;
|
|
FILETIME ftime;
|
|
GetLocalTime(&sysnow);
|
|
SystemTimeToFileTime(&sysnow, &ftime);
|
|
/*
|
|
* 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64
|
|
* This should not cause a problems in this (21th) century
|
|
* 2. Windows FILETIME is a number of 100-nanosecond intervals since January
|
|
* 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC,
|
|
* thus, we need to convert to seconds and adjust amount (subtract 11644473600
|
|
* seconds)
|
|
*/
|
|
return (double) (((int64_t) ftime.dwLowDateTime +
|
|
((int64_t) ftime.dwHighDateTime << 32)) /
|
|
10000000.0) -
|
|
11644473600;
|
|
#else
|
|
struct timeval tv;
|
|
if (gettimeofday(&tv, NULL /* tz */) != 0) return 0;
|
|
return (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0);
|
|
#endif /* _WIN32 */
|
|
}
|
|
|
|
void mg_usleep(unsigned long usecs) {
|
|
#if MG_ARCH == MG_ARCH_WIN32
|
|
Sleep(usecs / 1000);
|
|
#elif MG_ARCH == MG_ARCH_ESP8266
|
|
ets_delay_us(usecs);
|
|
#else
|
|
usleep(usecs);
|
|
#endif
|
|
}
|
|
|
|
unsigned long mg_millis(void) {
|
|
#if MG_ARCH == MG_ARCH_WIN32
|
|
return GetTickCount();
|
|
#elif MG_ARCH == MG_ARCH_ESP32
|
|
return esp_timer_get_time() / 1000;
|
|
#elif MG_ARCH == MG_ARCH_ESP8266
|
|
return system_get_time() / 1000;
|
|
#elif MG_ARCH == MG_ARCH_FREERTOS
|
|
return xTaskGetTickCount() * portTICK_PERIOD_MS;
|
|
#else
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
#endif
|
|
}
|
|
|
|
#ifdef MG_ENABLE_LINES
|
|
#line 1 "src/ws.c"
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct ws_msg {
|
|
uint8_t flags;
|
|
size_t header_len;
|
|
size_t data_len;
|
|
};
|
|
|
|
static void ws_handshake(struct mg_connection *c, const char *key,
|
|
size_t key_len) {
|
|
const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
unsigned char sha[20], b64_sha[30];
|
|
// mem[256], *buf = mem;
|
|
// int len = 0;
|
|
mg_sha1_ctx sha_ctx;
|
|
mg_sha1_init(&sha_ctx);
|
|
mg_sha1_update(&sha_ctx, (unsigned char *) key, key_len);
|
|
mg_sha1_update(&sha_ctx, (unsigned char *) magic, 36);
|
|
mg_sha1_final(sha, &sha_ctx);
|
|
mg_base64_encode(sha, sizeof(sha), (char *) b64_sha);
|
|
mg_printf(c,
|
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
|
"Upgrade: websocket\r\n"
|
|
"Connection: Upgrade\r\n"
|
|
"Sec-WebSocket-Accept: %s\r\n\r\n",
|
|
b64_sha);
|
|
// mg_send(c, buf, len);
|
|
// if (buf != mem) free(buf);
|
|
}
|
|
|
|
static size_t ws_process(uint8_t *buf, size_t len, struct ws_msg *msg) {
|
|
size_t i, n = 0, mask_len = 0;
|
|
memset(msg, 0, sizeof(*msg));
|
|
if (len >= 2) {
|
|
n = buf[1] & 0x7f;
|
|
mask_len = buf[1] & WEBSOCKET_FLAGS_MASK_FIN ? 4 : 0;
|
|
msg->flags = *(unsigned char *) buf;
|
|
if (n < 126 && len >= mask_len) {
|
|
msg->data_len = n;
|
|
msg->header_len = 2 + mask_len;
|
|
} else if (n == 126 && len >= 4 + mask_len) {
|
|
msg->header_len = 4 + mask_len;
|
|
msg->data_len = mg_ntohs(*(uint16_t *) &buf[2]);
|
|
} else if (len >= 10 + mask_len) {
|
|
msg->header_len = 10 + mask_len;
|
|
msg->data_len =
|
|
(int) (((uint64_t) mg_ntohl(*(uint32_t *) &buf[2])) << 32) +
|
|
mg_ntohl(*(uint32_t *) &buf[6]);
|
|
}
|
|
}
|
|
if (msg->header_len + msg->data_len > len) return 0;
|
|
if (mask_len > 0) {
|
|
uint8_t *p = buf + msg->header_len, *m = p - mask_len;
|
|
for (i = 0; i < msg->data_len; i++) p[i] ^= m[i & 3];
|
|
}
|
|
return msg->header_len + msg->data_len;
|
|
}
|
|
|
|
size_t mg_ws_send(struct mg_connection *c, const char *buf, size_t len,
|
|
int op) {
|
|
unsigned char header[10], mask[4];
|
|
size_t i, header_len = 0;
|
|
header[0] = (uint8_t)(op | WEBSOCKET_FLAGS_MASK_FIN);
|
|
if (len < 126) {
|
|
header[1] = (unsigned char) len;
|
|
header_len = 2;
|
|
} else if (len < 65535) {
|
|
uint16_t tmp = mg_htons((uint16_t) len);
|
|
header[1] = 126;
|
|
memcpy(&header[2], &tmp, sizeof(tmp));
|
|
header_len = 4;
|
|
} else {
|
|
uint32_t tmp;
|
|
header[1] = 127;
|
|
tmp = mg_htonl((uint32_t)((uint64_t) len >> 32));
|
|
memcpy(&header[2], &tmp, sizeof(tmp));
|
|
tmp = mg_htonl((uint32_t)(len & 0xffffffff));
|
|
memcpy(&header[6], &tmp, sizeof(tmp));
|
|
header_len = 10;
|
|
}
|
|
if (c->is_client) header[1] |= 1 << 7; // Set masking flag
|
|
mg_send(c, header, header_len);
|
|
if (c->is_client) {
|
|
mg_random(mask, sizeof(mask));
|
|
mg_send(c, mask, sizeof(mask));
|
|
header_len += sizeof(mask);
|
|
}
|
|
LOG(LL_VERBOSE_DEBUG, ("WS out: %d [%.*s]", (int) len, (int) len, buf));
|
|
mg_send(c, buf, len);
|
|
if (c->is_client) {
|
|
uint8_t *p = c->send.buf + c->send.len - len;
|
|
for (i = 0; i < len; i++) p[i] ^= mask[i & 3];
|
|
}
|
|
return header_len + len;
|
|
}
|
|
|
|
static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data,
|
|
void *fn_data) {
|
|
struct ws_msg msg;
|
|
|
|
if (ev == MG_EV_READ) {
|
|
if (!c->is_websocket && c->is_client) {
|
|
int n = mg_http_get_request_len(c->recv.buf, c->recv.len);
|
|
if (n < 0) {
|
|
c->is_closing = 1; // Some just, not an HTTP request
|
|
} else if (n > 0) {
|
|
if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
|
|
LOG(LL_ERROR, ("%p WS handshake error: %.*s", c->fd, 15, c->recv.buf));
|
|
c->is_closing = 1;
|
|
} else {
|
|
c->is_websocket = 1;
|
|
mg_call(c, MG_EV_WS_OPEN, NULL);
|
|
}
|
|
mg_iobuf_delete(&c->recv, n);
|
|
} else {
|
|
return; // A request is not yet received
|
|
}
|
|
}
|
|
|
|
while (ws_process(c->recv.buf, c->recv.len, &msg) > 0) {
|
|
char *s = (char *) c->recv.buf + msg.header_len;
|
|
switch (msg.flags & WEBSOCKET_FLAGS_MASK_OP) {
|
|
case WEBSOCKET_OP_PING:
|
|
LOG(LL_DEBUG, ("%s", "WS PONG"));
|
|
mg_ws_send(c, s, msg.data_len, WEBSOCKET_OP_PONG);
|
|
break;
|
|
case WEBSOCKET_OP_PONG:
|
|
// Ignore
|
|
break;
|
|
case WEBSOCKET_OP_CLOSE: {
|
|
struct mg_ws_message evd = {{s, msg.data_len}, msg.flags};
|
|
LOG(LL_ERROR, ("%p Got WS CLOSE", c->fd));
|
|
mg_call(c, MG_EV_WS_MSG, &evd);
|
|
c->is_closing = 1;
|
|
return;
|
|
}
|
|
default: {
|
|
struct mg_ws_message evd = {{s, msg.data_len}, msg.flags};
|
|
mg_call(c, MG_EV_WS_MSG, &evd);
|
|
break;
|
|
}
|
|
}
|
|
mg_iobuf_delete(&c->recv, msg.header_len + msg.data_len);
|
|
}
|
|
}
|
|
(void) fn_data;
|
|
(void) ev_data;
|
|
}
|
|
|
|
struct mg_connection *mg_ws_connect(struct mg_mgr *mgr, const char *url,
|
|
mg_event_handler_t fn, void *fn_data,
|
|
const char *fmt, ...) {
|
|
struct mg_connection *c = mg_connect(mgr, url, fn, fn_data);
|
|
if (c != NULL) {
|
|
char nonce[16], key[30], mem1[128], mem2[256], *buf1 = mem1, *buf2 = mem2;
|
|
struct mg_str host = mg_url_host(url);
|
|
int n1 = 0, n2 = 0;
|
|
if (fmt != NULL) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
n1 = mg_vasprintf(&buf1, sizeof(mem1), fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
// Send handshake request
|
|
// mg_url_host(url, host, sizeof(host));
|
|
mg_random(nonce, sizeof(nonce));
|
|
mg_base64_encode((unsigned char *) nonce, sizeof(nonce), key);
|
|
// LOG(LL_DEBUG, "%p [%s]", uri, uri == NULL ? "???" : uri);
|
|
n2 = mg_asprintf(&buf2, sizeof(mem2),
|
|
"GET %s HTTP/1.1\r\n"
|
|
"Upgrade: websocket\r\n"
|
|
"Host: %.*s\r\n"
|
|
"Connection: Upgrade\r\n"
|
|
"%.*s"
|
|
"Sec-WebSocket-Version: 13\r\n"
|
|
"Sec-WebSocket-Key: %s\r\n"
|
|
"\r\n",
|
|
mg_url_uri(url), (int) host.len, host.ptr, n1, buf1, key);
|
|
mg_send(c, buf2, n2);
|
|
if (buf1 != mem1) free(buf1);
|
|
if (buf2 != mem2) free(buf2);
|
|
c->pfn = mg_ws_cb;
|
|
c->fn_data = fn_data;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void mg_ws_upgrade(struct mg_connection *c, struct mg_http_message *hm) {
|
|
struct mg_str *wskey = mg_http_get_header(hm, "Sec-WebSocket-Key");
|
|
c->pfn = mg_ws_cb;
|
|
if (wskey != NULL) ws_handshake(c, wskey->ptr, wskey->len);
|
|
c->is_websocket = 1;
|
|
}
|