// Copyright (c) 2004-2013 Sergey Lyubka // Copyright (c) 2013-2021 Cesanta Software Limited // All rights reserved // // This software is dual-licensed: you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. For the terms of this // license, see . // // 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 . #include "mongoose.h" #ifdef MG_ENABLE_LINES #line 1 "src/private.h" #endif void mg_connect_resolved(struct mg_connection *); #ifdef MG_ENABLE_LINES #line 1 "src/event.c" #endif void mg_call(struct mg_connection *c, int ev, void *ev_data) { if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data); if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data); } void mg_error(struct mg_connection *c, const char *fmt, ...) { char mem[256], *buf = mem; va_list ap; va_start(ap, fmt); mg_vasprintf(&buf, sizeof(mem), fmt, ap); va_end(ap); LOG(LL_ERROR, ("%lu %s", c->id, buf)); mg_call(c, MG_EV_ERROR, buf); if (buf != mem) free(buf); c->is_closing = 1; } #ifdef MG_ENABLE_LINES #line 1 "src/fs_packed.c" #endif struct packed_file { const char *data; size_t size; size_t pos; }; const char *mg_unpack(const char *path, size_t *size, time_t *mtime); const char *mg_unlist(size_t no); #if MG_ENABLE_PACKED_FS #else const char *mg_unpack(const char *path, size_t *size, time_t *mtime) { (void) path, (void) size, (void) mtime; return NULL; } const char *mg_unlist(size_t no) { (void) no; return NULL; } #endif static int is_dir_prefix(const char *prefix, size_t n, const char *path) { return n < strlen(path) && memcmp(prefix, path, n) == 0 && path[n] == '/'; //(n == 0 || path[n] == MG_DIRSEP); } static int packed_stat(const char *path, size_t *size, time_t *mtime) { const char *p; size_t i, n = strlen(path); if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file // Scan all files. If `path` is a dir prefix for any of them, it's a dir for (i = 0; (p = mg_unlist(i)) != NULL; i++) { if (is_dir_prefix(path, n, p)) return MG_FS_DIR; } return 0; } static struct mg_fd *packed_open(const char *path, int flags) { size_t size = 0; const char *data = mg_unpack(path, &size, NULL); struct packed_file *fp = NULL; struct mg_fd *fd = NULL; if (data == NULL) return NULL; if (flags & MG_FS_WRITE) return NULL; fp = (struct packed_file *) calloc(1, sizeof(*fp)); fd = (struct mg_fd *) calloc(1, sizeof(*fd)); fp->size = size; fp->data = data; fd->fd = fp; fd->fs = &mg_fs_packed; return fd; } static void packed_close(struct mg_fd *fd) { if (fd) free(fd->fd), free(fd); } static size_t packed_read(void *fd, void *buf, size_t len) { struct packed_file *fp = (struct packed_file *) fd; if (fp->pos + len > fp->size) len = fp->size - fp->pos; memcpy(buf, &fp->data[fp->pos], len); fp->pos += len; return len; } static size_t packed_write(void *fd, const void *buf, size_t len) { (void) fd, (void) buf, (void) len; return 0; } static size_t packed_seek(void *fd, size_t offset) { struct packed_file *fp = (struct packed_file *) fd; fp->pos = offset; if (fp->pos > fp->size) fp->pos = fp->size; return fp->pos; } struct mg_fs mg_fs_packed = {packed_stat, packed_open, packed_close, packed_read, packed_write, packed_seek}; #ifdef MG_ENABLE_LINES #line 1 "src/fs_posix.c" #endif #if defined(FOPEN_MAX) static int p_stat(const char *path, size_t *size, time_t *mtime) { #ifdef _WIN32 struct _stati64 st; wchar_t tmp[PATH_MAX]; MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); if (_wstati64(tmp, &st) != 0) return 0; #else struct stat st; if (stat(path, &st) != 0) return 0; #endif if (size) *size = (size_t) st.st_size; if (mtime) *mtime = st.st_mtime; return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); } #ifdef _WIN32 struct dirent { char d_name[MAX_PATH]; }; typedef struct win32_dir { HANDLE handle; WIN32_FIND_DATAW info; struct dirent result; } DIR; int gettimeofday(struct timeval *tv, void *tz) { FILETIME ft; unsigned __int64 tmpres = 0; if (tv != NULL) { GetSystemTimeAsFileTime(&ft); tmpres |= ft.dwHighDateTime; tmpres <<= 32; tmpres |= ft.dwLowDateTime; tmpres /= 10; // convert into microseconds tmpres -= (int64_t) 11644473600000000; tv->tv_sec = (long) (tmpres / 1000000UL); tv->tv_usec = (long) (tmpres % 1000000UL); } (void) tz; return 0; } static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) { int ret; char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; strncpy(buf, path, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; // Trim trailing slashes. Leave backslash for paths like "X:\" p = buf + strlen(buf) - 1; while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); // Convert back to Unicode. If doubly-converted string does not match the // original, something is fishy, reject. WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), NULL, NULL); if (strcmp(buf, buf2) != 0) { wbuf[0] = L'\0'; ret = 0; } return ret; } DIR *opendir(const char *name) { DIR *d = NULL; wchar_t wpath[MAX_PATH]; DWORD attrs; if (name == NULL) { SetLastError(ERROR_BAD_ARGUMENTS); } else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); } else { to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0])); attrs = GetFileAttributesW(wpath); if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { (void) wcscat(wpath, L"\\*"); d->handle = FindFirstFileW(wpath, &d->info); d->result.d_name[0] = '\0'; } else { free(d); d = NULL; } } return d; } int closedir(DIR *d) { int result = 0; if (d != NULL) { if (d->handle != INVALID_HANDLE_VALUE) result = FindClose(d->handle) ? 0 : -1; free(d); } else { result = -1; SetLastError(ERROR_BAD_ARGUMENTS); } return result; } struct dirent *readdir(DIR *d) { struct dirent *result = NULL; if (d != NULL) { memset(&d->result, 0, sizeof(d->result)); if (d->handle != INVALID_HANDLE_VALUE) { result = &d->result; WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name, sizeof(result->d_name), NULL, NULL); if (!FindNextFileW(d->handle, &d->info)) { FindClose(d->handle); d->handle = INVALID_HANDLE_VALUE; } } else { SetLastError(ERROR_FILE_NOT_FOUND); } } else { SetLastError(ERROR_BAD_ARGUMENTS); } return result; } #endif static struct mg_fd *p_open(const char *path, int flags) { const char *mode = flags == (MG_FS_READ | MG_FS_WRITE) ? "r+b" : flags & MG_FS_READ ? "rb" : flags & MG_FS_WRITE ? "wb" : ""; void *fp = NULL; struct mg_fd *fd = NULL; #ifdef _WIN32 wchar_t b1[PATH_MAX], b2[10]; MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); fp = (void *) _wfopen(b1, b2); #else fp = (void *) fopen(path, mode); #endif if (fp == NULL) return NULL; fd = (struct mg_fd *) calloc(1, sizeof(*fd)); fd->fd = fp; fd->fs = &mg_fs_posix; return fd; } static void p_close(struct mg_fd *fd) { if (fd != NULL) fclose((FILE *) fd->fd), free(fd); } static size_t p_read(void *fp, void *buf, size_t len) { return fread(buf, 1, len, (FILE *) fp); } static size_t p_write(void *fp, const void *buf, size_t len) { return fwrite(buf, 1, len, (FILE *) fp); } static size_t p_seek(void *fp, size_t offset) { #if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ _XOPEN_SOURCE >= 600 fseeko((FILE *) fp, (off_t) offset, SEEK_SET); #else fseek((FILE *) fp, (long) offset, SEEK_SET); #endif return (size_t) ftell((FILE *) fp); } #else static char *p_realpath(const char *path, char *resolved_path) { (void) path, (void) resolved_path; return NULL; } static int p_stat(const char *path, size_t *size, time_t *mtime) { (void) path, (void) size, (void) mtime; return 0; } static struct mg_fd *p_open(const char *path, int flags) { (void) path, (void) flags; return NULL; } static void p_close(struct mg_fd *fd) { (void) fd; } static size_t p_read(void *fd, void *buf, size_t len) { (void) fd, (void) buf, (void) len; return 0; } static size_t p_write(void *fd, const void *buf, size_t len) { (void) fd, (void) buf, (void) len; return 0; } static size_t p_seek(void *fd, size_t offset) { (void) fd, (void) offset; return (size_t) ~0; } #endif struct mg_fs mg_fs_posix = {p_stat, p_open, p_close, p_read, p_write, p_seek}; #ifdef MG_ENABLE_LINES #line 1 "src/http.c" #endif // Multipart POST example: // --xyz // Content-Disposition: form-data; name="val" // // abcdef // --xyz // Content-Disposition: form-data; name="foo"; filename="a.txt" // Content-Type: text/plain // // hello world // // --xyz-- size_t mg_http_next_multipart(struct mg_str body, size_t ofs, struct mg_http_part *part) { struct mg_str cd = mg_str_n("Content-Disposition", 19); const char *s = body.ptr; size_t b = ofs, h1, h2, b1, b2, max = body.len; // Init part params if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0); // Skip boundary while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++; if (b <= ofs || b + 2 >= max) return 0; // LOG(LL_INFO, ("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s)); // Skip headers h1 = h2 = b + 2; for (;;) { while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++; if (h2 == h1) break; if (h2 + 2 >= max) return 0; // LOG(LL_INFO, ("Header: [%.*s]", (int) (h2 - h1), &s[h1])); if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' && mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) { struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2)); part->name = mg_http_get_header_var(v, mg_str_n("name", 4)); part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8)); } h1 = h2 = h2 + 2; } b1 = b2 = h2 + 2; while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' && memcmp(&s[b2 + 2], s, b - ofs) == 0)) b2++; if (b2 + 2 >= max) return 0; if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1); // LOG(LL_INFO, ("Body: [%.*s]", (int) (b2 - b1), &s[b1])); return b2 + 2; } int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst, size_t dst_len) { const char *p, *e, *s; size_t name_len; int len; if (dst == NULL || dst_len == 0) { len = -2; // Bad destination } else if (buf->ptr == NULL || name == NULL || buf->len == 0) { len = -1; // Bad source dst[0] = '\0'; } else { name_len = strlen(name); e = buf->ptr + buf->len; len = -4; // Name does not exist dst[0] = '\0'; for (p = buf->ptr; p + name_len < e; p++) { if ((p == buf->ptr || p[-1] == '&') && p[name_len] == '=' && !mg_ncasecmp(name, p, name_len)) { p += name_len + 1; s = (const char *) memchr(p, '&', (size_t) (e - p)); if (s == NULL) s = e; len = mg_url_decode(p, (size_t) (s - p), dst, dst_len, 1); if (len < 0) len = -3; // Failed to decode break; } } } return len; } int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, int is_form_url_encoded) { size_t i, j; for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) { if (src[i] == '%') { // Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len if (i + 2 < src_len && isxdigit(*(const unsigned char *) (src + i + 1)) && isxdigit(*(const unsigned char *) (src + i + 2))) { mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]); i += 2; } else { return -1; } } else if (is_form_url_encoded && src[i] == '+') { dst[j] = ' '; } else { dst[j] = src[i]; } } if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination return i >= src_len && j < dst_len ? (int) j : -1; } int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) { size_t i; for (i = 0; i < buf_len; i++) { if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) return -1; if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') || (i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n')) return (int) i + 1; } return 0; } static const char *skip(const char *s, const char *e, const char *d, struct mg_str *v) { v->ptr = s; while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++; v->len = (size_t) (s - v->ptr); while (s < e && strchr(d, *s) != NULL) s++; return s; } struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) { size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]); for (i = 0; i < max && h->headers[i].name.len > 0; i++) { struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value; if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v; } return NULL; } void mg_http_parse_headers(const char *s, const char *end, struct mg_http_header *h, int max_headers) { int i; for (i = 0; i < max_headers; i++) { struct mg_str k, v, tmp; const char *he = skip(s, end, "\n", &tmp); s = skip(s, he, ": \r\n", &k); s = skip(s, he, "\r\n", &v); if (k.len == tmp.len) continue; while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces if (k.len == 0) break; // LOG(LL_INFO, ("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr, //(int) k.len, k.ptr, (int) v.len, v.ptr)); h[i].name = k; h[i].value = v; } } int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) { int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len); const char *end = s + req_len, *qs; struct mg_str *cl; memset(hm, 0, sizeof(*hm)); if (req_len <= 0) return req_len; hm->message.ptr = hm->head.ptr = s; hm->body.ptr = end; hm->head.len = (size_t) req_len; hm->chunk.ptr = end; hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite // Parse request line s = skip(s, end, " ", &hm->method); s = skip(s, end, " ", &hm->uri); s = skip(s, end, "\r\n", &hm->proto); // Sanity check. Allow protocol/reason to be empty if (hm->method.len == 0 || hm->uri.len == 0) return -1; // If URI contains '?' character, setup query string if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) { hm->query.ptr = qs + 1; hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1)); hm->uri.len = (size_t) (qs - hm->uri.ptr); } mg_http_parse_headers(s, end, hm->headers, sizeof(hm->headers) / sizeof(hm->headers[0])); if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) { hm->body.len = (size_t) mg_to64(*cl); hm->message.len = (size_t) req_len + hm->body.len; } // mg_http_parse() is used to parse both HTTP requests and HTTP // responses. If HTTP response does not have Content-Length set, then // body is read until socket is closed, i.e. body.len is infinite (~0). // // For HTTP requests though, according to // http://tools.ietf.org/html/rfc7231#section-8.1.3, // only POST and PUT methods have defined body semantics. // Therefore, if Content-Length is not specified and methods are // not one of PUT or POST, set body length to 0. // // So, if it is HTTP request, and Content-Length is not set, // and method is not (PUT or POST) then reset body length to zero. is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0; if (hm->body.len == (size_t) ~0 && !is_response && mg_vcasecmp(&hm->method, "PUT") != 0 && mg_vcasecmp(&hm->method, "POST") != 0) { hm->body.len = 0; hm->message.len = (size_t) req_len; } // The 204 (No content) responses also have 0 body length if (hm->body.len == (size_t) ~0 && is_response && mg_vcasecmp(&hm->uri, "204") == 0) { hm->body.len = 0; hm->message.len = (size_t) req_len; } return req_len; } static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt, va_list ap) { char mem[256], *buf = mem; int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); mg_printf(c, "%X\r\n", len); mg_send(c, buf, len > 0 ? (size_t) len : 0); mg_send(c, "\r\n", 2); if (buf != mem) free(buf); } void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) { va_list ap; va_start(ap, fmt); mg_http_vprintf_chunk(c, fmt, ap); va_end(ap); } void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) { mg_printf(c, "%lX\r\n", (unsigned long) len); mg_send(c, buf, len); mg_send(c, "\r\n", 2); } // clang-format off static const char *mg_http_status_code_str(int status_code) { switch (status_code) { case 100: return "Continue"; case 101: return "Switching Protocols"; case 102: return "Processing"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 207: return "Multi-Status"; case 208: return "Already Reported"; case 226: return "IM Used"; case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Found"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 307: return "Temporary Redirect"; case 308: return "Permanent Redirect"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Timeout"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Payload Too Large"; case 414: return "Request-URI Too Long"; case 415: return "Unsupported Media Type"; case 416: return "Requested Range Not Satisfiable"; case 417: return "Expectation Failed"; case 418: return "I'm a teapot"; case 421: return "Misdirected Request"; case 422: return "Unprocessable Entity"; case 423: return "Locked"; case 424: return "Failed Dependency"; case 426: return "Upgrade Required"; case 428: return "Precondition Required"; case 429: return "Too Many Requests"; case 431: return "Request Header Fields Too Large"; case 444: return "Connection Closed Without Response"; case 451: return "Unavailable For Legal Reasons"; case 499: return "Client Closed Request"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; case 505: return "HTTP Version Not Supported"; case 506: return "Variant Also Negotiates"; case 507: return "Insufficient Storage"; case 508: return "Loop Detected"; case 510: return "Not Extended"; case 511: return "Network Authentication Required"; case 599: return "Network Connect Timeout Error"; default: return "OK"; } } // clang-format on void mg_http_reply(struct mg_connection *c, int code, const char *headers, const char *fmt, ...) { char mem[256], *buf = mem; va_list ap; int len; va_start(ap, fmt); len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); va_end(ap); mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: %d\r\n\r\n", code, mg_http_status_code_str(code), headers == NULL ? "" : headers, len); mg_send(c, buf, len > 0 ? (size_t) len : 0); if (buf != mem) free(buf); } static void http_cb(struct mg_connection *, int, void *, void *); static void restore_http_cb(struct mg_connection *c) { struct mg_fd *fd = (struct mg_fd *) c->pfn_data; if (fd != NULL) fd->fs->close(fd); c->pfn_data = NULL; c->pfn = http_cb; } char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) { snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) mtime, (int64_t) size); return buf; } static void static_cb(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_WRITE || ev == MG_EV_POLL) { struct mg_fd *fd = (struct mg_fd *) fn_data; // Read to send IO buffer directly, avoid extra on-stack buffer size_t n, max = 2 * MG_IO_SIZE; if (c->send.size < max) mg_iobuf_resize(&c->send, max); if (c->send.len >= c->send.size) return; // Rate limit n = fd->fs->read(fd->fd, c->send.buf + c->send.len, c->send.size - c->send.len); if (n > 0) c->send.len += n; if (c->send.len < c->send.size) restore_http_cb(c); } else if (ev == MG_EV_CLOSE) { restore_http_cb(c); } (void) ev_data; } static struct mg_str guess_content_type(struct mg_str path, const char *extra) { // clang-format off struct mimeentry { struct mg_str extension, value; }; #define MIME_ENTRY(a, b) {{a, sizeof(a) - 1 }, { b, sizeof(b) - 1 }} // clang-format on const struct mimeentry tab[] = { MIME_ENTRY("html", "text/html; charset=utf-8"), MIME_ENTRY("htm", "text/html; charset=utf-8"), MIME_ENTRY("css", "text/css; charset=utf-8"), MIME_ENTRY("js", "text/javascript; charset=utf-8"), MIME_ENTRY("gif", "image/gif"), MIME_ENTRY("png", "image/png"), MIME_ENTRY("woff", "font/woff"), MIME_ENTRY("ttf", "font/ttf"), MIME_ENTRY("aac", "audio/aac"), MIME_ENTRY("avi", "video/x-msvideo"), MIME_ENTRY("azw", "application/vnd.amazon.ebook"), MIME_ENTRY("bin", "application/octet-stream"), MIME_ENTRY("bmp", "image/bmp"), MIME_ENTRY("bz", "application/x-bzip"), MIME_ENTRY("bz2", "application/x-bzip2"), MIME_ENTRY("csv", "text/csv"), MIME_ENTRY("doc", "application/msword"), MIME_ENTRY("epub", "application/epub+zip"), MIME_ENTRY("exe", "application/octet-stream"), MIME_ENTRY("gz", "application/gzip"), MIME_ENTRY("ico", "image/x-icon"), MIME_ENTRY("json", "application/json"), MIME_ENTRY("mid", "audio/mid"), MIME_ENTRY("mjs", "text/javascript"), MIME_ENTRY("mov", "video/quicktime"), MIME_ENTRY("mp3", "audio/mpeg"), MIME_ENTRY("mp4", "video/mp4"), MIME_ENTRY("mpeg", "video/mpeg"), MIME_ENTRY("mpg", "video/mpeg"), MIME_ENTRY("ogg", "application/ogg"), MIME_ENTRY("pdf", "application/pdf"), MIME_ENTRY("rar", "application/rar"), MIME_ENTRY("rtf", "application/rtf"), MIME_ENTRY("shtml", "text/html; charset=utf-8"), MIME_ENTRY("svg", "image/svg+xml"), MIME_ENTRY("tar", "application/tar"), MIME_ENTRY("tgz", "application/tar-gz"), MIME_ENTRY("txt", "text/plain; charset=utf-8"), MIME_ENTRY("wasm", "application/wasm"), MIME_ENTRY("wav", "audio/wav"), MIME_ENTRY("weba", "audio/webm"), MIME_ENTRY("webm", "video/webm"), MIME_ENTRY("webp", "image/webp"), MIME_ENTRY("xls", "application/excel"), MIME_ENTRY("xml", "application/xml"), MIME_ENTRY("xsl", "application/xml"), MIME_ENTRY("zip", "application/zip"), MIME_ENTRY("3gp", "video/3gpp"), MIME_ENTRY("7z", "application/x-7z-compressed"), MIME_ENTRY("7z", "application/x-7z-compressed"), {{0, 0}, {0, 0}}, }; size_t i = 0; struct mg_str k, v, s = mg_str(extra); // Shrink path to its extension only while (i < path.len && path.ptr[path.len - i - 1] != '.') i++; path.ptr += path.len - i; path.len = i; // Process user-provided mime type overrides, if any while (mg_commalist(&s, &k, &v)) { if (mg_strcmp(path, k) == 0) return v; } // Process built-in mime types for (i = 0; tab[i].extension.ptr != NULL; i++) { if (mg_strcmp(path, tab[i].extension) == 0) return tab[i].value; } return mg_str("text/plain; charset=utf-8"); } static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { size_t i, numparsed = 0; LOG(LL_INFO, ("%.*s", (int) s->len, s->ptr)); for (i = 0; i + 6 < s->len; i++) { if (memcmp(&s->ptr[i], "bytes=", 6) == 0) { struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6); if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; *a = mg_to64(p); // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--; if (p.len && p.ptr[0] == '-') p.ptr++, p.len--; *b = mg_to64(p); if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++; // LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed)); break; } } return (int) numparsed; } void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, const char *path, struct mg_http_serve_opts *opts) { char etag[64]; struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; struct mg_fd *fd = fs->open(path, MG_FS_READ); size_t size = 0; time_t mtime = 0; struct mg_str *inm = NULL; if (fd == NULL || fs->stat(path, &size, &mtime) == 0) { LOG(LL_DEBUG, ("404 [%s] %p", path, (void *) fd)); mg_http_reply(c, 404, "", "%s", "Not found\n"); fs->close(fd); // NOTE: mg_http_etag() call should go first! } else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL && (inm = mg_http_get_header(hm, "If-None-Match")) != NULL && mg_vcasecmp(inm, etag) == 0) { fs->close(fd); mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n"); } else { int n, status = 200; char range[100] = ""; int64_t r1 = 0, r2 = 0, cl = (int64_t) size; struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); // Handle Range header struct mg_str *rh = mg_http_get_header(hm, "Range"); if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) { // If range is specified like "400-", set second limit to content len if (n == 1) r2 = cl - 1; if (r1 > r2 || r2 >= cl) { status = 416; cl = 0; snprintf(range, sizeof(range), "Content-Range: bytes */" MG_INT64_FMT "\r\n", (int64_t) size); } else { status = 206; cl = r2 - r1 + 1; snprintf(range, sizeof(range), "Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT "/" MG_INT64_FMT "\r\n", r1, r1 + cl - 1, (int64_t) size); fs->seek(fd->fd, (size_t) r1); } } mg_printf(c, "HTTP/1.1 %d %s\r\nContent-Type: %.*s\r\n" "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n", status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, etag, cl, range, opts->extra_headers ? opts->extra_headers : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { c->is_draining = 1; fs->close(fd); } else { c->pfn = static_cb; c->pfn_data = fd; } } } struct printdirentrydata { struct mg_connection *c; struct mg_http_message *hm; struct mg_http_serve_opts *opts; const char *dir; }; static void printdirentry(const char *name, void *userdata) { struct printdirentrydata *d = (struct printdirentrydata *) userdata; struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs; size_t size = 0; time_t mtime = 0; char path[MG_PATH_MAX], sz[64], mod[64]; int flags, n = 0; // LOG(LL_DEBUG, ("[%s] [%s]", d->dir, name)); if (snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) < 0) { LOG(LL_ERROR, ("%s truncated", name)); } else if ((flags = fs->stat(path, &size, &mtime)) == 0) { LOG(LL_ERROR, ("%lu stat(%s): %d", d->c->id, path, errno)); } else { const char *slash = flags & MG_FS_DIR ? "/" : ""; struct tm t; if (flags & MG_FS_DIR) { snprintf(sz, sizeof(sz), "%s", "[DIR]"); } else if (size < 1024) { snprintf(sz, sizeof(sz), "%d", (int) size); } else if (size < 0x100000) { snprintf(sz, sizeof(sz), "%.1fk", (double) size / 1024.0); } else if (size < 0x40000000) { snprintf(sz, sizeof(sz), "%.1fM", (double) size / 1048576); } else { snprintf(sz, sizeof(sz), "%.1fG", (double) size / 1073741824); } strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&mtime, &t)); n = (int) mg_url_encode(name, strlen(name), path, sizeof(path)); mg_printf(d->c, " %s%s" "%s%s\n", n, path, slash, name, slash, mod, sz); } } static void remove_double_dots(char *s) { char *p = s; while (*s != '\0') { *p++ = *s++; if (s[-1] == '/' || s[-1] == '\\') { while (s[0] != '\0') { if (s[0] == '/' || s[0] == '\\') { s++; } else if (s[0] == '.' && s[1] == '.') { s += 2; } else { break; } } } } *p = '\0'; } // Resolve requested file into `path` and return its fs->stat() result static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm, struct mg_fs *fs, struct mg_str url, struct mg_str dir, char *path, size_t path_size) { int flags = 0, tmp; // Append URI to the root_dir, and sanitize it size_t n = (size_t) snprintf(path, path_size, "%.*s", (int) dir.len, dir.ptr); if (n > path_size) n = path_size; path[path_size - 1] = '\0'; if ((fs->stat(path, NULL, NULL) & MG_FS_DIR) == 0) { mg_http_reply(c, 400, "", "Invalid web root [%.*s]\n", (int) dir.len, dir.ptr); } else { if (n + 2 < path_size) path[n++] = '/', path[n] = '\0'; mg_url_decode(hm->uri.ptr + url.len, hm->uri.len - url.len, path + n, path_size - n, 0); path[path_size - 1] = '\0'; // Double-check remove_double_dots(path); n = strlen(path); LOG(LL_DEBUG, ("--> %s", path)); while (n > 0 && path[n - 1] == '/') path[--n] = 0; // Trim trailing slashes flags = fs->stat(path, NULL, NULL); // Does it exist? if (flags == 0) { mg_http_reply(c, 404, "", "Not found\n"); // Does not exist, doh } else if (flags & MG_FS_DIR) { if (((snprintf(path + n, path_size - n, "/index.html") > 0 && (tmp = fs->stat(path, NULL, NULL)) != 0) || (snprintf(path + n, path_size - n, "/index.shtml") > 0 && (tmp = fs->stat(path, NULL, NULL)) != 0))) { flags = tmp; } else { path[n] = '\0'; // Remove appended index file name } } } return flags; } static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts, char *path, size_t path_size) { struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs; struct mg_str k, v, s = mg_str(opts->root_dir), u = {0, 0}, p = {0, 0}; while (mg_commalist(&s, &k, &v)) { if (v.len == 0) v = k, k = mg_str("/"); if (hm->uri.len < k.len) continue; if (mg_strcmp(k, mg_str_n(hm->uri.ptr, k.len)) != 0) continue; u = k, p = v; } return uri_to_path2(c, hm, fs, u, p, path, path_size); } void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts) { char path[MG_PATH_MAX] = ""; const char *sp = opts->ssi_pattern; int flags = uri_to_path(c, hm, opts, path, sizeof(path)); if (flags == 0) return; LOG(LL_DEBUG, ("%.*s %s %d", (int) hm->uri.len, hm->uri.ptr, path, flags)); mg_http_serve_file(c, hm, path, opts); } static bool mg_is_url_safe(int c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '.' || c == '_' || c == '-' || c == '~'; } size_t mg_url_encode(const char *s, size_t sl, char *buf, size_t len) { size_t i, n = 0; for (i = 0; i < sl; i++) { int c = *(unsigned char *) &s[i]; if (n + 4 >= len) return 0; if (mg_is_url_safe(c)) { buf[n++] = s[i]; } else { buf[n++] = '%'; mg_hex(&s[i], 1, &buf[n]); n += 2; } } return n; } static struct mg_str stripquotes(struct mg_str s) { return s.len > 1 && s.ptr[0] == '"' && s.ptr[s.len - 1] == '"' ? mg_str_n(s.ptr + 1, s.len - 2) : s; } struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { size_t i; for (i = 0; i + v.len + 2 < s.len; i++) { if (s.ptr[i + v.len] == '=' && memcmp(&s.ptr[i], v.ptr, v.len) == 0) { const char *p = &s.ptr[i + v.len + 1], *b = p, *x = &s.ptr[s.len]; int q = p < x && *p == '"' ? 1 : 0; while (p < x && (q ? p == b || *p != '"' : *p != ';' && *p != ' ')) p++; // LOG(LL_INFO, ("[%.*s] [%.*s] [%.*s]", (int) s.len, s.ptr, (int) v.len, // v.ptr, (int) (p - b), b)); return stripquotes(mg_str_n(b, (size_t) (p - b + q))); } } return mg_str_n(NULL, 0); } bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len); } static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) { size_t i = 0, n; while (i < len && buf[i] != '\r' && i != '\n') i++; n = mg_unhexn((char *) buf, i); while (i < len && (buf[i] == '\r' || i == '\n')) i++; // LOG(LL_INFO, ("len %zu i %zu n %zu ", len, i, n)); if (ll != NULL) *ll = i + 1; if (i < len && i + n + 2 < len) return i + n + 3; return 0; } // Walk through all chunks in the chunked body. For each chunk, fire // an MG_EV_HTTP_CHUNK event. static void walkchunks(struct mg_connection *c, struct mg_http_message *hm, size_t reqlen) { size_t off = 0, bl, ll; while (off + reqlen < c->recv.len) { char *buf = (char *) &c->recv.buf[reqlen]; size_t memo = c->recv.len; size_t cl = get_chunk_length(&buf[off], memo - reqlen - off, &ll); // LOG(LL_INFO, ("len %zu off %zu cl %zu ll %zu", len, off, cl, ll)); if (cl == 0) break; hm->chunk = mg_str_n(&buf[off + ll], cl < ll + 2 ? 0 : cl - ll - 2); mg_call(c, MG_EV_HTTP_CHUNK, hm); // Increase offset only if user has not deleted this chunk if (memo == c->recv.len) off += cl; if (cl <= 5) { // Zero chunk - last one. Prepare body - cut off chunk lengths off = bl = 0; while (off + reqlen < c->recv.len) { char *buf2 = (char *) &c->recv.buf[reqlen]; size_t memo2 = c->recv.len; size_t cl2 = get_chunk_length(&buf2[off], memo2 - reqlen - off, &ll); size_t n = cl2 < ll + 2 ? 0 : cl2 - ll - 2; memmove(buf2 + bl, buf2 + off + ll, n); bl += n; off += cl2; if (cl2 <= 5) break; } // LOG(LL_INFO, ("BL->%d del %d off %d", (int) bl, (int) del, (int) off)); c->recv.len -= off - bl; // Set message length to indicate we've received // everything, to fire MG_EV_HTTP_MSG hm->message.len = bl + reqlen; hm->body.len = bl; break; } } } static bool mg_is_chunked(struct mg_http_message *hm) { struct mg_str needle = mg_str_n("chunked", 7); struct mg_str *te = mg_http_get_header(hm, "Transfer-Encoding"); return te != NULL && mg_strstr(*te, needle) != NULL; } void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) { struct mg_str ch = hm->chunk; if (mg_is_chunked(hm)) { ch.len += 4; // \r\n before and after the chunk ch.ptr -= 2; while (ch.ptr > hm->body.ptr && *ch.ptr != '\n') ch.ptr--, ch.len++; } { const char *end = &ch.ptr[ch.len]; size_t n = (size_t) (end - (char *) c->recv.buf); if (c->recv.len > n) { memmove((char *) ch.ptr, end, (size_t) (c->recv.len - n)); } // LOG(LL_INFO, ("DELETING CHUNK: %zu %zu %zu\n%.*s", c->recv.len, n, // ch.len, (int) ch.len, ch.ptr)); } c->recv.len -= ch.len; } static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) { if (ev == MG_EV_READ || ev == MG_EV_CLOSE) { struct mg_http_message hm; for (;;) { int n = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm); bool is_chunked = n > 0 && mg_is_chunked(&hm); if (ev == MG_EV_CLOSE) { hm.message.len = c->recv.len; hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr); } else if (is_chunked && n > 0) { walkchunks(c, &hm, (size_t) n); } // LOG(LL_INFO, //("---->%d %d\n%.*s", n, is_chunked, (int) c->recv.len, c->recv.buf)); if (n < 0 && ev == MG_EV_READ) { mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf); break; } else if (n > 0 && (size_t) c->recv.len >= hm.message.len) { mg_call(c, MG_EV_HTTP_MSG, &hm); mg_iobuf_del(&c->recv, 0, hm.message.len); } else { if (n > 0 && !is_chunked) { hm.chunk = mg_str_n((char *) &c->recv.buf[n], c->recv.len - (size_t) n); mg_call(c, MG_EV_HTTP_CHUNK, &hm); } break; } } } (void) fnd; (void) evd; } struct mg_connection *mg_http_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data) { struct mg_connection *c = mg_connect(mgr, url, fn, fn_data); if (c != NULL) c->pfn = http_cb; return c; } struct mg_connection *mg_http_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data) { struct mg_connection *c = mg_listen(mgr, url, fn, fn_data); if (c != NULL) c->pfn = http_cb; return c; } #ifdef MG_ENABLE_LINES #line 1 "src/iobuf.c" #endif #include // Not using memset for zeroing memory, cause it can be dropped by compiler // See https://github.com/cesanta/mongoose/pull/1265 static void zeromem(volatile unsigned char *buf, size_t len) { if (buf != NULL) { while (len--) *buf++ = 0; } } int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) { int ok = 1; if (new_size == 0) { zeromem(io->buf, io->size); free(io->buf); io->buf = NULL; io->len = io->size = 0; } else if (new_size != io->size) { // NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the // porting to some obscure platforms like FreeRTOS void *p = calloc(1, new_size); if (p != NULL) { size_t len = new_size < io->len ? new_size : io->len; if (len > 0) memcpy(p, io->buf, len); zeromem(io->buf, io->size); free(io->buf); io->buf = (unsigned char *) p; io->size = new_size; } else { ok = 0; LOG(LL_ERROR, ("%lu->%lu", (unsigned long) io->size, (unsigned long) new_size)); } } return ok; } int mg_iobuf_init(struct mg_iobuf *io, size_t size) { return mg_iobuf_resize(io, size); } size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf, size_t len, size_t chunk_size) { size_t new_size = io->len + len; if (new_size > io->size) { new_size += chunk_size; // Make sure that io->size new_size -= new_size % chunk_size; // is aligned by chunk_size boundary mg_iobuf_resize(io, new_size); // Attempt to realloc if (new_size != io->size) len = 0; // Realloc failure, append nothing } if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs); if (buf != NULL) memmove(io->buf + ofs, buf, len); if (ofs > io->len) io->len += ofs - io->len; io->len += len; return len; } size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) { if (ofs > io->len) ofs = io->len; if (ofs + len > io->len) len = io->len - ofs; memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len); zeromem(io->buf + io->len - len, len); io->len -= len; return len; } void mg_iobuf_free(struct mg_iobuf *io) { mg_iobuf_resize(io, 0); } #ifdef MG_ENABLE_LINES #line 1 "src/log.c" #endif #if MG_ENABLE_LOG static void mg_log_stdout(const void *buf, size_t len, void *userdata) { (void) userdata; fwrite(buf, 1, len, stdout); } static const char *s_spec = "2"; static void (*s_fn)(const void *, size_t, void *) = mg_log_stdout; static void *s_fn_param = NULL; void mg_log_set(const char *spec) { LOG(LL_DEBUG, ("Setting log level to %s", spec)); s_spec = spec; } bool mg_log_prefix(int level, const char *file, int line, const char *fname) { // static unsigned long seq; int max = LL_INFO; struct mg_str k, v, s = mg_str(s_spec); const char *p = strrchr(file, '/'); if (s_fn == NULL) return false; if (p == NULL) p = strrchr(file, '\\'); p = p == NULL ? file : p + 1; while (mg_commalist(&s, &k, &v)) { if (v.len == 0) max = atoi(k.ptr); if (v.len > 0 && strncmp(p, k.ptr, k.len) == 0) max = atoi(v.ptr); } if (level <= max) { char timebuf[21], buf[50] = ""; time_t t = time(NULL); struct tm tmp, *tm = gmtime_r(&t, &tmp); int n; (void) tmp; strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm); n = snprintf(buf, sizeof(buf), "%s %d %s:%d:%s", timebuf, level, p, line, fname); if (n < 0 || n > (int) sizeof(buf) - 2) n = sizeof(buf) - 2; while (n < (int) sizeof(buf) - 1) buf[n++] = ' '; s_fn(buf, sizeof(buf) - 1, s_fn_param); return true; } else { return false; } } void mg_log(const char *fmt, ...) { char mem[256], *buf = mem; va_list ap; int len = 0; va_start(ap, fmt); len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); va_end(ap); s_fn(buf, len > 0 ? (size_t) len : 0, s_fn_param); s_fn("\n", 1, s_fn_param); if (buf != mem) free(buf); } void mg_log_set_callback(void (*fn)(const void *, size_t, void *), void *fnd) { s_fn = fn; s_fn_param = fnd; } #endif #ifdef MG_ENABLE_LINES #line 1 "src/net.c" #endif int mg_vprintf(struct mg_connection *c, const char *fmt, va_list ap) { char mem[256], *buf = mem; int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap); len = mg_send(c, buf, len > 0 ? (size_t) len : 0); if (buf != mem) free(buf); return len; } int mg_printf(struct mg_connection *c, const char *fmt, ...) { int len = 0; va_list ap; va_start(ap, fmt); len = mg_vprintf(c, fmt, ap); va_end(ap); return len; } char *mg_straddr(struct mg_connection *c, char *buf, size_t len) { char tmp[100]; const char *fmt = c->peer.is_ip6 ? "[%s]:%d" : "%s:%d"; mg_ntoa(&c->peer, tmp, sizeof(tmp)); snprintf(buf, len, fmt, tmp, (int) mg_ntohs(c->peer.port)); return buf; } char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len) { if (addr->is_ip6) { uint16_t *p = (uint16_t *) addr->ip6; snprintf(buf, len, "%x:%x:%x:%x:%x:%x:%x:%x", mg_htons(p[0]), mg_htons(p[1]), mg_htons(p[2]), mg_htons(p[3]), mg_htons(p[4]), mg_htons(p[5]), mg_htons(p[6]), mg_htons(p[7])); } else { uint8_t p[4]; memcpy(p, &addr->ip, sizeof(p)); snprintf(buf, len, "%d.%d.%d.%d", (int) p[0], (int) p[1], (int) p[2], (int) p[3]); } return buf; } static bool mg_atonl(struct mg_str str, struct mg_addr *addr) { if (mg_vcasecmp(&str, "localhost") != 0) return false; addr->ip = mg_htonl(0x7f000001); addr->is_ip6 = false; return true; } static bool mg_aton4(struct mg_str str, struct mg_addr *addr) { uint8_t data[4] = {0, 0, 0, 0}; size_t i, num_dots = 0; for (i = 0; i < str.len; i++) { if (str.ptr[i] >= '0' && str.ptr[i] <= '9') { int octet = data[num_dots] * 10 + (str.ptr[i] - '0'); if (octet > 255) return false; data[num_dots] = (uint8_t) octet; } else if (str.ptr[i] == '.') { if (num_dots >= 3 || i == 0 || str.ptr[i - 1] == '.') return false; num_dots++; } else { return false; } } if (num_dots != 3 || str.ptr[i - 1] == '.') return false; memcpy(&addr->ip, data, sizeof(data)); addr->is_ip6 = false; return true; } static bool mg_v4mapped(struct mg_str str, struct mg_addr *addr) { int i; if (str.len < 14) return false; if (str.ptr[0] != ':' || str.ptr[1] != ':' || str.ptr[6] != ':') return false; for (i = 2; i < 6; i++) { if (str.ptr[i] != 'f' && str.ptr[i] != 'F') return false; } if (!mg_aton4(mg_str_n(&str.ptr[7], str.len - 7), addr)) return false; memset(addr->ip6, 0, sizeof(addr->ip6)); addr->ip6[10] = addr->ip6[11] = 255; memcpy(&addr->ip6[12], &addr->ip, 4); addr->is_ip6 = true; return true; } static bool mg_aton6(struct mg_str str, struct mg_addr *addr) { size_t i, j = 0, n = 0, dc = 42; if (str.len > 2 && str.ptr[0] == '[') str.ptr++, str.len -= 2; if (mg_v4mapped(str, addr)) return true; for (i = 0; i < str.len; i++) { if ((str.ptr[i] >= '0' && str.ptr[i] <= '9') || (str.ptr[i] >= 'a' && str.ptr[i] <= 'f') || (str.ptr[i] >= 'A' && str.ptr[i] <= 'F')) { unsigned long val; if (i > j + 3) return false; // LOG(LL_DEBUG, ("%zu %zu [%.*s]", i, j, (int) (i - j + 1), // &str.ptr[j])); val = mg_unhexn(&str.ptr[j], i - j + 1); addr->ip6[n] = (uint8_t) ((val >> 8) & 255); addr->ip6[n + 1] = (uint8_t) (val & 255); } else if (str.ptr[i] == ':') { j = i + 1; if (i > 0 && str.ptr[i - 1] == ':') { dc = n; // Double colon if (i > 1 && str.ptr[i - 2] == ':') return false; } else if (i > 0) { n += 2; } if (n > 14) return false; addr->ip6[n] = addr->ip6[n + 1] = 0; // For trailing :: } else { return false; } } if (n < 14 && dc == 42) return false; if (n < 14) { memmove(&addr->ip6[dc + (14 - n)], &addr->ip6[dc], n - dc + 2); memset(&addr->ip6[dc], 0, 14 - n); } addr->is_ip6 = true; return true; } bool mg_aton(struct mg_str str, struct mg_addr *addr) { // LOG(LL_INFO, ("[%.*s]", (int) str.len, str.ptr)); return mg_atonl(str, addr) || mg_aton4(str, addr) || mg_aton6(str, addr); } void mg_mgr_free(struct mg_mgr *mgr) { struct mg_connection *c; for (c = mgr->conns; c != NULL; c = c->next) c->is_closing = 1; mg_mgr_poll(mgr, 0); #if MG_ARCH == MG_ARCH_FREERTOS_TCP FreeRTOS_DeleteSocketSet(mgr->ss); #endif LOG(LL_INFO, ("All connections closed")); } void mg_mgr_init(struct mg_mgr *mgr) { memset(mgr, 0, sizeof(*mgr)); #if defined(_WIN32) && MG_ENABLE_WINSOCK // clang-format off { WSADATA data; WSAStartup(MAKEWORD(2, 2), &data); } // clang-format on #elif MG_ARCH == MG_ARCH_FREERTOS_TCP mgr->ss = FreeRTOS_CreateSocketSet(); #elif defined(__unix) || defined(__unix__) || defined(__APPLE__) // Ignore SIGPIPE signal, so if client cancels the request, it // won't kill the whole process. signal(SIGPIPE, SIG_IGN); #endif } #ifdef MG_ENABLE_LINES #line 1 "src/sock.c" #endif #if MG_ENABLE_SOCKET #if defined(_WIN32) && MG_ENABLE_WINSOCK #define MG_SOCK_ERRNO WSAGetLastError() #ifndef SO_EXCLUSIVEADDRUSE #define SO_EXCLUSIVEADDRUSE ((int) (~SO_REUSEADDR)) #endif #elif MG_ARCH == MG_ARCH_FREERTOS_TCP #define MG_SOCK_ERRNO errno typedef Socket_t SOCKET; #define INVALID_SOCKET FREERTOS_INVALID_SOCKET #else #define MG_SOCK_ERRNO errno #ifndef closesocket #define closesocket(x) close(x) #endif #define INVALID_SOCKET (-1) typedef int SOCKET; #endif #define FD(c_) ((SOCKET) (size_t) (c_)->fd) #define S2PTR(s_) ((void *) (size_t) (s_)) #ifndef MSG_NONBLOCKING #define MSG_NONBLOCKING 0 #endif #ifndef AF_INET6 #define AF_INET6 10 #endif union usa { struct sockaddr sa; struct sockaddr_in sin; #if MG_ENABLE_IPV6 struct sockaddr_in6 sin6; #endif }; static socklen_t tousa(struct mg_addr *a, union usa *usa) { socklen_t len = sizeof(usa->sin); memset(usa, 0, sizeof(*usa)); usa->sin.sin_family = AF_INET; usa->sin.sin_port = a->port; *(uint32_t *) &usa->sin.sin_addr = a->ip; #if MG_ENABLE_IPV6 if (a->is_ip6) { usa->sin.sin_family = AF_INET6; usa->sin6.sin6_port = a->port; memcpy(&usa->sin6.sin6_addr, a->ip6, sizeof(a->ip6)); len = sizeof(usa->sin6); } #endif return len; } static void tomgaddr(union usa *usa, struct mg_addr *a, bool is_ip6) { a->is_ip6 = is_ip6; a->port = usa->sin.sin_port; memcpy(&a->ip, &usa->sin.sin_addr, sizeof(a->ip)); #if MG_ENABLE_IPV6 if (is_ip6) { memcpy(a->ip6, &usa->sin6.sin6_addr, sizeof(a->ip6)); a->port = usa->sin6.sin6_port; } #endif } static bool mg_sock_would_block(void) { int err = MG_SOCK_ERRNO; return err == EINPROGRESS || err == EWOULDBLOCK #ifndef WINCE || err == EAGAIN || err == EINTR #endif #if defined(_WIN32) && MG_ENABLE_WINSOCK || err == WSAEINTR || err == WSAEWOULDBLOCK #endif ; } static struct mg_connection *alloc_conn(struct mg_mgr *mgr, bool is_client, SOCKET fd) { struct mg_connection *c = (struct mg_connection *) calloc(1, sizeof(*c)); if (c != NULL) { c->is_client = is_client; c->fd = S2PTR(fd); c->mgr = mgr; c->id = ++mgr->nextid; } return c; } static long mg_sock_send(struct mg_connection *c, const void *buf, size_t len) { long n = send(FD(c), (char *) buf, len, MSG_NONBLOCKING); return n == 0 ? -1 : n < 0 && mg_sock_would_block() ? 0 : n; } bool mg_send(struct mg_connection *c, const void *buf, size_t len) { return c->is_udp ? mg_sock_send(c, buf, len) > 0 : mg_iobuf_add(&c->send, c->send.len, buf, len, MG_IO_SIZE); } static void mg_set_non_blocking_mode(SOCKET fd) { #if defined(_WIN32) && MG_ENABLE_WINSOCK unsigned long on = 1; #elif MG_ARCH == MG_ARCH_FREERTOS_TCP const BaseType_t off = 0; setsockopt(fd, 0, FREERTOS_SO_RCVTIMEO, &off, sizeof(off)); setsockopt(fd, 0, FREERTOS_SO_SNDTIMEO, &off, sizeof(off)); #elif MG_ARCH == MG_ARCH_FREERTOS_LWIP lwip_fcntl(fd, F_SETFL, O_NONBLOCK); #else fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK, FD_CLOEXEC); #endif } SOCKET mg_open_listener(const char *url, struct mg_addr *addr) { SOCKET fd = INVALID_SOCKET; int s_err = 0; // Memoized socket error, in case closesocket() overrides it memset(addr, 0, sizeof(*addr)); addr->port = mg_htons(mg_url_port(url)); if (!mg_aton(mg_url_host(url), addr)) { LOG(LL_ERROR, ("invalid listening URL: %s", url)); } else { union usa usa; socklen_t slen = tousa(addr, &usa); int on = 1, af = addr->is_ip6 ? AF_INET6 : AF_INET; int type = strncmp(url, "udp:", 4) == 0 ? SOCK_DGRAM : SOCK_STREAM; int proto = type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; (void) on; if ((fd = socket(af, type, proto)) != INVALID_SOCKET && #if (!defined(_WIN32) || !defined(SO_EXCLUSIVEADDRUSE)) && \ (!defined(LWIP_SOCKET) || (defined(LWIP_SOCKET) && SO_REUSE == 1)) // 1. SO_RESUSEADDR is not enabled on Windows because the semantics of // SO_REUSEADDR on UNIX and Windows is different. On Windows, // SO_REUSEADDR allows to bind a socket to a port without error even // if the port is already open by another program. This is not the // behavior SO_REUSEADDR was designed for, and leads to hard-to-track // failure scenarios. Therefore, SO_REUSEADDR was disabled on Windows // unless SO_EXCLUSIVEADDRUSE is supported and set on a socket. // 2. In case of LWIP, SO_REUSEADDR should be explicitly enabled, by // defining // SO_REUSE (in lwipopts.h), otherwise the code below will compile // but won't work! (setsockopt will return EINVAL) !setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) && #endif #if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE) // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" //! setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &on, sizeof(on)) //! && !setsockopt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &on, sizeof(on)) && #endif bind(fd, &usa.sa, slen) == 0 && // NOTE(lsm): FreeRTOS uses backlog value as a connection limit (type == SOCK_DGRAM || listen(fd, 128) == 0)) { // In case port was set to 0, get the real port number if (getsockname(fd, &usa.sa, &slen) == 0) { addr->port = usa.sin.sin_port; #if MG_ENABLE_IPV6 if (addr->is_ip6) addr->port = usa.sin6.sin6_port; #endif } mg_set_non_blocking_mode(fd); } else if (fd != INVALID_SOCKET) { s_err = MG_SOCK_ERRNO; closesocket(fd); fd = INVALID_SOCKET; } } if (fd == INVALID_SOCKET) { if (s_err == 0) s_err = MG_SOCK_ERRNO; LOG(LL_ERROR, ("Failed to listen on %s, errno %d", url, s_err)); } return fd; } static long mg_sock_recv(struct mg_connection *c, void *buf, size_t len) { long n = 0; if (c->is_udp) { union usa usa; socklen_t slen = tousa(&c->peer, &usa); n = recvfrom(FD(c), (char *) buf, len, 0, &usa.sa, &slen); if (n > 0) tomgaddr(&usa, &c->peer, slen != sizeof(usa.sin)); } else { n = recv(FD(c), (char *) buf, len, MSG_NONBLOCKING); } return n == 0 ? -1 : n < 0 && mg_sock_would_block() ? 0 : n; } // NOTE(lsm): do only one iteration of reads, cause some systems // (e.g. FreeRTOS stack) return 0 instead of -1/EWOULDBLOCK when no data static void read_conn(struct mg_connection *c) { if (c->recv.len >= MG_MAX_RECV_BUF_SIZE) { mg_error(c, "max_recv_buf_size reached"); } else if (c->recv.size - c->recv.len < MG_IO_SIZE && !mg_iobuf_resize(&c->recv, c->recv.size + MG_IO_SIZE)) { mg_error(c, "oom"); } else { char *buf = (char *) &c->recv.buf[c->recv.len]; size_t len = c->recv.size - c->recv.len; long n = mg_sock_recv(c, buf, len); LOG(n > 0 ? LL_VERBOSE_DEBUG : LL_DEBUG, ("%-3lu %d%d%d%d%d%d%d%d%d%d%d%d %7ld %ld/%ld err %d", c->id, c->is_listening, c->is_client, c->is_accepted, c->is_resolving, c->is_connecting, c->is_udp, c->is_websocket, c->is_hexdumping, c->is_draining, c->is_closing, c->is_readable, c->is_writable, (long) c->recv.len, n, (long) len, MG_SOCK_ERRNO)); if (n == 0) { // Do nothing } else if (n < 0) { c->is_closing = 1; // Error, or normal termination } else if (n > 0) { struct mg_str evd = mg_str_n(buf, (size_t) n); if (c->is_hexdumping) { char *s = mg_hexdump(buf, (size_t) n); LOG(LL_INFO, ("\n-- %lu %s %s %ld\n%s", c->id, c->label, "<-", n, s)); free(s); } c->recv.len += (size_t) n; mg_call(c, MG_EV_READ, &evd); } } } static void write_conn(struct mg_connection *c) { char *buf = (char *) c->send.buf; size_t len = c->send.len; long n = mg_sock_send(c, buf, len); LOG(n > 0 ? LL_VERBOSE_DEBUG : LL_DEBUG, ("%-3lu %d%d%d%d%d%d%d%d%d%d%d%d %7ld %ld err %d", c->id, c->is_listening, c->is_client, c->is_accepted, c->is_resolving, c->is_connecting, c->is_udp, c->is_websocket, c->is_hexdumping, c->is_draining, c->is_closing, c->is_readable, c->is_writable, (long) c->send.len, n, MG_SOCK_ERRNO)); if (n == 0) { // Do nothing } else if (n < 0) { c->is_closing = 1; // Error, or normal termination } else if (n > 0) { // Hexdump before deleting if (c->is_hexdumping) { char *s = mg_hexdump(buf, (size_t) n); LOG(LL_INFO, ("\n-- %lu %s %s %ld\n%s", c->id, c->label, "<-", n, s)); free(s); } mg_iobuf_del(&c->send, 0, (size_t) n); if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); mg_call(c, MG_EV_WRITE, &n); // if (c->send.len == 0) mg_iobuf_resize(&c->send, 0); } } static void close_conn(struct mg_connection *c) { LIST_DELETE(struct mg_connection, &c->mgr->conns, c); // Order of operations is important. `MG_EV_CLOSE` event must be fired // before we deallocate received data, see #1331 mg_call(c, MG_EV_CLOSE, NULL); LOG(LL_DEBUG, ("%lu closed", c->id)); if (FD(c) != INVALID_SOCKET) { closesocket(FD(c)); #if MG_ARCH == MG_ARCH_FREERTOS_TCP FreeRTOS_FD_CLR(c->fd, c->mgr->ss, eSELECT_ALL); #endif c->fd = S2PTR(INVALID_SOCKET); } mg_iobuf_free(&c->recv); mg_iobuf_free(&c->send); memset(c, 0, sizeof(*c)); free(c); } static void setsockopts(struct mg_connection *c) { #if MG_ARCH == MG_ARCH_FREERTOS_TCP (void) c; #else int on = 1; #if !defined(SOL_TCP) #define SOL_TCP IPPROTO_TCP #endif setsockopt(FD(c), SOL_TCP, TCP_NODELAY, (char *) &on, sizeof(on)); #if defined(TCP_QUICKACK) setsockopt(FD(c), SOL_TCP, TCP_QUICKACK, (char *) &on, sizeof(on)); #endif setsockopt(FD(c), SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)); #if ESP32 || ESP8266 || defined(__linux__) int idle = 60; setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); #endif #if !defined(_WIN32) && !defined(__QNX__) { int cnt = 3, intvl = 20; setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt)); setsockopt(FD(c), IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)); } #endif #endif } void mg_connect_resolved(struct mg_connection *c) { char buf[40]; int type = c->is_udp ? SOCK_DGRAM : SOCK_STREAM; int rc, af = c->peer.is_ip6 ? AF_INET6 : AF_INET; mg_straddr(c, buf, sizeof(buf)); c->fd = S2PTR(socket(af, type, 0)); if (FD(c) == INVALID_SOCKET) { mg_error(c, "socket(): %d", MG_SOCK_ERRNO); } else { union usa usa; socklen_t slen = tousa(&c->peer, &usa); if (c->is_udp == 0) mg_set_non_blocking_mode(FD(c)); if (c->is_udp == 0) setsockopts(c); mg_call(c, MG_EV_RESOLVE, NULL); if ((rc = connect(FD(c), &usa.sa, slen)) == 0) { mg_call(c, MG_EV_CONNECT, NULL); } else if (mg_sock_would_block()) { c->is_connecting = 1; } else { mg_error(c, "connect: %d", MG_SOCK_ERRNO); } } } struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data) { struct mg_connection *c = NULL; if ((c = alloc_conn(mgr, 1, INVALID_SOCKET)) == NULL) { LOG(LL_ERROR, ("OOM")); } else { struct mg_str host = mg_url_host(url); LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); c->is_udp = (strncmp(url, "udp:", 4) == 0); c->peer.port = mg_htons(mg_url_port(url)); c->fn = fn; c->fn_data = fn_data; LOG(LL_DEBUG, ("%lu -> %s", c->id, url)); } return c; } static void accept_conn(struct mg_mgr *mgr, struct mg_connection *lsn) { struct mg_connection *c = NULL; union usa usa; socklen_t sa_len = sizeof(usa); SOCKET fd = accept(FD(lsn), &usa.sa, &sa_len); if (fd == INVALID_SOCKET) { LOG(LL_ERROR, ("%lu accept failed, errno %d", lsn->id, MG_SOCK_ERRNO)); #if (!defined(_WIN32) && (MG_ARCH != MG_ARCH_FREERTOS_TCP)) } else if ((long) fd >= FD_SETSIZE) { LOG(LL_ERROR, ("%ld > %ld", (long) fd, (long) FD_SETSIZE)); closesocket(fd); #endif } else if ((c = alloc_conn(mgr, 0, fd)) == NULL) { LOG(LL_ERROR, ("%lu OOM", lsn->id)); closesocket(fd); } else { char buf[40]; tomgaddr(&usa, &c->peer, sa_len != sizeof(usa.sin)); mg_straddr(c, buf, sizeof(buf)); LOG(LL_DEBUG, ("%lu accepted %s", c->id, buf)); mg_set_non_blocking_mode(FD(c)); setsockopts(c); LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); c->is_accepted = 1; c->is_hexdumping = lsn->is_hexdumping; c->pfn = lsn->pfn; c->pfn_data = lsn->pfn_data; c->fn = lsn->fn; c->fn_data = lsn->fn_data; mg_call(c, MG_EV_ACCEPT, NULL); } } static bool mg_socketpair(SOCKET sp[2], union usa usa[2]) { socklen_t n = sizeof(usa[0].sin); bool result = false; (void) memset(&usa[0], 0, sizeof(usa[0])); usa[0].sin.sin_family = AF_INET; *(uint32_t *) &usa->sin.sin_addr = mg_htonl(0x7f000001); // 127.0.0.1 usa[1] = usa[0]; if ((sp[0] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET && (sp[1] = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET && bind(sp[0], &usa[0].sa, n) == 0 && bind(sp[1], &usa[1].sa, n) == 0 && getsockname(sp[0], &usa[0].sa, &n) == 0 && getsockname(sp[1], &usa[1].sa, &n) == 0 && connect(sp[0], &usa[1].sa, n) == 0 && connect(sp[1], &usa[0].sa, n) == 0) { mg_set_non_blocking_mode(sp[1]); // Set close-on-exec result = true; } else { if (sp[0] != INVALID_SOCKET) closesocket(sp[0]); if (sp[1] != INVALID_SOCKET) closesocket(sp[1]); sp[0] = sp[1] = INVALID_SOCKET; } return result; } void mg_mgr_wakeup(struct mg_connection *c) { LOG(LL_INFO, ("skt: %p", c->pfn_data)); send((SOCKET) (size_t) c->pfn_data, "\x01", 1, MSG_NONBLOCKING); } static void pf1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { if (ev == MG_EV_READ) mg_iobuf_free(&c->recv); (void) ev_data, (void) fn_data; } struct mg_connection *mg_mkpipe(struct mg_mgr *mgr, mg_event_handler_t fn, void *fn_data) { union usa usa[2]; SOCKET sp[2] = {INVALID_SOCKET, INVALID_SOCKET}; struct mg_connection *c = NULL; if (!mg_socketpair(sp, usa)) { LOG(LL_ERROR, ("Cannot create socket pair")); } else if ((c = alloc_conn(mgr, false, sp[1])) == NULL) { closesocket(sp[0]); closesocket(sp[1]); LOG(LL_ERROR, ("OOM")); } else { LOG(LL_INFO, ("pipe %lu", (unsigned long) sp[0])); tomgaddr(&usa[0], &c->peer, false); c->is_udp = 1; c->pfn = pf1; c->pfn_data = (void *) (size_t) sp[0]; c->fn = fn; c->fn_data = fn_data; LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); } return c; } struct mg_connection *mg_listen(struct mg_mgr *mgr, const char *url, mg_event_handler_t fn, void *fn_data) { struct mg_connection *c = NULL; bool is_udp = strncmp(url, "udp:", 4) == 0; struct mg_addr addr; SOCKET fd = mg_open_listener(url, &addr); if (fd == INVALID_SOCKET) { LOG(LL_ERROR, ("Failed: %s, errno %d", url, MG_SOCK_ERRNO)); } else if ((c = alloc_conn(mgr, 0, fd)) == NULL) { LOG(LL_ERROR, ("OOM %s", url)); closesocket(fd); } else { memcpy(&c->peer, &addr, sizeof(struct mg_addr)); c->fd = S2PTR(fd); c->is_listening = 1; c->is_udp = is_udp; LIST_ADD_HEAD(struct mg_connection, &mgr->conns, c); c->fn = fn; c->fn_data = fn_data; LOG(LL_INFO, ("%lu accepting on %s (port %u)", c->id, url, mg_ntohs(c->peer.port))); } return c; } static void mg_iotest(struct mg_mgr *mgr, int ms) { #if MG_ARCH == MG_ARCH_FREERTOS_TCP struct mg_connection *c; for (c = mgr->conns; c != NULL; c = c->next) { if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue; FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT); if (c->is_connecting || (c->send.len > 0)) FreeRTOS_FD_SET(c->fd, mgr->ss, eSELECT_WRITE); } FreeRTOS_select(mgr->ss, pdMS_TO_TICKS(ms)); for (c = mgr->conns; c != NULL; c = c->next) { EventBits_t bits = FreeRTOS_FD_ISSET(c->fd, mgr->ss); c->is_readable = bits & (eSELECT_READ | eSELECT_EXCEPT) ? 1 : 0; c->is_writable = bits & eSELECT_WRITE ? 1 : 0; FreeRTOS_FD_CLR(c->fd, mgr->ss, eSELECT_READ | eSELECT_EXCEPT | eSELECT_WRITE); } #else struct timeval tv = {ms / 1000, (ms % 1000) * 1000}; struct mg_connection *c; fd_set rset, wset; SOCKET maxfd = 0; int rc; FD_ZERO(&rset); FD_ZERO(&wset); for (c = mgr->conns; c != NULL; c = c->next) { if (c->is_closing || c->is_resolving || FD(c) == INVALID_SOCKET) continue; FD_SET(FD(c), &rset); if (FD(c) > maxfd) maxfd = FD(c); if (c->is_connecting || (c->send.len > 0)) FD_SET(FD(c), &wset); } if ((rc = select((int) maxfd + 1, &rset, &wset, NULL, &tv)) < 0) { LOG(LL_DEBUG, ("select: %d %d", rc, MG_SOCK_ERRNO)); FD_ZERO(&rset); FD_ZERO(&wset); } for (c = mgr->conns; c != NULL; c = c->next) { c->is_readable = c->is_tls && c->is_readable ? 1 : FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &rset); c->is_writable = FD(c) != INVALID_SOCKET && FD_ISSET(FD(c), &wset); } #endif } static void connect_conn(struct mg_connection *c) { int rc = 0; #if MG_ARCH != MG_ARCH_FREERTOS_TCP socklen_t len = sizeof(rc); if (getsockopt(FD(c), SOL_SOCKET, SO_ERROR, (char *) &rc, &len)) rc = 1; #endif if (rc == EAGAIN || rc == EWOULDBLOCK) rc = 0; c->is_connecting = 0; if (rc) { char buf[40]; mg_error(c, "error connecting to %s", mg_straddr(c, buf, sizeof(buf))); } else { mg_call(c, MG_EV_CONNECT, NULL); } } void mg_mgr_poll(struct mg_mgr *mgr, int ms) { struct mg_connection *c, *tmp; unsigned long now; mg_iotest(mgr, ms); now = mg_millis(); mg_timer_poll(now); for (c = mgr->conns; c != NULL; c = tmp) { tmp = c->next; mg_call(c, MG_EV_POLL, &now); LOG(LL_VERBOSE_DEBUG, ("%lu %c%c %c%c%c", c->id, c->is_readable ? 'r' : '-', c->is_writable ? 'w' : '-', c->is_connecting ? 'C' : 'c', c->is_resolving ? 'R' : 'r', c->is_closing ? 'C' : 'c')); if (c->is_resolving || c->is_closing) { // Do nothing } else if (c->is_listening && c->is_udp == 0) { if (c->is_readable) accept_conn(mgr, c); } else if (c->is_connecting) { if (c->is_readable || c->is_writable) connect_conn(c); } else { if (c->is_readable) read_conn(c); if (c->is_writable) write_conn(c); } if (c->is_draining && c->send.len == 0) c->is_closing = 1; if (c->is_closing) close_conn(c); } } #endif #ifdef MG_ENABLE_LINES #line 1 "src/str.c" #endif #include struct mg_str mg_str_s(const char *s) { struct mg_str str = {s, s == NULL ? 0 : strlen(s)}; return str; } struct mg_str mg_str_n(const char *s, size_t n) { struct mg_str str = {s, n}; return str; } int mg_lower(const char *s) { return tolower(*(const unsigned char *) s); } int mg_ncasecmp(const char *s1, const char *s2, size_t len) { int diff = 0; if (len > 0) do { diff = mg_lower(s1++) - mg_lower(s2++); } while (diff == 0 && s1[-1] != '\0' && --len > 0); return diff; } int mg_casecmp(const char *s1, const char *s2) { return mg_ncasecmp(s1, s2, (size_t) ~0); } int mg_vcmp(const struct mg_str *s1, const char *s2) { size_t n2 = strlen(s2), n1 = s1->len; int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2); if (r == 0) return (int) (n1 - n2); return r; } int mg_vcasecmp(const struct mg_str *str1, const char *str2) { size_t n2 = strlen(str2), n1 = str1->len; int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2); if (r == 0) return (int) (n1 - n2); return r; } struct mg_str mg_strdup(const struct mg_str s) { struct mg_str r = {NULL, 0}; if (s.len > 0 && s.ptr != NULL) { char *sc = (char *) calloc(1, s.len + 1); if (sc != NULL) { memcpy(sc, s.ptr, s.len); sc[s.len] = '\0'; r.ptr = sc; r.len = s.len; } } return r; } int mg_strcmp(const struct mg_str str1, const struct mg_str str2) { size_t i = 0; while (i < str1.len && i < str2.len) { int c1 = str1.ptr[i]; int c2 = str2.ptr[i]; if (c1 < c2) return -1; if (c1 > c2) return 1; i++; } if (i < str1.len) return 1; if (i < str2.len) return -1; return 0; } const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle) { size_t i; if (needle.len > haystack.len) return NULL; for (i = 0; i <= haystack.len - needle.len; i++) { if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) { return haystack.ptr + i; } } return NULL; } struct mg_str mg_strstrip(struct mg_str s) { while (s.len > 0 && isspace((int) *s.ptr)) s.ptr++, s.len--; while (s.len > 0 && isspace((int) *(s.ptr + s.len - 1))) s.len--; return s; } #ifdef MG_ENABLE_LINES #line 1 "src/timer.c" #endif // Copyright (c) Cesanta Software Limited // All rights reserved struct mg_timer *g_timers; void mg_timer_init(struct mg_timer *t, unsigned long ms, unsigned flags, void (*fn)(void *), void *arg) { struct mg_timer tmp = {ms, flags, fn, arg, 0UL, g_timers}; *t = tmp; g_timers = t; if (flags & MG_TIMER_RUN_NOW) fn(arg); } void mg_timer_free(struct mg_timer *t) { struct mg_timer **head = &g_timers; while (*head && *head != t) head = &(*head)->next; if (*head) *head = t->next; } void mg_timer_poll(unsigned long now_ms) { // If time goes back (wrapped around), reset timers struct mg_timer *t, *tmp; static unsigned long oldnow; // Timestamp in a previous invocation if (oldnow > now_ms) { // If it is wrapped, reset timers for (t = g_timers; t != NULL; t = t->next) t->expire = 0; } oldnow = now_ms; for (t = g_timers; t != NULL; t = tmp) { tmp = t->next; if (t->expire == 0) t->expire = now_ms + t->period_ms; if (t->expire > now_ms) continue; t->fn(t->arg); // Try to tick timers with the given period as accurate as possible, // even if this polling function is called with some random period. t->expire = now_ms - t->expire > (unsigned long) t->period_ms ? now_ms + t->period_ms : t->expire + t->period_ms; if (!(t->flags & MG_TIMER_REPEAT)) mg_timer_free(t); } } #ifdef MG_ENABLE_LINES #line 1 "src/url.c" #endif #include struct url { size_t key, user, pass, host, port, uri, end; }; int mg_url_is_ssl(const char *url) { return strncmp(url, "https:", 6) == 0 || strncmp(url, "ssl:", 4) == 0; } static struct url urlparse(const char *url) { size_t i; struct url u; memset(&u, 0, sizeof(u)); for (i = 0; url[i] != '\0'; i++) { if (i > 0 && u.host == 0 && url[i - 1] == '/' && url[i] == '/') { u.host = i + 1; u.port = 0; } else if (url[i] == ']') { u.port = 0; // IPv6 URLs, like http://[::1]/bar } else if (url[i] == ':' && u.port == 0 && u.uri == 0) { u.port = i + 1; } else if (url[i] == '@' && u.user == 0 && u.pass == 0) { u.user = u.host; u.pass = u.port; u.host = i + 1; u.port = 0; } else if (u.host && u.uri == 0 && url[i] == '/') { u.uri = i; } } u.end = i; #if 0 printf("[%s] %d %d %d %d %d\n", url, u.user, u.pass, u.host, u.port, u.uri); #endif return u; } struct mg_str mg_url_host(const char *url) { struct url u = urlparse(url); size_t n = u.port ? u.port - u.host - 1 : u.uri ? u.uri - u.host : u.end - u.host; struct mg_str s = mg_str_n(url + u.host, n); return s; } const char *mg_url_uri(const char *url) { struct url u = urlparse(url); return u.uri ? url + u.uri : "/"; } unsigned short mg_url_port(const char *url) { struct url u = urlparse(url); unsigned short port = 0; if (memcmp(url, "http:", 5) == 0 || memcmp(url, "ws:", 3) == 0) port = 80; if (memcmp(url, "wss:", 4) == 0 || memcmp(url, "https:", 6) == 0) port = 443; if (memcmp(url, "mqtt:", 5) == 0) port = 1883; if (memcmp(url, "mqtts:", 6) == 0) port = 8883; if (u.port) port = (unsigned short) atoi(url + u.port); return port; } struct mg_str mg_url_user(const char *url) { struct url u = urlparse(url); struct mg_str s = mg_str(""); if (u.user && (u.pass || u.host)) { size_t n = u.pass ? u.pass - u.user - 1 : u.host - u.user - 1; s = mg_str_n(url + u.user, n); } return s; } struct mg_str mg_url_pass(const char *url) { struct url u = urlparse(url); struct mg_str s = mg_str_n("", 0UL); if (u.pass && u.host) { size_t n = u.host - u.pass - 1; s = mg_str_n(url + u.pass, n); } return s; } #ifdef MG_ENABLE_LINES #line 1 "src/util.c" #endif char *mg_file_read(const char *path, size_t *sizep) { FILE *fp; char *data = NULL; size_t size = 0; if ((fp = fopen(path, "rb")) != NULL) { fseek(fp, 0, SEEK_END); size = (size_t) ftell(fp); rewind(fp); data = (char *) calloc(1, size + 1); if (data != NULL) { if (fread(data, 1, size, fp) != size) { free(data); data = NULL; } else { data[size] = '\0'; if (sizep != NULL) *sizep = size; } } fclose(fp); } return data; } bool mg_file_write(const char *path, const void *buf, size_t len) { bool result = false; FILE *fp; char tmp[MG_PATH_MAX]; snprintf(tmp, sizeof(tmp), "%s.%d", path, rand()); fp = fopen(tmp, "wb"); if (fp != NULL) { result = fwrite(buf, 1, len, fp) == len; fclose(fp); if (result) { remove(path); rename(tmp, path); } else { remove(tmp); } } return result; } bool mg_file_printf(const char *path, const char *fmt, ...) { char tmp[256], *buf = tmp; bool result; int len; va_list ap; va_start(ap, fmt); len = mg_vasprintf(&buf, sizeof(tmp), fmt, ap); va_end(ap); result = mg_file_write(path, buf, len > 0 ? (size_t) len : 0); if (buf != tmp) free(buf); return result; } #if MG_ENABLE_CUSTOM_RANDOM #else void mg_random(void *buf, size_t len) { bool done = false; unsigned char *p = (unsigned char *) buf; #if MG_ARCH == MG_ARCH_ESP32 while (len--) *p++ = (unsigned char) (esp_random() & 255); #elif MG_ARCH == MG_ARCH_WIN32 #elif MG_ARCH_UNIX FILE *fp = fopen("/dev/urandom", "rb"); if (fp != NULL) { if (fread(buf, 1, len, fp) == len) done = true; fclose(fp); } #endif // Fallback to a pseudo random gen if (!done) { while (len--) *p++ = (unsigned char) (rand() & 255); } } #endif bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) { size_t i = 0, j = 0, ni = 0, nj = 0; while (i < n1 || j < n2) { if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) { i++, j++; } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) { ni = i, nj = j + 1, i++; } else if (nj > 0 && nj <= n2 && (s1[ni] == '#' || s2[j] != '/')) { i = ni, j = nj; } else { // printf(">>: [%s] [%s] %d %d %d %d\n", s1, s2, i, j, ni, nj); return false; } } return true; } static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff, size_t *klen, size_t *voff, size_t *vlen) { size_t kvlen, kl; for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != ',';) kvlen++; for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++; if (koff != NULL) *koff = ofs; if (klen != NULL) *klen = kl; if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0; if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0; ofs += kvlen + 1; return ofs > n ? n : ofs; } bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) { size_t koff = 0, klen = 0, voff = 0, vlen = 0; size_t off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen); if (k != NULL) *k = mg_str_n(s->ptr + koff, klen); if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen); *s = mg_str_n(s->ptr + off, s->len - off); return off > 0; } uint32_t mg_ntohl(uint32_t net) { uint8_t data[4] = {0, 0, 0, 0}; memcpy(&data, &net, sizeof(data)); return (((uint32_t) data[3]) << 0) | (((uint32_t) data[2]) << 8) | (((uint32_t) data[1]) << 16) | (((uint32_t) data[0]) << 24); } uint16_t mg_ntohs(uint16_t net) { uint8_t data[2] = {0, 0}; memcpy(&data, &net, sizeof(data)); return (uint16_t) ((uint16_t) data[1] | (((uint16_t) data[0]) << 8)); } char *mg_hexdump(const void *buf, size_t len) { const unsigned char *p = (const unsigned char *) buf; size_t i, idx, n = 0, ofs = 0, dlen = len * 5 + 100; char ascii[17] = "", *dst = (char *) calloc(1, dlen); if (dst == NULL) return dst; for (i = 0; i < len; i++) { idx = i % 16; if (idx == 0) { if (i > 0 && dlen > n) n += (size_t) snprintf(dst + n, dlen - n, " %s\n", ascii); if (dlen > n) n += (size_t) snprintf(dst + n, dlen - n, "%04x ", (int) (i + ofs)); } if (dlen < n) break; n += (size_t) snprintf(dst + n, dlen - n, " %02x", p[i]); ascii[idx] = (char) (p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i]); ascii[idx + 1] = '\0'; } while (i++ % 16) { if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, "%s", " "); } if (n < dlen) n += (size_t) snprintf(dst + n, dlen - n, " %s\n", ascii); if (n > dlen - 1) n = dlen - 1; dst[n] = '\0'; return dst; } char *mg_hex(const void *buf, size_t len, char *to) { const unsigned char *p = (const unsigned char *) buf; static const char *hex = "0123456789abcdef"; size_t i = 0; for (; len--; p++) { to[i++] = hex[p[0] >> 4]; to[i++] = hex[p[0] & 0x0f]; } to[i] = '\0'; return to; } static unsigned char mg_unhex_nimble(unsigned char c) { return (c >= '0' && c <= '9') ? (unsigned char) (c - '0') : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7') : (unsigned char) (c - 'W'); } unsigned long mg_unhexn(const char *s, size_t len) { unsigned long i = 0, v = 0; for (i = 0; i < len; i++) v <<= 4, v |= mg_unhex_nimble(((uint8_t *) s)[i]); return v; } void mg_unhex(const char *buf, size_t len, unsigned char *to) { size_t i; for (i = 0; i < len; i += 2) { to[i >> 1] = (unsigned char) mg_unhexn(&buf[i], 2); } } int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap) { va_list ap_copy; int len; va_copy(ap_copy, ap); len = vsnprintf(*buf, size, fmt, ap_copy); va_end(ap_copy); if (len < 0) { // eCos and Windows are not standard-compliant and return -1 when // the buffer is too small. Keep allocating larger buffers until we // succeed or out of memory. // LCOV_EXCL_START *buf = NULL; while (len < 0) { free(*buf); if (size == 0) size = 5; size *= 2; if ((*buf = (char *) calloc(1, size)) == NULL) { len = -1; break; } va_copy(ap_copy, ap); len = vsnprintf(*buf, size - 1, fmt, ap_copy); va_end(ap_copy); } // Microsoft version of vsnprintf() is not always null-terminated, so put // the terminator manually if (*buf != NULL) (*buf)[len] = 0; // LCOV_EXCL_STOP } else if (len >= (int) size) { /// Standard-compliant code path. Allocate a buffer that is large enough if ((*buf = (char *) calloc(1, (size_t) len + 1)) == NULL) { len = -1; // LCOV_EXCL_LINE } else { // LCOV_EXCL_LINE va_copy(ap_copy, ap); len = vsnprintf(*buf, (size_t) len + 1, fmt, ap_copy); va_end(ap_copy); } } return len; } int mg_asprintf(char **buf, size_t size, const char *fmt, ...) { int ret; va_list ap; va_start(ap, fmt); ret = mg_vasprintf(buf, size, fmt, ap); va_end(ap); return ret; } int64_t mg_to64(struct mg_str str) { int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */; size_t i = 0; while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++; if (i < str.len && str.ptr[i] == '-') neg = -1, i++; while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') { if (result > max) return 0; result *= 10; result += (str.ptr[i] - '0'); i++; } return result * neg; } uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len) { int i; crc = ~crc; while (len--) { crc ^= *(unsigned char *) buf++; for (i = 0; i < 8; i++) crc = crc & 1 ? (crc >> 1) ^ 0xedb88320 : crc >> 1; } return ~crc; } static int isbyte(int n) { return n >= 0 && n <= 255; } static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) { int n, a, b, c, d, slash = 32, len = 0; if ((sscanf(spec, "%d.%d.%d.%d/%d%n", &a, &b, &c, &d, &slash, &n) == 5 || sscanf(spec, "%d.%d.%d.%d%n", &a, &b, &c, &d, &n) == 4) && isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && slash >= 0 && slash < 33) { len = n; *net = ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | (uint32_t) d; *mask = slash ? (uint32_t) (0xffffffffU << (32 - slash)) : (uint32_t) 0; } return len; } int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip) { struct mg_str k, v; int allowed = acl.len == 0 ? '+' : '-'; // If any ACL is set, deny by default while (mg_commalist(&acl, &k, &v)) { uint32_t net, mask; if (k.ptr[0] != '+' && k.ptr[0] != '-') return -1; if (parse_net(&k.ptr[1], &net, &mask) == 0) return -2; if ((remote_ip & mask) == net) allowed = k.ptr[0]; } return allowed == '+'; } double mg_time(void) { #if MG_ARCH == MG_ARCH_WIN32 SYSTEMTIME sysnow; FILETIME ftime; GetLocalTime(&sysnow); SystemTimeToFileTime(&sysnow, &ftime); /* * 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64 * This should not cause a problems in this (21th) century * 2. Windows FILETIME is a number of 100-nanosecond intervals since January * 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC, * thus, we need to convert to seconds and adjust amount (subtract 11644473600 * seconds) */ return (double) (((int64_t) ftime.dwLowDateTime + ((int64_t) ftime.dwHighDateTime << 32)) / 10000000.0) - 11644473600; #elif MG_ARCH == MG_ARCH_FREERTOS_TCP return mg_millis() / 1000.0; #else struct timeval tv; if (gettimeofday(&tv, NULL /* tz */) != 0) return 0; return (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0); #endif /* _WIN32 */ } void mg_usleep(unsigned long usecs) { #if MG_ARCH == MG_ARCH_WIN32 Sleep(usecs / 1000); #elif MG_ARCH == MG_ARCH_ESP8266 ets_delay_us(usecs); #elif MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_FREERTOS_LWIP vTaskDelay(pdMS_TO_TICKS(usecs / 1000)); #else usleep((useconds_t) usecs); #endif } unsigned long mg_millis(void) { #if MG_ARCH == MG_ARCH_WIN32 return GetTickCount(); #elif MG_ARCH == MG_ARCH_ESP32 return esp_timer_get_time() / 1000; #elif MG_ARCH == MG_ARCH_ESP8266 return xTaskGetTickCount() * portTICK_PERIOD_MS; #elif MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_FREERTOS_LWIP return xTaskGetTickCount() * portTICK_PERIOD_MS; #else struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); return (unsigned long) ((uint64_t) ts.tv_sec * 1000 + (uint64_t) ts.tv_nsec / 1000000); #endif }