mirror of
https://git.swurl.xyz/swirl/pacebin.git
synced 2024-11-25 14:29:19 +02:00
2714 lines
86 KiB
C
2714 lines
86 KiB
C
|
// Copyright (c) 2004-2013 Sergey Lyubka
|
||
|
// Copyright (c) 2013-2021 Cesanta Software Limited
|
||
|
// All rights reserved
|
||
|
//
|
||
|
// This software is dual-licensed: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License version 2 as
|
||
|
// published by the Free Software Foundation. For the terms of this
|
||
|
// license, see <http://www.gnu.org/licenses/>.
|
||
|
//
|
||
|
// You are free to use this software under the terms of the GNU General
|
||
|
// Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||
|
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||
|
// See the GNU General Public License for more details.
|
||
|
//
|
||
|
// Alternatively, you can license this software under a commercial
|
||
|
// license, as set out in <https://www.cesanta.com/license>.
|
||
|
|
||
|
#include "mongoose.h"
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/private.h"
|
||
|
#endif
|
||
|
void mg_connect_resolved(struct mg_connection *);
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/event.c"
|
||
|
#endif
|
||
|
|
||
|
void mg_call(struct mg_connection *c, int ev, void *ev_data) {
|
||
|
if (c->pfn != NULL) c->pfn(c, ev, ev_data, c->pfn_data);
|
||
|
if (c->fn != NULL) c->fn(c, ev, ev_data, c->fn_data);
|
||
|
}
|
||
|
|
||
|
void mg_error(struct mg_connection *c, const char *fmt, ...) {
|
||
|
char mem[256], *buf = mem;
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
||
|
va_end(ap);
|
||
|
LOG(LL_ERROR, ("%lu %s", c->id, buf));
|
||
|
mg_call(c, MG_EV_ERROR, buf);
|
||
|
if (buf != mem) free(buf);
|
||
|
c->is_closing = 1;
|
||
|
}
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/fs_packed.c"
|
||
|
#endif
|
||
|
|
||
|
struct packed_file {
|
||
|
const char *data;
|
||
|
size_t size;
|
||
|
size_t pos;
|
||
|
};
|
||
|
|
||
|
const char *mg_unpack(const char *path, size_t *size, time_t *mtime);
|
||
|
const char *mg_unlist(size_t no);
|
||
|
#if MG_ENABLE_PACKED_FS
|
||
|
#else
|
||
|
const char *mg_unpack(const char *path, size_t *size, time_t *mtime) {
|
||
|
(void) path, (void) size, (void) mtime;
|
||
|
return NULL;
|
||
|
}
|
||
|
const char *mg_unlist(size_t no) {
|
||
|
(void) no;
|
||
|
return NULL;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
|
||
|
return n < strlen(path) && memcmp(prefix, path, n) == 0 && path[n] == '/';
|
||
|
//(n == 0 || path[n] == MG_DIRSEP);
|
||
|
}
|
||
|
|
||
|
static int packed_stat(const char *path, size_t *size, time_t *mtime) {
|
||
|
const char *p;
|
||
|
size_t i, n = strlen(path);
|
||
|
if (mg_unpack(path, size, mtime)) return MG_FS_READ; // Regular file
|
||
|
// Scan all files. If `path` is a dir prefix for any of them, it's a dir
|
||
|
for (i = 0; (p = mg_unlist(i)) != NULL; i++) {
|
||
|
if (is_dir_prefix(path, n, p)) return MG_FS_DIR;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void packed_list(const char *dir, void (*fn)(const char *, void *),
|
||
|
void *userdata) {
|
||
|
char buf[256], tmp[sizeof(buf)];
|
||
|
const char *path, *begin, *end;
|
||
|
size_t i, n = strlen(dir);
|
||
|
tmp[0] = '\0'; // Previously listed entry
|
||
|
for (i = 0; (path = mg_unlist(i)) != NULL; i++) {
|
||
|
if (!is_dir_prefix(dir, n, path)) continue;
|
||
|
begin = &path[n + 1];
|
||
|
end = strchr(begin, '/');
|
||
|
if (end == NULL) end = begin + strlen(begin);
|
||
|
snprintf(buf, sizeof(buf), "%.*s", (int) (end - begin), begin);
|
||
|
buf[sizeof(buf) - 1] = '\0';
|
||
|
// If this entry has been already listed, skip
|
||
|
// NOTE: we're assuming that file list is sorted alphabetically
|
||
|
if (strcmp(buf, tmp) == 0) continue;
|
||
|
fn(buf, userdata); // Not yet listed, call user function
|
||
|
strcpy(tmp, buf); // And save this entry as listed
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static struct mg_fd *packed_open(const char *path, int flags) {
|
||
|
size_t size = 0;
|
||
|
const char *data = mg_unpack(path, &size, NULL);
|
||
|
struct packed_file *fp = NULL;
|
||
|
struct mg_fd *fd = NULL;
|
||
|
if (data == NULL) return NULL;
|
||
|
if (flags & MG_FS_WRITE) return NULL;
|
||
|
fp = (struct packed_file *) calloc(1, sizeof(*fp));
|
||
|
fd = (struct mg_fd *) calloc(1, sizeof(*fd));
|
||
|
fp->size = size;
|
||
|
fp->data = data;
|
||
|
fd->fd = fp;
|
||
|
fd->fs = &mg_fs_packed;
|
||
|
return fd;
|
||
|
}
|
||
|
|
||
|
static void packed_close(struct mg_fd *fd) {
|
||
|
if (fd) free(fd->fd), free(fd);
|
||
|
}
|
||
|
|
||
|
static size_t packed_read(void *fd, void *buf, size_t len) {
|
||
|
struct packed_file *fp = (struct packed_file *) fd;
|
||
|
if (fp->pos + len > fp->size) len = fp->size - fp->pos;
|
||
|
memcpy(buf, &fp->data[fp->pos], len);
|
||
|
fp->pos += len;
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
static size_t packed_write(void *fd, const void *buf, size_t len) {
|
||
|
(void) fd, (void) buf, (void) len;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static size_t packed_seek(void *fd, size_t offset) {
|
||
|
struct packed_file *fp = (struct packed_file *) fd;
|
||
|
fp->pos = offset;
|
||
|
if (fp->pos > fp->size) fp->pos = fp->size;
|
||
|
return fp->pos;
|
||
|
}
|
||
|
|
||
|
struct mg_fs mg_fs_packed = {packed_stat, packed_list, packed_open,
|
||
|
packed_close, packed_read, packed_write,
|
||
|
packed_seek};
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/fs_posix.c"
|
||
|
#endif
|
||
|
|
||
|
#if defined(FOPEN_MAX)
|
||
|
static int p_stat(const char *path, size_t *size, time_t *mtime) {
|
||
|
#ifdef _WIN32
|
||
|
struct _stati64 st;
|
||
|
wchar_t tmp[PATH_MAX];
|
||
|
MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0]));
|
||
|
if (_wstati64(tmp, &st) != 0) return 0;
|
||
|
#else
|
||
|
struct stat st;
|
||
|
if (stat(path, &st) != 0) return 0;
|
||
|
#endif
|
||
|
if (size) *size = (size_t) st.st_size;
|
||
|
if (mtime) *mtime = st.st_mtime;
|
||
|
return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0);
|
||
|
}
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
struct dirent {
|
||
|
char d_name[MAX_PATH];
|
||
|
};
|
||
|
|
||
|
typedef struct win32_dir {
|
||
|
HANDLE handle;
|
||
|
WIN32_FIND_DATAW info;
|
||
|
struct dirent result;
|
||
|
} DIR;
|
||
|
|
||
|
int gettimeofday(struct timeval *tv, void *tz) {
|
||
|
FILETIME ft;
|
||
|
unsigned __int64 tmpres = 0;
|
||
|
|
||
|
if (tv != NULL) {
|
||
|
GetSystemTimeAsFileTime(&ft);
|
||
|
tmpres |= ft.dwHighDateTime;
|
||
|
tmpres <<= 32;
|
||
|
tmpres |= ft.dwLowDateTime;
|
||
|
tmpres /= 10; // convert into microseconds
|
||
|
tmpres -= (int64_t) 11644473600000000;
|
||
|
tv->tv_sec = (long) (tmpres / 1000000UL);
|
||
|
tv->tv_usec = (long) (tmpres % 1000000UL);
|
||
|
}
|
||
|
(void) tz;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
|
||
|
int ret;
|
||
|
char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
|
||
|
strncpy(buf, path, sizeof(buf));
|
||
|
buf[sizeof(buf) - 1] = '\0';
|
||
|
// Trim trailing slashes. Leave backslash for paths like "X:\"
|
||
|
p = buf + strlen(buf) - 1;
|
||
|
while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
|
||
|
memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
|
||
|
ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
|
||
|
// Convert back to Unicode. If doubly-converted string does not match the
|
||
|
// original, something is fishy, reject.
|
||
|
WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
|
||
|
NULL, NULL);
|
||
|
if (strcmp(buf, buf2) != 0) {
|
||
|
wbuf[0] = L'\0';
|
||
|
ret = 0;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
DIR *opendir(const char *name) {
|
||
|
DIR *d = NULL;
|
||
|
wchar_t wpath[MAX_PATH];
|
||
|
DWORD attrs;
|
||
|
|
||
|
if (name == NULL) {
|
||
|
SetLastError(ERROR_BAD_ARGUMENTS);
|
||
|
} else if ((d = (DIR *) calloc(1, sizeof(*d))) == NULL) {
|
||
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||
|
} else {
|
||
|
to_wchar(name, wpath, sizeof(wpath) / sizeof(wpath[0]));
|
||
|
attrs = GetFileAttributesW(wpath);
|
||
|
if (attrs != 0Xffffffff && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
|
||
|
(void) wcscat(wpath, L"\\*");
|
||
|
d->handle = FindFirstFileW(wpath, &d->info);
|
||
|
d->result.d_name[0] = '\0';
|
||
|
} else {
|
||
|
free(d);
|
||
|
d = NULL;
|
||
|
}
|
||
|
}
|
||
|
return d;
|
||
|
}
|
||
|
|
||
|
int closedir(DIR *d) {
|
||
|
int result = 0;
|
||
|
if (d != NULL) {
|
||
|
if (d->handle != INVALID_HANDLE_VALUE)
|
||
|
result = FindClose(d->handle) ? 0 : -1;
|
||
|
free(d);
|
||
|
} else {
|
||
|
result = -1;
|
||
|
SetLastError(ERROR_BAD_ARGUMENTS);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
struct dirent *readdir(DIR *d) {
|
||
|
struct dirent *result = NULL;
|
||
|
if (d != NULL) {
|
||
|
memset(&d->result, 0, sizeof(d->result));
|
||
|
if (d->handle != INVALID_HANDLE_VALUE) {
|
||
|
result = &d->result;
|
||
|
WideCharToMultiByte(CP_UTF8, 0, d->info.cFileName, -1, result->d_name,
|
||
|
sizeof(result->d_name), NULL, NULL);
|
||
|
if (!FindNextFileW(d->handle, &d->info)) {
|
||
|
FindClose(d->handle);
|
||
|
d->handle = INVALID_HANDLE_VALUE;
|
||
|
}
|
||
|
} else {
|
||
|
SetLastError(ERROR_FILE_NOT_FOUND);
|
||
|
}
|
||
|
} else {
|
||
|
SetLastError(ERROR_BAD_ARGUMENTS);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static void p_list(const char *dir, void (*fn)(const char *, void *),
|
||
|
void *userdata) {
|
||
|
#if MG_ENABLE_DIRLIST
|
||
|
struct dirent *dp;
|
||
|
DIR *dirp;
|
||
|
if ((dirp = (opendir(dir))) == NULL) return;
|
||
|
while ((dp = readdir(dirp)) != NULL) {
|
||
|
if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue;
|
||
|
fn(dp->d_name, userdata);
|
||
|
}
|
||
|
closedir(dirp);
|
||
|
#else
|
||
|
(void) dir, (void) fn, (void) userdata;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static struct mg_fd *p_open(const char *path, int flags) {
|
||
|
const char *mode = flags == (MG_FS_READ | MG_FS_WRITE) ? "r+b"
|
||
|
: flags & MG_FS_READ ? "rb"
|
||
|
: flags & MG_FS_WRITE ? "wb"
|
||
|
: "";
|
||
|
void *fp = NULL;
|
||
|
struct mg_fd *fd = NULL;
|
||
|
#ifdef _WIN32
|
||
|
wchar_t b1[PATH_MAX], b2[10];
|
||
|
MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0]));
|
||
|
MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0]));
|
||
|
fp = (void *) _wfopen(b1, b2);
|
||
|
#else
|
||
|
fp = (void *) fopen(path, mode);
|
||
|
#endif
|
||
|
if (fp == NULL) return NULL;
|
||
|
fd = (struct mg_fd *) calloc(1, sizeof(*fd));
|
||
|
fd->fd = fp;
|
||
|
fd->fs = &mg_fs_posix;
|
||
|
return fd;
|
||
|
}
|
||
|
|
||
|
static void p_close(struct mg_fd *fd) {
|
||
|
if (fd != NULL) fclose((FILE *) fd->fd), free(fd);
|
||
|
}
|
||
|
|
||
|
static size_t p_read(void *fp, void *buf, size_t len) {
|
||
|
return fread(buf, 1, len, (FILE *) fp);
|
||
|
}
|
||
|
|
||
|
static size_t p_write(void *fp, const void *buf, size_t len) {
|
||
|
return fwrite(buf, 1, len, (FILE *) fp);
|
||
|
}
|
||
|
|
||
|
static size_t p_seek(void *fp, size_t offset) {
|
||
|
#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \
|
||
|
_XOPEN_SOURCE >= 600
|
||
|
fseeko((FILE *) fp, (off_t) offset, SEEK_SET);
|
||
|
#else
|
||
|
fseek((FILE *) fp, (long) offset, SEEK_SET);
|
||
|
#endif
|
||
|
return (size_t) ftell((FILE *) fp);
|
||
|
}
|
||
|
#else
|
||
|
static char *p_realpath(const char *path, char *resolved_path) {
|
||
|
(void) path, (void) resolved_path;
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int p_stat(const char *path, size_t *size, time_t *mtime) {
|
||
|
(void) path, (void) size, (void) mtime;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void p_list(const char *path, void (*fn)(const char *, void *),
|
||
|
void *userdata) {
|
||
|
(void) path, (void) fn, (void) userdata;
|
||
|
}
|
||
|
|
||
|
static struct mg_fd *p_open(const char *path, int flags) {
|
||
|
(void) path, (void) flags;
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void p_close(struct mg_fd *fd) {
|
||
|
(void) fd;
|
||
|
}
|
||
|
|
||
|
static size_t p_read(void *fd, void *buf, size_t len) {
|
||
|
(void) fd, (void) buf, (void) len;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static size_t p_write(void *fd, const void *buf, size_t len) {
|
||
|
(void) fd, (void) buf, (void) len;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static size_t p_seek(void *fd, size_t offset) {
|
||
|
(void) fd, (void) offset;
|
||
|
return (size_t) ~0;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close,
|
||
|
p_read, p_write, p_seek};
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/http.c"
|
||
|
#endif
|
||
|
|
||
|
// Multipart POST example:
|
||
|
// --xyz
|
||
|
// Content-Disposition: form-data; name="val"
|
||
|
//
|
||
|
// abcdef
|
||
|
// --xyz
|
||
|
// Content-Disposition: form-data; name="foo"; filename="a.txt"
|
||
|
// Content-Type: text/plain
|
||
|
//
|
||
|
// hello world
|
||
|
//
|
||
|
// --xyz--
|
||
|
size_t mg_http_next_multipart(struct mg_str body, size_t ofs,
|
||
|
struct mg_http_part *part) {
|
||
|
struct mg_str cd = mg_str_n("Content-Disposition", 19);
|
||
|
const char *s = body.ptr;
|
||
|
size_t b = ofs, h1, h2, b1, b2, max = body.len;
|
||
|
|
||
|
// Init part params
|
||
|
if (part != NULL) part->name = part->filename = part->body = mg_str_n(0, 0);
|
||
|
|
||
|
// Skip boundary
|
||
|
while (b + 2 < max && s[b] != '\r' && s[b + 1] != '\n') b++;
|
||
|
if (b <= ofs || b + 2 >= max) return 0;
|
||
|
// LOG(LL_INFO, ("B: %zu %zu [%.*s]", ofs, b - ofs, (int) (b - ofs), s));
|
||
|
|
||
|
// Skip headers
|
||
|
h1 = h2 = b + 2;
|
||
|
for (;;) {
|
||
|
while (h2 + 2 < max && s[h2] != '\r' && s[h2 + 1] != '\n') h2++;
|
||
|
if (h2 == h1) break;
|
||
|
if (h2 + 2 >= max) return 0;
|
||
|
// LOG(LL_INFO, ("Header: [%.*s]", (int) (h2 - h1), &s[h1]));
|
||
|
if (part != NULL && h1 + cd.len + 2 < h2 && s[h1 + cd.len] == ':' &&
|
||
|
mg_ncasecmp(&s[h1], cd.ptr, cd.len) == 0) {
|
||
|
struct mg_str v = mg_str_n(&s[h1 + cd.len + 2], h2 - (h1 + cd.len + 2));
|
||
|
part->name = mg_http_get_header_var(v, mg_str_n("name", 4));
|
||
|
part->filename = mg_http_get_header_var(v, mg_str_n("filename", 8));
|
||
|
}
|
||
|
h1 = h2 = h2 + 2;
|
||
|
}
|
||
|
b1 = b2 = h2 + 2;
|
||
|
while (b2 + 2 + (b - ofs) + 2 < max && !(s[b2] == '\r' && s[b2 + 1] == '\n' &&
|
||
|
memcmp(&s[b2 + 2], s, b - ofs) == 0))
|
||
|
b2++;
|
||
|
|
||
|
if (b2 + 2 >= max) return 0;
|
||
|
if (part != NULL) part->body = mg_str_n(&s[b1], b2 - b1);
|
||
|
// LOG(LL_INFO, ("Body: [%.*s]", (int) (b2 - b1), &s[b1]));
|
||
|
return b2 + 2;
|
||
|
}
|
||
|
|
||
|
int mg_http_get_var(const struct mg_str *buf, const char *name, char *dst,
|
||
|
size_t dst_len) {
|
||
|
const char *p, *e, *s;
|
||
|
size_t name_len;
|
||
|
int len;
|
||
|
|
||
|
if (dst == NULL || dst_len == 0) {
|
||
|
len = -2; // Bad destination
|
||
|
} else if (buf->ptr == NULL || name == NULL || buf->len == 0) {
|
||
|
len = -1; // Bad source
|
||
|
dst[0] = '\0';
|
||
|
} else {
|
||
|
name_len = strlen(name);
|
||
|
e = buf->ptr + buf->len;
|
||
|
len = -4; // Name does not exist
|
||
|
dst[0] = '\0';
|
||
|
for (p = buf->ptr; p + name_len < e; p++) {
|
||
|
if ((p == buf->ptr || p[-1] == '&') && p[name_len] == '=' &&
|
||
|
!mg_ncasecmp(name, p, name_len)) {
|
||
|
p += name_len + 1;
|
||
|
s = (const char *) memchr(p, '&', (size_t) (e - p));
|
||
|
if (s == NULL) s = e;
|
||
|
len = mg_url_decode(p, (size_t) (s - p), dst, dst_len, 1);
|
||
|
if (len < 0) len = -3; // Failed to decode
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
|
||
|
int is_form_url_encoded) {
|
||
|
size_t i, j;
|
||
|
for (i = j = 0; i < src_len && j + 1 < dst_len; i++, j++) {
|
||
|
if (src[i] == '%') {
|
||
|
// Use `i + 2 < src_len`, not `i < src_len - 2`, note small src_len
|
||
|
if (i + 2 < src_len && isxdigit(*(const unsigned char *) (src + i + 1)) &&
|
||
|
isxdigit(*(const unsigned char *) (src + i + 2))) {
|
||
|
mg_unhex(src + i + 1, 2, (uint8_t *) &dst[j]);
|
||
|
i += 2;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
} else if (is_form_url_encoded && src[i] == '+') {
|
||
|
dst[j] = ' ';
|
||
|
} else {
|
||
|
dst[j] = src[i];
|
||
|
}
|
||
|
}
|
||
|
if (j < dst_len) dst[j] = '\0'; // Null-terminate the destination
|
||
|
return i >= src_len && j < dst_len ? (int) j : -1;
|
||
|
}
|
||
|
|
||
|
int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
|
||
|
size_t i;
|
||
|
for (i = 0; i < buf_len; i++) {
|
||
|
if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128)
|
||
|
return -1;
|
||
|
if ((i > 0 && buf[i] == '\n' && buf[i - 1] == '\n') ||
|
||
|
(i > 3 && buf[i] == '\n' && buf[i - 1] == '\r' && buf[i - 2] == '\n'))
|
||
|
return (int) i + 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static const char *skip(const char *s, const char *e, const char *d,
|
||
|
struct mg_str *v) {
|
||
|
v->ptr = s;
|
||
|
while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
|
||
|
v->len = (size_t) (s - v->ptr);
|
||
|
while (s < e && strchr(d, *s) != NULL) s++;
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
|
||
|
size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
|
||
|
for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
|
||
|
struct mg_str *k = &h->headers[i].name, *v = &h->headers[i].value;
|
||
|
if (n == k->len && mg_ncasecmp(k->ptr, name, n) == 0) return v;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void mg_http_parse_headers(const char *s, const char *end,
|
||
|
struct mg_http_header *h, int max_headers) {
|
||
|
int i;
|
||
|
for (i = 0; i < max_headers; i++) {
|
||
|
struct mg_str k, v, tmp;
|
||
|
const char *he = skip(s, end, "\n", &tmp);
|
||
|
s = skip(s, he, ": \r\n", &k);
|
||
|
s = skip(s, he, "\r\n", &v);
|
||
|
if (k.len == tmp.len) continue;
|
||
|
while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces
|
||
|
if (k.len == 0) break;
|
||
|
// LOG(LL_INFO, ("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len - 1, tmp.ptr,
|
||
|
//(int) k.len, k.ptr, (int) v.len, v.ptr));
|
||
|
h[i].name = k;
|
||
|
h[i].value = v;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
|
||
|
int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
|
||
|
const char *end = s + req_len, *qs;
|
||
|
struct mg_str *cl;
|
||
|
|
||
|
memset(hm, 0, sizeof(*hm));
|
||
|
if (req_len <= 0) return req_len;
|
||
|
|
||
|
hm->message.ptr = hm->head.ptr = s;
|
||
|
hm->body.ptr = end;
|
||
|
hm->head.len = (size_t) req_len;
|
||
|
hm->chunk.ptr = end;
|
||
|
hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite
|
||
|
|
||
|
// Parse request line
|
||
|
s = skip(s, end, " ", &hm->method);
|
||
|
s = skip(s, end, " ", &hm->uri);
|
||
|
s = skip(s, end, "\r\n", &hm->proto);
|
||
|
|
||
|
// Sanity check. Allow protocol/reason to be empty
|
||
|
if (hm->method.len == 0 || hm->uri.len == 0) return -1;
|
||
|
|
||
|
// If URI contains '?' character, setup query string
|
||
|
if ((qs = (const char *) memchr(hm->uri.ptr, '?', hm->uri.len)) != NULL) {
|
||
|
hm->query.ptr = qs + 1;
|
||
|
hm->query.len = (size_t) (&hm->uri.ptr[hm->uri.len] - (qs + 1));
|
||
|
hm->uri.len = (size_t) (qs - hm->uri.ptr);
|
||
|
}
|
||
|
|
||
|
mg_http_parse_headers(s, end, hm->headers,
|
||
|
sizeof(hm->headers) / sizeof(hm->headers[0]));
|
||
|
if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
|
||
|
hm->body.len = (size_t) mg_to64(*cl);
|
||
|
hm->message.len = (size_t) req_len + hm->body.len;
|
||
|
}
|
||
|
|
||
|
// mg_http_parse() is used to parse both HTTP requests and HTTP
|
||
|
// responses. If HTTP response does not have Content-Length set, then
|
||
|
// body is read until socket is closed, i.e. body.len is infinite (~0).
|
||
|
//
|
||
|
// For HTTP requests though, according to
|
||
|
// http://tools.ietf.org/html/rfc7231#section-8.1.3,
|
||
|
// only POST and PUT methods have defined body semantics.
|
||
|
// Therefore, if Content-Length is not specified and methods are
|
||
|
// not one of PUT or POST, set body length to 0.
|
||
|
//
|
||
|
// So, if it is HTTP request, and Content-Length is not set,
|
||
|
// and method is not (PUT or POST) then reset body length to zero.
|
||
|
is_response = mg_ncasecmp(hm->method.ptr, "HTTP/", 5) == 0;
|
||
|
if (hm->body.len == (size_t) ~0 && !is_response &&
|
||
|
mg_vcasecmp(&hm->method, "PUT") != 0 &&
|
||
|
mg_vcasecmp(&hm->method, "POST") != 0) {
|
||
|
hm->body.len = 0;
|
||
|
hm->message.len = (size_t) req_len;
|
||
|
}
|
||
|
|
||
|
// The 204 (No content) responses also have 0 body length
|
||
|
if (hm->body.len == (size_t) ~0 && is_response &&
|
||
|
mg_vcasecmp(&hm->uri, "204") == 0) {
|
||
|
hm->body.len = 0;
|
||
|
hm->message.len = (size_t) req_len;
|
||
|
}
|
||
|
|
||
|
return req_len;
|
||
|
}
|
||
|
|
||
|
static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
|
||
|
va_list ap) {
|
||
|
char mem[256], *buf = mem;
|
||
|
int len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
||
|
mg_printf(c, "%X\r\n", len);
|
||
|
mg_send(c, buf, len > 0 ? (size_t) len : 0);
|
||
|
mg_send(c, "\r\n", 2);
|
||
|
if (buf != mem) free(buf);
|
||
|
}
|
||
|
|
||
|
void mg_http_printf_chunk(struct mg_connection *c, const char *fmt, ...) {
|
||
|
va_list ap;
|
||
|
va_start(ap, fmt);
|
||
|
mg_http_vprintf_chunk(c, fmt, ap);
|
||
|
va_end(ap);
|
||
|
}
|
||
|
|
||
|
void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
|
||
|
mg_printf(c, "%lX\r\n", (unsigned long) len);
|
||
|
mg_send(c, buf, len);
|
||
|
mg_send(c, "\r\n", 2);
|
||
|
}
|
||
|
|
||
|
// clang-format off
|
||
|
static const char *mg_http_status_code_str(int status_code) {
|
||
|
switch (status_code) {
|
||
|
case 100: return "Continue";
|
||
|
case 101: return "Switching Protocols";
|
||
|
case 102: return "Processing";
|
||
|
case 200: return "OK";
|
||
|
case 201: return "Created";
|
||
|
case 202: return "Accepted";
|
||
|
case 203: return "Non-authoritative Information";
|
||
|
case 204: return "No Content";
|
||
|
case 205: return "Reset Content";
|
||
|
case 206: return "Partial Content";
|
||
|
case 207: return "Multi-Status";
|
||
|
case 208: return "Already Reported";
|
||
|
case 226: return "IM Used";
|
||
|
case 300: return "Multiple Choices";
|
||
|
case 301: return "Moved Permanently";
|
||
|
case 302: return "Found";
|
||
|
case 303: return "See Other";
|
||
|
case 304: return "Not Modified";
|
||
|
case 305: return "Use Proxy";
|
||
|
case 307: return "Temporary Redirect";
|
||
|
case 308: return "Permanent Redirect";
|
||
|
case 400: return "Bad Request";
|
||
|
case 401: return "Unauthorized";
|
||
|
case 402: return "Payment Required";
|
||
|
case 403: return "Forbidden";
|
||
|
case 404: return "Not Found";
|
||
|
case 405: return "Method Not Allowed";
|
||
|
case 406: return "Not Acceptable";
|
||
|
case 407: return "Proxy Authentication Required";
|
||
|
case 408: return "Request Timeout";
|
||
|
case 409: return "Conflict";
|
||
|
case 410: return "Gone";
|
||
|
case 411: return "Length Required";
|
||
|
case 412: return "Precondition Failed";
|
||
|
case 413: return "Payload Too Large";
|
||
|
case 414: return "Request-URI Too Long";
|
||
|
case 415: return "Unsupported Media Type";
|
||
|
case 416: return "Requested Range Not Satisfiable";
|
||
|
case 417: return "Expectation Failed";
|
||
|
case 418: return "I'm a teapot";
|
||
|
case 421: return "Misdirected Request";
|
||
|
case 422: return "Unprocessable Entity";
|
||
|
case 423: return "Locked";
|
||
|
case 424: return "Failed Dependency";
|
||
|
case 426: return "Upgrade Required";
|
||
|
case 428: return "Precondition Required";
|
||
|
case 429: return "Too Many Requests";
|
||
|
case 431: return "Request Header Fields Too Large";
|
||
|
case 444: return "Connection Closed Without Response";
|
||
|
case 451: return "Unavailable For Legal Reasons";
|
||
|
case 499: return "Client Closed Request";
|
||
|
case 500: return "Internal Server Error";
|
||
|
case 501: return "Not Implemented";
|
||
|
case 502: return "Bad Gateway";
|
||
|
case 503: return "Service Unavailable";
|
||
|
case 504: return "Gateway Timeout";
|
||
|
case 505: return "HTTP Version Not Supported";
|
||
|
case 506: return "Variant Also Negotiates";
|
||
|
case 507: return "Insufficient Storage";
|
||
|
case 508: return "Loop Detected";
|
||
|
case 510: return "Not Extended";
|
||
|
case 511: return "Network Authentication Required";
|
||
|
case 599: return "Network Connect Timeout Error";
|
||
|
default: return "OK";
|
||
|
}
|
||
|
}
|
||
|
// clang-format on
|
||
|
|
||
|
void mg_http_reply(struct mg_connection *c, int code, const char *headers,
|
||
|
const char *fmt, ...) {
|
||
|
char mem[256], *buf = mem;
|
||
|
va_list ap;
|
||
|
int len;
|
||
|
va_start(ap, fmt);
|
||
|
len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
||
|
va_end(ap);
|
||
|
mg_printf(c, "HTTP/1.1 %d %s\r\n%sContent-Length: %d\r\n\r\n", code,
|
||
|
mg_http_status_code_str(code), headers == NULL ? "" : headers, len);
|
||
|
mg_send(c, buf, len > 0 ? (size_t) len : 0);
|
||
|
if (buf != mem) free(buf);
|
||
|
}
|
||
|
|
||
|
static void http_cb(struct mg_connection *, int, void *, void *);
|
||
|
static void restore_http_cb(struct mg_connection *c) {
|
||
|
struct mg_fd *fd = (struct mg_fd *) c->pfn_data;
|
||
|
if (fd != NULL) fd->fs->close(fd);
|
||
|
c->pfn_data = NULL;
|
||
|
c->pfn = http_cb;
|
||
|
}
|
||
|
|
||
|
char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime) {
|
||
|
snprintf(buf, len, "\"%lx." MG_INT64_FMT "\"", (unsigned long) mtime,
|
||
|
(int64_t) size);
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
int mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
|
||
|
const char *dir) {
|
||
|
char offset[40] = "", name[200] = "", path[256];
|
||
|
mg_http_get_var(&hm->query, "offset", offset, sizeof(offset));
|
||
|
mg_http_get_var(&hm->query, "name", name, sizeof(name));
|
||
|
if (name[0] == '\0') {
|
||
|
mg_http_reply(c, 400, "", "%s", "name required");
|
||
|
return -1;
|
||
|
} else {
|
||
|
FILE *fp;
|
||
|
size_t oft = strtoul(offset, NULL, 0);
|
||
|
snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, name);
|
||
|
LOG(LL_DEBUG,
|
||
|
("%p %d bytes @ %d [%s]", c->fd, (int) hm->body.len, (int) oft, name));
|
||
|
if ((fp = fopen(path, oft == 0 ? "wb" : "ab")) == NULL) {
|
||
|
mg_http_reply(c, 400, "", "fopen(%s): %d", name, errno);
|
||
|
return -2;
|
||
|
} else {
|
||
|
fwrite(hm->body.ptr, 1, hm->body.len, fp);
|
||
|
fclose(fp);
|
||
|
mg_http_reply(c, 200, "", "");
|
||
|
return (int) hm->body.len;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void static_cb(struct mg_connection *c, int ev, void *ev_data,
|
||
|
void *fn_data) {
|
||
|
if (ev == MG_EV_WRITE || ev == MG_EV_POLL) {
|
||
|
struct mg_fd *fd = (struct mg_fd *) fn_data;
|
||
|
// Read to send IO buffer directly, avoid extra on-stack buffer
|
||
|
size_t n, max = 2 * MG_IO_SIZE;
|
||
|
if (c->send.size < max) mg_iobuf_resize(&c->send, max);
|
||
|
if (c->send.len >= c->send.size) return; // Rate limit
|
||
|
n = fd->fs->read(fd->fd, c->send.buf + c->send.len,
|
||
|
c->send.size - c->send.len);
|
||
|
if (n > 0) c->send.len += n;
|
||
|
if (c->send.len < c->send.size) restore_http_cb(c);
|
||
|
} else if (ev == MG_EV_CLOSE) {
|
||
|
restore_http_cb(c);
|
||
|
}
|
||
|
(void) ev_data;
|
||
|
}
|
||
|
|
||
|
static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
|
||
|
// clang-format off
|
||
|
struct mimeentry { struct mg_str extension, value; };
|
||
|
#define MIME_ENTRY(a, b) {{a, sizeof(a) - 1 }, { b, sizeof(b) - 1 }}
|
||
|
// clang-format on
|
||
|
const struct mimeentry tab[] = {
|
||
|
MIME_ENTRY("html", "text/html; charset=utf-8"),
|
||
|
MIME_ENTRY("htm", "text/html; charset=utf-8"),
|
||
|
MIME_ENTRY("css", "text/css; charset=utf-8"),
|
||
|
MIME_ENTRY("js", "text/javascript; charset=utf-8"),
|
||
|
MIME_ENTRY("gif", "image/gif"),
|
||
|
MIME_ENTRY("png", "image/png"),
|
||
|
MIME_ENTRY("woff", "font/woff"),
|
||
|
MIME_ENTRY("ttf", "font/ttf"),
|
||
|
MIME_ENTRY("aac", "audio/aac"),
|
||
|
MIME_ENTRY("avi", "video/x-msvideo"),
|
||
|
MIME_ENTRY("azw", "application/vnd.amazon.ebook"),
|
||
|
MIME_ENTRY("bin", "application/octet-stream"),
|
||
|
MIME_ENTRY("bmp", "image/bmp"),
|
||
|
MIME_ENTRY("bz", "application/x-bzip"),
|
||
|
MIME_ENTRY("bz2", "application/x-bzip2"),
|
||
|
MIME_ENTRY("csv", "text/csv"),
|
||
|
MIME_ENTRY("doc", "application/msword"),
|
||
|
MIME_ENTRY("epub", "application/epub+zip"),
|
||
|
MIME_ENTRY("exe", "application/octet-stream"),
|
||
|
MIME_ENTRY("gz", "application/gzip"),
|
||
|
MIME_ENTRY("ico", "image/x-icon"),
|
||
|
MIME_ENTRY("json", "application/json"),
|
||
|
MIME_ENTRY("mid", "audio/mid"),
|
||
|
MIME_ENTRY("mjs", "text/javascript"),
|
||
|
MIME_ENTRY("mov", "video/quicktime"),
|
||
|
MIME_ENTRY("mp3", "audio/mpeg"),
|
||
|
MIME_ENTRY("mp4", "video/mp4"),
|
||
|
MIME_ENTRY("mpeg", "video/mpeg"),
|
||
|
MIME_ENTRY("mpg", "video/mpeg"),
|
||
|
MIME_ENTRY("ogg", "application/ogg"),
|
||
|
MIME_ENTRY("pdf", "application/pdf"),
|
||
|
MIME_ENTRY("rar", "application/rar"),
|
||
|
MIME_ENTRY("rtf", "application/rtf"),
|
||
|
MIME_ENTRY("shtml", "text/html; charset=utf-8"),
|
||
|
MIME_ENTRY("svg", "image/svg+xml"),
|
||
|
MIME_ENTRY("tar", "application/tar"),
|
||
|
MIME_ENTRY("tgz", "application/tar-gz"),
|
||
|
MIME_ENTRY("txt", "text/plain; charset=utf-8"),
|
||
|
MIME_ENTRY("wasm", "application/wasm"),
|
||
|
MIME_ENTRY("wav", "audio/wav"),
|
||
|
MIME_ENTRY("weba", "audio/webm"),
|
||
|
MIME_ENTRY("webm", "video/webm"),
|
||
|
MIME_ENTRY("webp", "image/webp"),
|
||
|
MIME_ENTRY("xls", "application/excel"),
|
||
|
MIME_ENTRY("xml", "application/xml"),
|
||
|
MIME_ENTRY("xsl", "application/xml"),
|
||
|
MIME_ENTRY("zip", "application/zip"),
|
||
|
MIME_ENTRY("3gp", "video/3gpp"),
|
||
|
MIME_ENTRY("7z", "application/x-7z-compressed"),
|
||
|
MIME_ENTRY("7z", "application/x-7z-compressed"),
|
||
|
{{0, 0}, {0, 0}},
|
||
|
};
|
||
|
size_t i = 0;
|
||
|
struct mg_str k, v, s = mg_str(extra);
|
||
|
|
||
|
// Shrink path to its extension only
|
||
|
while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
|
||
|
path.ptr += path.len - i;
|
||
|
path.len = i;
|
||
|
|
||
|
// Process user-provided mime type overrides, if any
|
||
|
while (mg_commalist(&s, &k, &v)) {
|
||
|
if (mg_strcmp(path, k) == 0) return v;
|
||
|
}
|
||
|
|
||
|
// Process built-in mime types
|
||
|
for (i = 0; tab[i].extension.ptr != NULL; i++) {
|
||
|
if (mg_strcmp(path, tab[i].extension) == 0) return tab[i].value;
|
||
|
}
|
||
|
|
||
|
return mg_str("text/plain; charset=utf-8");
|
||
|
}
|
||
|
|
||
|
static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
|
||
|
size_t i, numparsed = 0;
|
||
|
LOG(LL_INFO, ("%.*s", (int) s->len, s->ptr));
|
||
|
for (i = 0; i + 6 < s->len; i++) {
|
||
|
if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
|
||
|
struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
|
||
|
if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
|
||
|
*a = mg_to64(p);
|
||
|
// LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
|
||
|
while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--;
|
||
|
if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
|
||
|
*b = mg_to64(p);
|
||
|
if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
|
||
|
// LOG(LL_INFO, ("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return (int) numparsed;
|
||
|
}
|
||
|
|
||
|
void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
|
||
|
const char *path, struct mg_http_serve_opts *opts) {
|
||
|
char etag[64];
|
||
|
struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
|
||
|
struct mg_fd *fd = fs->open(path, MG_FS_READ);
|
||
|
size_t size = 0;
|
||
|
time_t mtime = 0;
|
||
|
struct mg_str *inm = NULL;
|
||
|
|
||
|
if (fd == NULL || fs->stat(path, &size, &mtime) == 0) {
|
||
|
LOG(LL_DEBUG, ("404 [%s] %p", path, (void *) fd));
|
||
|
mg_http_reply(c, 404, "", "%s", "Not found\n");
|
||
|
fs->close(fd);
|
||
|
// NOTE: mg_http_etag() call should go first!
|
||
|
} else if (mg_http_etag(etag, sizeof(etag), size, mtime) != NULL &&
|
||
|
(inm = mg_http_get_header(hm, "If-None-Match")) != NULL &&
|
||
|
mg_vcasecmp(inm, etag) == 0) {
|
||
|
fs->close(fd);
|
||
|
mg_printf(c, "HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n");
|
||
|
} else {
|
||
|
int n, status = 200;
|
||
|
char range[100] = "";
|
||
|
int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
|
||
|
struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types);
|
||
|
|
||
|
// Handle Range header
|
||
|
struct mg_str *rh = mg_http_get_header(hm, "Range");
|
||
|
if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) {
|
||
|
// If range is specified like "400-", set second limit to content len
|
||
|
if (n == 1) r2 = cl - 1;
|
||
|
if (r1 > r2 || r2 >= cl) {
|
||
|
status = 416;
|
||
|
cl = 0;
|
||
|
snprintf(range, sizeof(range),
|
||
|
"Content-Range: bytes */" MG_INT64_FMT "\r\n", (int64_t) size);
|
||
|
} else {
|
||
|
status = 206;
|
||
|
cl = r2 - r1 + 1;
|
||
|
snprintf(range, sizeof(range),
|
||
|
"Content-Range: bytes " MG_INT64_FMT "-" MG_INT64_FMT
|
||
|
"/" MG_INT64_FMT "\r\n",
|
||
|
r1, r1 + cl - 1, (int64_t) size);
|
||
|
fs->seek(fd->fd, (size_t) r1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mg_printf(c,
|
||
|
"HTTP/1.1 %d %s\r\nContent-Type: %.*s\r\n"
|
||
|
"Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n",
|
||
|
status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
|
||
|
etag, cl, range, opts->extra_headers ? opts->extra_headers : "");
|
||
|
if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
|
||
|
c->is_draining = 1;
|
||
|
fs->close(fd);
|
||
|
} else {
|
||
|
c->pfn = static_cb;
|
||
|
c->pfn_data = fd;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct printdirentrydata {
|
||
|
struct mg_connection *c;
|
||
|
struct mg_http_message *hm;
|
||
|
struct mg_http_serve_opts *opts;
|
||
|
const char *dir;
|
||
|
};
|
||
|
|
||
|
static void printdirentry(const char *name, void *userdata) {
|
||
|
struct printdirentrydata *d = (struct printdirentrydata *) userdata;
|
||
|
struct mg_fs *fs = d->opts->fs == NULL ? &mg_fs_posix : d->opts->fs;
|
||
|
size_t size = 0;
|
||
|
time_t mtime = 0;
|
||
|
char path[MG_PATH_MAX], sz[64], mod[64];
|
||
|
int flags, n = 0;
|
||
|
|
||
|
// LOG(LL_DEBUG, ("[%s] [%s]", d->dir, name));
|
||
|
if (snprintf(path, sizeof(path), "%s%c%s", d->dir, '/', name) < 0) {
|
||
|
LOG(LL_ERROR, ("%s truncated", name));
|
||
|
} else if ((flags = fs->stat(path, &size, &mtime)) == 0) {
|
||
|
LOG(LL_ERROR, ("%lu stat(%s): %d", d->c->id, path, errno));
|
||
|
} else {
|
||
|
const char *slash = flags & MG_FS_DIR ? "/" : "";
|
||
|
struct tm t;
|
||
|
if (flags & MG_FS_DIR) {
|
||
|
snprintf(sz, sizeof(sz), "%s", "[DIR]");
|
||
|
} else if (size < 1024) {
|
||
|
snprintf(sz, sizeof(sz), "%d", (int) size);
|
||
|
} else if (size < 0x100000) {
|
||
|
snprintf(sz, sizeof(sz), "%.1fk", (double) size / 1024.0);
|
||
|
} else if (size < 0x40000000) {
|
||
|
snprintf(sz, sizeof(sz), "%.1fM", (double) size / 1048576);
|
||
|
} else {
|
||
|
snprintf(sz, sizeof(sz), "%.1fG", (double) size / 1073741824);
|
||
|
}
|
||
|
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime_r(&mtime, &t));
|
||
|
n = (int) mg_url_encode(name, strlen(name), path, sizeof(path));
|
||
|
mg_printf(d->c,
|
||
|
" <tr><td><a href=\"%.*s%s\">%s%s</a></td>"
|
||
|
"<td>%s</td><td>%s</td></tr>\n",
|
||
|
n, path, slash, name, slash, mod, sz);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void listdir(struct mg_connection *c, struct mg_http_message *hm,
|
||
|
struct mg_http_serve_opts *opts, char *dir) {
|
||
|
static const char *sort_js_code =
|
||
|
"<script>function srt(tb, sc, so, d) {"
|
||
|
"var tr = Array.prototype.slice.call(tb.rows, 0),"
|
||
|
"tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
|
||
|
"n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
|
||
|
"t1 = a.cells[2].getAttribute('name'), "
|
||
|
"t2 = b.cells[2].getAttribute('name'); "
|
||
|
"return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
|
||
|
"n1 ? parseInt(n2) - parseInt(n1) : "
|
||
|
"c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
|
||
|
static const char *sort_js_code2 =
|
||
|
"for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
|
||
|
"if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
|
||
|
"};"
|
||
|
"window.onload = function() {"
|
||
|
"var tb = document.getElementById('tb');"
|
||
|
"var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
|
||
|
"var sc = m[1], so = m[2]; document.onclick = function(ev) { "
|
||
|
"var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
|
||
|
"sc = c; ev.preventDefault();}};"
|
||
|
"srt(tb, sc, so, true);"
|
||
|
"}"
|
||
|
"</script>";
|
||
|
struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
|
||
|
struct printdirentrydata d = {c, hm, opts, dir};
|
||
|
char tmp[10];
|
||
|
size_t off, n;
|
||
|
|
||
|
mg_printf(c,
|
||
|
"HTTP/1.1 200 OK\r\n"
|
||
|
"Content-Type: text/html; charset=utf-8\r\n"
|
||
|
"%s"
|
||
|
"Content-Length: \r\n\r\n",
|
||
|
opts->extra_headers == NULL ? "" : opts->extra_headers);
|
||
|
off = c->send.len; // Start of body
|
||
|
mg_printf(c,
|
||
|
"<!DOCTYPE html><html><head><title>Index of %.*s</title>%s%s"
|
||
|
"<style>th,td {text-align: left; padding-right: 1em; "
|
||
|
"font-family: monospace; }</style></head>"
|
||
|
"<body><h1>Index of %.*s</h1><table cellpadding=\"0\"><thead>"
|
||
|
"<tr><th><a href=\"#\" rel=\"0\">Name</a></th><th>"
|
||
|
"<a href=\"#\" rel=\"1\">Modified</a></th>"
|
||
|
"<th><a href=\"#\" rel=\"2\">Size</a></th></tr>"
|
||
|
"<tr><td colspan=\"3\"><hr></td></tr>"
|
||
|
"</thead>"
|
||
|
"<tbody id=\"tb\">\n",
|
||
|
(int) hm->uri.len, hm->uri.ptr, sort_js_code, sort_js_code2,
|
||
|
(int) hm->uri.len, hm->uri.ptr);
|
||
|
|
||
|
fs->list(dir, printdirentry, &d);
|
||
|
mg_printf(c,
|
||
|
"</tbody><tfoot><tr><td colspan=\"3\"><hr></td></tr></tfoot>"
|
||
|
"</table><address>Mongoose v.%s</address></body></html>\n",
|
||
|
MG_VERSION);
|
||
|
n = (size_t) snprintf(tmp, sizeof(tmp), "%lu",
|
||
|
(unsigned long) (c->send.len - off));
|
||
|
if (n > sizeof(tmp)) n = 0;
|
||
|
memcpy(c->send.buf + off - 10, tmp, n); // Set content length
|
||
|
}
|
||
|
|
||
|
static void remove_double_dots(char *s) {
|
||
|
char *p = s;
|
||
|
while (*s != '\0') {
|
||
|
*p++ = *s++;
|
||
|
if (s[-1] == '/' || s[-1] == '\\') {
|
||
|
while (s[0] != '\0') {
|
||
|
if (s[0] == '/' || s[0] == '\\') {
|
||
|
s++;
|
||
|
} else if (s[0] == '.' && s[1] == '.') {
|
||
|
s += 2;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
*p = '\0';
|
||
|
}
|
||
|
|
||
|
// Resolve requested file into `path` and return its fs->stat() result
|
||
|
static int uri_to_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;
|
||
|
int flags = 0, tmp;
|
||
|
// Append URI to the root_dir, and sanitize it
|
||
|
size_t n = (size_t) snprintf(path, path_size, "%s", opts->root_dir);
|
||
|
if (n > path_size) n = path_size;
|
||
|
mg_url_decode(hm->uri.ptr, hm->uri.len, path + n, path_size - n, 0);
|
||
|
path[path_size - 1] = '\0'; // Double-check
|
||
|
remove_double_dots(path);
|
||
|
n = strlen(path);
|
||
|
while (n > 0 && path[n - 1] == '/') path[--n] = 0; // Strip 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;
|
||
|
}
|
||
|
|
||
|
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] = "";
|
||
|
struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
|
||
|
if ((fs->stat(opts->root_dir, NULL, NULL) & MG_FS_DIR) == 0) {
|
||
|
mg_http_reply(c, 400, "", "Invalid web root [%s]\n", opts->root_dir);
|
||
|
} else {
|
||
|
int flags = uri_to_path(c, hm, opts, path, sizeof(path));
|
||
|
if (flags == 0) return;
|
||
|
LOG(LL_DEBUG, ("%.*s %s %d", (int) hm->uri.len, hm->uri.ptr, path, flags));
|
||
|
if (flags & MG_FS_DIR) {
|
||
|
listdir(c, hm, opts, path);
|
||
|
} else {
|
||
|
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 <string.h>
|
||
|
|
||
|
// Not using memset for zeroing memory, cause it can be dropped by compiler
|
||
|
// See https://github.com/cesanta/mongoose/pull/1265
|
||
|
static void zeromem(volatile unsigned char *buf, size_t len) {
|
||
|
if (buf != NULL) {
|
||
|
while (len--) *buf++ = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int mg_iobuf_resize(struct mg_iobuf *io, size_t new_size) {
|
||
|
int ok = 1;
|
||
|
if (new_size == 0) {
|
||
|
zeromem(io->buf, io->size);
|
||
|
free(io->buf);
|
||
|
io->buf = NULL;
|
||
|
io->len = io->size = 0;
|
||
|
} else if (new_size != io->size) {
|
||
|
// NOTE(lsm): do not use realloc here. Use calloc/free only, to ease the
|
||
|
// porting to some obscure platforms like FreeRTOS
|
||
|
void *p = calloc(1, new_size);
|
||
|
if (p != NULL) {
|
||
|
size_t len = new_size < io->len ? new_size : io->len;
|
||
|
if (len > 0) memcpy(p, io->buf, len);
|
||
|
zeromem(io->buf, io->size);
|
||
|
free(io->buf);
|
||
|
io->buf = (unsigned char *) p;
|
||
|
io->size = new_size;
|
||
|
} else {
|
||
|
ok = 0;
|
||
|
LOG(LL_ERROR,
|
||
|
("%lu->%lu", (unsigned long) io->size, (unsigned long) new_size));
|
||
|
}
|
||
|
}
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
int mg_iobuf_init(struct mg_iobuf *io, size_t size) {
|
||
|
return mg_iobuf_resize(io, size);
|
||
|
}
|
||
|
|
||
|
size_t mg_iobuf_add(struct mg_iobuf *io, size_t ofs, const void *buf,
|
||
|
size_t len, size_t chunk_size) {
|
||
|
size_t new_size = io->len + len;
|
||
|
if (new_size > io->size) {
|
||
|
new_size += chunk_size; // Make sure that io->size
|
||
|
new_size -= new_size % chunk_size; // is aligned by chunk_size boundary
|
||
|
mg_iobuf_resize(io, new_size); // Attempt to realloc
|
||
|
if (new_size != io->size) len = 0; // Realloc failure, append nothing
|
||
|
}
|
||
|
if (ofs < io->len) memmove(io->buf + ofs + len, io->buf + ofs, io->len - ofs);
|
||
|
if (buf != NULL) memmove(io->buf + ofs, buf, len);
|
||
|
if (ofs > io->len) io->len += ofs - io->len;
|
||
|
io->len += len;
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
size_t mg_iobuf_del(struct mg_iobuf *io, size_t ofs, size_t len) {
|
||
|
if (ofs > io->len) ofs = io->len;
|
||
|
if (ofs + len > io->len) len = io->len - ofs;
|
||
|
memmove(io->buf + ofs, io->buf + ofs + len, io->len - ofs - len);
|
||
|
zeromem(io->buf + io->len - len, len);
|
||
|
io->len -= len;
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
void mg_iobuf_free(struct mg_iobuf *io) {
|
||
|
mg_iobuf_resize(io, 0);
|
||
|
}
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/log.c"
|
||
|
#endif
|
||
|
|
||
|
#if MG_ENABLE_LOG
|
||
|
static void mg_log_stdout(const void *buf, size_t len, void *userdata) {
|
||
|
(void) userdata;
|
||
|
fwrite(buf, 1, len, stdout);
|
||
|
}
|
||
|
|
||
|
static const char *s_spec = "2";
|
||
|
static void (*s_fn)(const void *, size_t, void *) = mg_log_stdout;
|
||
|
static void *s_fn_param = NULL;
|
||
|
|
||
|
void mg_log_set(const char *spec) {
|
||
|
LOG(LL_DEBUG, ("Setting log level to %s", spec));
|
||
|
s_spec = spec;
|
||
|
}
|
||
|
|
||
|
bool mg_log_prefix(int level, const char *file, int line, const char *fname) {
|
||
|
// static unsigned long seq;
|
||
|
int max = LL_INFO;
|
||
|
struct mg_str k, v, s = mg_str(s_spec);
|
||
|
const char *p = strrchr(file, '/');
|
||
|
|
||
|
if (s_fn == NULL) return false;
|
||
|
|
||
|
if (p == NULL) p = strrchr(file, '\\');
|
||
|
p = p == NULL ? file : p + 1;
|
||
|
|
||
|
while (mg_commalist(&s, &k, &v)) {
|
||
|
if (v.len == 0) max = atoi(k.ptr);
|
||
|
if (v.len > 0 && strncmp(p, k.ptr, k.len) == 0) max = atoi(v.ptr);
|
||
|
}
|
||
|
|
||
|
if (level <= max) {
|
||
|
char timebuf[21], buf[50] = "";
|
||
|
time_t t = time(NULL);
|
||
|
struct tm tmp, *tm = gmtime_r(&t, &tmp);
|
||
|
int n;
|
||
|
(void) tmp;
|
||
|
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm);
|
||
|
n = snprintf(buf, sizeof(buf), "%s %d %s:%d:%s", timebuf, level, p, line,
|
||
|
fname);
|
||
|
if (n < 0 || n > (int) sizeof(buf) - 2) n = sizeof(buf) - 2;
|
||
|
while (n < (int) sizeof(buf) - 1) buf[n++] = ' ';
|
||
|
s_fn(buf, sizeof(buf) - 1, s_fn_param);
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mg_log(const char *fmt, ...) {
|
||
|
char mem[256], *buf = mem;
|
||
|
va_list ap;
|
||
|
int len = 0;
|
||
|
va_start(ap, fmt);
|
||
|
len = mg_vasprintf(&buf, sizeof(mem), fmt, ap);
|
||
|
va_end(ap);
|
||
|
s_fn(buf, len > 0 ? (size_t) len : 0, s_fn_param);
|
||
|
s_fn("\n", 1, s_fn_param);
|
||
|
if (buf != mem) free(buf);
|
||
|
}
|
||
|
|
||
|
void mg_log_set_callback(void (*fn)(const void *, size_t, void *), void *fnd) {
|
||
|
s_fn = fn;
|
||
|
s_fn_param = fnd;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/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 (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;
|
||
|
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) {
|
||
|
closesocket(fd);
|
||
|
fd = INVALID_SOCKET;
|
||
|
}
|
||
|
}
|
||
|
if (fd == INVALID_SOCKET) {
|
||
|
LOG(LL_ERROR, ("Failed to listen on %s, errno %d", url, MG_SOCK_ERRNO));
|
||
|
}
|
||
|
|
||
|
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 <stdlib.h>
|
||
|
|
||
|
struct mg_str mg_str_s(const char *s) {
|
||
|
struct mg_str str = {s, s == NULL ? 0 : strlen(s)};
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
struct mg_str mg_str_n(const char *s, size_t n) {
|
||
|
struct mg_str str = {s, n};
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
int mg_lower(const char *s) {
|
||
|
return tolower(*(const unsigned char *) s);
|
||
|
}
|
||
|
|
||
|
int mg_ncasecmp(const char *s1, const char *s2, size_t len) {
|
||
|
int diff = 0;
|
||
|
if (len > 0) do {
|
||
|
diff = mg_lower(s1++) - mg_lower(s2++);
|
||
|
} while (diff == 0 && s1[-1] != '\0' && --len > 0);
|
||
|
return diff;
|
||
|
}
|
||
|
|
||
|
int mg_casecmp(const char *s1, const char *s2) {
|
||
|
return mg_ncasecmp(s1, s2, (size_t) ~0);
|
||
|
}
|
||
|
|
||
|
int mg_vcmp(const struct mg_str *s1, const char *s2) {
|
||
|
size_t n2 = strlen(s2), n1 = s1->len;
|
||
|
int r = strncmp(s1->ptr, s2, (n1 < n2) ? n1 : n2);
|
||
|
if (r == 0) return (int) (n1 - n2);
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
int mg_vcasecmp(const struct mg_str *str1, const char *str2) {
|
||
|
size_t n2 = strlen(str2), n1 = str1->len;
|
||
|
int r = mg_ncasecmp(str1->ptr, str2, (n1 < n2) ? n1 : n2);
|
||
|
if (r == 0) return (int) (n1 - n2);
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
struct mg_str mg_strdup(const struct mg_str s) {
|
||
|
struct mg_str r = {NULL, 0};
|
||
|
if (s.len > 0 && s.ptr != NULL) {
|
||
|
char *sc = (char *) calloc(1, s.len + 1);
|
||
|
if (sc != NULL) {
|
||
|
memcpy(sc, s.ptr, s.len);
|
||
|
sc[s.len] = '\0';
|
||
|
r.ptr = sc;
|
||
|
r.len = s.len;
|
||
|
}
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
|
||
|
size_t i = 0;
|
||
|
while (i < str1.len && i < str2.len) {
|
||
|
int c1 = str1.ptr[i];
|
||
|
int c2 = str2.ptr[i];
|
||
|
if (c1 < c2) return -1;
|
||
|
if (c1 > c2) return 1;
|
||
|
i++;
|
||
|
}
|
||
|
if (i < str1.len) return 1;
|
||
|
if (i < str2.len) return -1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
const char *mg_strstr(const struct mg_str haystack,
|
||
|
const struct mg_str needle) {
|
||
|
size_t i;
|
||
|
if (needle.len > haystack.len) return NULL;
|
||
|
for (i = 0; i <= haystack.len - needle.len; i++) {
|
||
|
if (memcmp(haystack.ptr + i, needle.ptr, needle.len) == 0) {
|
||
|
return haystack.ptr + i;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
struct mg_str mg_strstrip(struct mg_str s) {
|
||
|
while (s.len > 0 && isspace((int) *s.ptr)) s.ptr++, s.len--;
|
||
|
while (s.len > 0 && isspace((int) *(s.ptr + s.len - 1))) s.len--;
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/timer.c"
|
||
|
#endif
|
||
|
// Copyright (c) Cesanta Software Limited
|
||
|
// All rights reserved
|
||
|
|
||
|
struct mg_timer *g_timers;
|
||
|
|
||
|
void mg_timer_init(struct mg_timer *t, unsigned long ms, unsigned flags,
|
||
|
void (*fn)(void *), void *arg) {
|
||
|
struct mg_timer tmp = {ms, flags, fn, arg, 0UL, g_timers};
|
||
|
*t = tmp;
|
||
|
g_timers = t;
|
||
|
if (flags & MG_TIMER_RUN_NOW) fn(arg);
|
||
|
}
|
||
|
|
||
|
void mg_timer_free(struct mg_timer *t) {
|
||
|
struct mg_timer **head = &g_timers;
|
||
|
while (*head && *head != t) head = &(*head)->next;
|
||
|
if (*head) *head = t->next;
|
||
|
}
|
||
|
|
||
|
void mg_timer_poll(unsigned long now_ms) {
|
||
|
// If time goes back (wrapped around), reset timers
|
||
|
struct mg_timer *t, *tmp;
|
||
|
static unsigned long oldnow; // Timestamp in a previous invocation
|
||
|
if (oldnow > now_ms) { // If it is wrapped, reset timers
|
||
|
for (t = g_timers; t != NULL; t = t->next) t->expire = 0;
|
||
|
}
|
||
|
oldnow = now_ms;
|
||
|
|
||
|
for (t = g_timers; t != NULL; t = tmp) {
|
||
|
tmp = t->next;
|
||
|
if (t->expire == 0) t->expire = now_ms + t->period_ms;
|
||
|
if (t->expire > now_ms) continue;
|
||
|
t->fn(t->arg);
|
||
|
// Try to tick timers with the given period as accurate as possible,
|
||
|
// even if this polling function is called with some random period.
|
||
|
t->expire = now_ms - t->expire > (unsigned long) t->period_ms
|
||
|
? now_ms + t->period_ms
|
||
|
: t->expire + t->period_ms;
|
||
|
if (!(t->flags & MG_TIMER_REPEAT)) mg_timer_free(t);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef MG_ENABLE_LINES
|
||
|
#line 1 "src/url.c"
|
||
|
#endif
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
struct url {
|
||
|
size_t key, user, pass, host, port, uri, end;
|
||
|
};
|
||
|
|
||
|
int mg_url_is_ssl(const char *url) {
|
||
|
return strncmp(url, "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);
|
||
|
if (s.len > 2 && s.ptr[0] == '[' && s.ptr[s.len - 1] == ']') {
|
||
|
s.len -= 2;
|
||
|
s.ptr++;
|
||
|
}
|
||
|
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) port = 80;
|
||
|
if (memcmp(url, "https:", 6) == 0) port = 443;
|
||
|
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
|
||
|
}
|
||
|
|