diff --git a/bin/bootstrap.nut b/bin/bootstrap.nut
index dfe80e8d..91460725 100644
--- a/bin/bootstrap.nut
+++ b/bin/bootstrap.nut
@@ -1 +1 @@
-print("Hello World");
\ No newline at end of file
+SqRoutine.Create(this, print, 100, 3, "HELLO WORLD");
\ No newline at end of file
diff --git a/cbp/ModJSON.cbp b/cbp/ModJSON.cbp
new file mode 100644
index 00000000..f4900ac4
--- /dev/null
+++ b/cbp/ModJSON.cbp
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cbp/ModMongoose.cbp b/cbp/ModMongoose.cbp
new file mode 100644
index 00000000..d0ef09f8
--- /dev/null
+++ b/cbp/ModMongoose.cbp
@@ -0,0 +1,443 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cbp/ModMySQL.cbp b/cbp/ModMySQL.cbp
new file mode 100644
index 00000000..05157dc2
--- /dev/null
+++ b/cbp/ModMySQL.cbp
@@ -0,0 +1,386 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cbp/default.workspace b/cbp/default.workspace
index aab317a2..07ffcdda 100644
--- a/cbp/default.workspace
+++ b/cbp/default.workspace
@@ -7,6 +7,7 @@
+
diff --git a/external/Mongoose/LICENSE b/external/Mongoose/LICENSE
new file mode 100644
index 00000000..9d5778c9
--- /dev/null
+++ b/external/Mongoose/LICENSE
@@ -0,0 +1,16 @@
+Copyright (c) 2004-2013 Sergey Lyubka
+Copyright (c) 2013-2016 Cesanta Software Limited
+All rights reserved
+
+This software is dual-licensed: you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2 as
+published by the Free Software Foundation. For the terms of this
+license, see .
+
+You are free to use this software under the terms of the GNU General
+Public License, but WITHOUT ANY WARRANTY; without even the implied
+warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+Alternatively, you can license this software under a commercial
+license, as set out in .
diff --git a/external/Mongoose/mongoose.c b/external/Mongoose/mongoose.c
new file mode 100644
index 00000000..bf9567eb
--- /dev/null
+++ b/external/Mongoose/mongoose.c
@@ -0,0 +1,11074 @@
+#include "mongoose.h"
+#ifdef MG_MODULE_LINES
+#line 1 "./src/internal.h"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_MONGOOSE_SRC_INTERNAL_H_
+#define CS_MONGOOSE_SRC_INTERNAL_H_
+
+#ifndef MG_MALLOC
+#define MG_MALLOC malloc
+#endif
+
+#ifndef MG_CALLOC
+#define MG_CALLOC calloc
+#endif
+
+#ifndef MG_REALLOC
+#define MG_REALLOC realloc
+#endif
+
+#ifndef MG_FREE
+#define MG_FREE free
+#endif
+
+#ifndef MBUF_REALLOC
+#define MBUF_REALLOC MG_REALLOC
+#endif
+
+#ifndef MBUF_FREE
+#define MBUF_FREE MG_FREE
+#endif
+
+#define MG_SET_PTRPTR(_ptr, _v) \
+ do { \
+ if (_ptr) *(_ptr) = _v; \
+ } while (0)
+
+#ifndef MG_INTERNAL
+#define MG_INTERNAL static
+#endif
+
+#ifdef PICOTCP
+#define NO_LIBC
+#define MG_DISABLE_FILESYSTEM
+#define MG_DISABLE_POPEN
+#define MG_DISABLE_CGI
+#define MG_DISABLE_DIRECTORY_LISTING
+#define MG_DISABLE_SOCKETPAIR
+#define MG_DISABLE_PFS
+#endif
+
+/* Amalgamated: #include "mongoose/src/net.h" */
+/* Amalgamated: #include "mongoose/src/http.h" */
+
+#define MG_CTL_MSG_MESSAGE_SIZE 8192
+
+/* internals that need to be accessible in unit tests */
+MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
+ int proto,
+ union socket_address *sa);
+
+MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,
+ int *proto, char *host, size_t host_len);
+MG_INTERNAL void mg_call(struct mg_connection *nc,
+ mg_event_handler_t ev_handler, int ev, void *ev_data);
+void mg_forward(struct mg_connection *from, struct mg_connection *to);
+MG_INTERNAL void mg_add_conn(struct mg_mgr *mgr, struct mg_connection *c);
+MG_INTERNAL void mg_remove_conn(struct mg_connection *c);
+MG_INTERNAL struct mg_connection *mg_create_connection(
+ struct mg_mgr *mgr, mg_event_handler_t callback,
+ struct mg_add_sock_opts opts);
+#ifndef MG_DISABLE_FILESYSTEM
+MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
+ const struct mg_serve_http_opts *opts,
+ char **local_path,
+ struct mg_str *remainder);
+#endif
+#ifdef _WIN32
+/* Retur value is the same as for MultiByteToWideChar. */
+int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len);
+#endif
+
+/*
+ * Reassemble the content of the buffer (buf, blen) which should be
+ * in the HTTP chunked encoding, by collapsing data chunks to the
+ * beginning of the buffer.
+ *
+ * If chunks get reassembled, modify hm->body to point to the reassembled
+ * body and fire MG_EV_HTTP_CHUNK event. If handler sets MG_F_DELETE_CHUNK
+ * in nc->flags, delete reassembled body from the mbuf.
+ *
+ * Return reassembled body size.
+ */
+MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
+ struct http_message *hm, char *buf,
+ size_t blen);
+
+#ifndef MG_DISABLE_FILESYSTEM
+MG_INTERNAL time_t mg_parse_date_string(const char *datetime);
+MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st);
+#endif
+
+struct ctl_msg {
+ mg_event_handler_t callback;
+ char message[MG_CTL_MSG_MESSAGE_SIZE];
+};
+
+#ifndef MG_DISABLE_MQTT
+struct mg_mqtt_message;
+MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm);
+#endif
+
+/* Forward declarations for testing. */
+extern void *(*test_malloc)(size_t size);
+extern void *(*test_calloc)(size_t count, size_t size);
+
+#endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/base64.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef EXCLUDE_COMMON
+
+/* Amalgamated: #include "common/base64.h" */
+#include
+
+/* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ */
+
+#define NUM_UPPERCASES ('Z' - 'A' + 1)
+#define NUM_LETTERS (NUM_UPPERCASES * 2)
+#define NUM_DIGITS ('9' - '0' + 1)
+
+/*
+ * Emit a base64 code char.
+ *
+ * Doesn't use memory, thus it's safe to use to safely dump memory in crashdumps
+ */
+static void cs_base64_emit_code(struct cs_base64_ctx *ctx, int v) {
+ if (v < NUM_UPPERCASES) {
+ ctx->b64_putc(v + 'A', ctx->user_data);
+ } else if (v < (NUM_LETTERS)) {
+ ctx->b64_putc(v - NUM_UPPERCASES + 'a', ctx->user_data);
+ } else if (v < (NUM_LETTERS + NUM_DIGITS)) {
+ ctx->b64_putc(v - NUM_LETTERS + '0', ctx->user_data);
+ } else {
+ ctx->b64_putc(v - NUM_LETTERS - NUM_DIGITS == 0 ? '+' : '/',
+ ctx->user_data);
+ }
+}
+
+static void cs_base64_emit_chunk(struct cs_base64_ctx *ctx) {
+ int a, b, c;
+
+ a = ctx->chunk[0];
+ b = ctx->chunk[1];
+ c = ctx->chunk[2];
+
+ cs_base64_emit_code(ctx, a >> 2);
+ cs_base64_emit_code(ctx, ((a & 3) << 4) | (b >> 4));
+ if (ctx->chunk_size > 1) {
+ cs_base64_emit_code(ctx, (b & 15) << 2 | (c >> 6));
+ }
+ if (ctx->chunk_size > 2) {
+ cs_base64_emit_code(ctx, c & 63);
+ }
+}
+
+void cs_base64_init(struct cs_base64_ctx *ctx, cs_base64_putc_t b64_putc,
+ void *user_data) {
+ ctx->chunk_size = 0;
+ ctx->b64_putc = b64_putc;
+ ctx->user_data = user_data;
+}
+
+void cs_base64_update(struct cs_base64_ctx *ctx, const char *str, size_t len) {
+ const unsigned char *src = (const unsigned char *) str;
+ size_t i;
+ for (i = 0; i < len; i++) {
+ ctx->chunk[ctx->chunk_size++] = src[i];
+ if (ctx->chunk_size == 3) {
+ cs_base64_emit_chunk(ctx);
+ ctx->chunk_size = 0;
+ }
+ }
+}
+
+void cs_base64_finish(struct cs_base64_ctx *ctx) {
+ if (ctx->chunk_size > 0) {
+ int i;
+ memset(&ctx->chunk[ctx->chunk_size], 0, 3 - ctx->chunk_size);
+ cs_base64_emit_chunk(ctx);
+ for (i = 0; i < (3 - ctx->chunk_size); i++) {
+ ctx->b64_putc('=', ctx->user_data);
+ }
+ }
+}
+
+#define BASE64_ENCODE_BODY \
+ static const char *b64 = \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; \
+ int i, j, a, b, c; \
+ \
+ for (i = j = 0; i < src_len; i += 3) { \
+ a = src[i]; \
+ b = i + 1 >= src_len ? 0 : src[i + 1]; \
+ c = i + 2 >= src_len ? 0 : src[i + 2]; \
+ \
+ BASE64_OUT(b64[a >> 2]); \
+ BASE64_OUT(b64[((a & 3) << 4) | (b >> 4)]); \
+ if (i + 1 < src_len) { \
+ BASE64_OUT(b64[(b & 15) << 2 | (c >> 6)]); \
+ } \
+ if (i + 2 < src_len) { \
+ BASE64_OUT(b64[c & 63]); \
+ } \
+ } \
+ \
+ while (j % 4 != 0) { \
+ BASE64_OUT('='); \
+ } \
+ BASE64_FLUSH()
+
+#define BASE64_OUT(ch) \
+ do { \
+ dst[j++] = (ch); \
+ } while (0)
+
+#define BASE64_FLUSH() \
+ do { \
+ dst[j++] = '\0'; \
+ } while (0)
+
+void cs_base64_encode(const unsigned char *src, int src_len, char *dst) {
+ BASE64_ENCODE_BODY;
+}
+
+#undef BASE64_OUT
+#undef BASE64_FLUSH
+
+#ifndef CS_DISABLE_STDIO
+#define BASE64_OUT(ch) \
+ do { \
+ fprintf(f, "%c", (ch)); \
+ j++; \
+ } while (0)
+
+#define BASE64_FLUSH()
+
+void cs_fprint_base64(FILE *f, const unsigned char *src, int src_len) {
+ BASE64_ENCODE_BODY;
+}
+
+#undef BASE64_OUT
+#undef BASE64_FLUSH
+#endif /* !CS_DISABLE_STDIO */
+
+/* Convert one byte of encoded base64 input stream to 6-bit chunk */
+static unsigned char from_b64(unsigned char ch) {
+ /* Inverse lookup map */
+ static const unsigned char tab[128] = {
+ 255, 255, 255, 255,
+ 255, 255, 255, 255, /* 0 */
+ 255, 255, 255, 255,
+ 255, 255, 255, 255, /* 8 */
+ 255, 255, 255, 255,
+ 255, 255, 255, 255, /* 16 */
+ 255, 255, 255, 255,
+ 255, 255, 255, 255, /* 24 */
+ 255, 255, 255, 255,
+ 255, 255, 255, 255, /* 32 */
+ 255, 255, 255, 62,
+ 255, 255, 255, 63, /* 40 */
+ 52, 53, 54, 55,
+ 56, 57, 58, 59, /* 48 */
+ 60, 61, 255, 255,
+ 255, 200, 255, 255, /* 56 '=' is 200, on index 61 */
+ 255, 0, 1, 2,
+ 3, 4, 5, 6, /* 64 */
+ 7, 8, 9, 10,
+ 11, 12, 13, 14, /* 72 */
+ 15, 16, 17, 18,
+ 19, 20, 21, 22, /* 80 */
+ 23, 24, 25, 255,
+ 255, 255, 255, 255, /* 88 */
+ 255, 26, 27, 28,
+ 29, 30, 31, 32, /* 96 */
+ 33, 34, 35, 36,
+ 37, 38, 39, 40, /* 104 */
+ 41, 42, 43, 44,
+ 45, 46, 47, 48, /* 112 */
+ 49, 50, 51, 255,
+ 255, 255, 255, 255, /* 120 */
+ };
+ return tab[ch & 127];
+}
+
+int cs_base64_decode(const unsigned char *s, int len, char *dst) {
+ unsigned char a, b, c, d;
+ int orig_len = len;
+ while (len >= 4 && (a = from_b64(s[0])) != 255 &&
+ (b = from_b64(s[1])) != 255 && (c = from_b64(s[2])) != 255 &&
+ (d = from_b64(s[3])) != 255) {
+ s += 4;
+ len -= 4;
+ if (a == 200 || b == 200) break; /* '=' can't be there */
+ *dst++ = a << 2 | b >> 4;
+ if (c == 200) break;
+ *dst++ = b << 4 | c >> 2;
+ if (d == 200) break;
+ *dst++ = c << 6 | d;
+ }
+ *dst = 0;
+ return orig_len - len;
+}
+
+#endif /* EXCLUDE_COMMON */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/cs_dbg.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Amalgamated: #include "common/cs_dbg.h" */
+
+#include
+#include
+
+/* Amalgamated: #include "common/cs_time.h" */
+
+enum cs_log_level cs_log_level =
+#ifdef CS_ENABLE_DEBUG
+ LL_VERBOSE_DEBUG;
+#else
+ LL_ERROR;
+#endif
+
+#ifndef CS_DISABLE_STDIO
+
+FILE *cs_log_file = NULL;
+
+#ifdef CS_LOG_TS_DIFF
+double cs_log_ts;
+#endif
+
+void cs_log_print_prefix(const char *func) {
+ if (cs_log_file == NULL) cs_log_file = stderr;
+ fprintf(cs_log_file, "%-20s ", func);
+#ifdef CS_LOG_TS_DIFF
+ {
+ double now = cs_time();
+ fprintf(cs_log_file, "%7u ", (unsigned int) ((now - cs_log_ts) * 1000000));
+ cs_log_ts = now;
+ }
+#endif
+}
+
+void cs_log_printf(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(cs_log_file, fmt, ap);
+ va_end(ap);
+ fputc('\n', cs_log_file);
+ fflush(cs_log_file);
+}
+
+void cs_log_set_file(FILE *file) {
+ cs_log_file = file;
+}
+
+#endif /* !CS_DISABLE_STDIO */
+
+void cs_log_set_level(enum cs_log_level level) {
+ cs_log_level = level;
+#if defined(CS_LOG_TS_DIFF) && !defined(CS_DISABLE_STDIO)
+ cs_log_ts = cs_time();
+#endif
+}
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/cs_dirent.h"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_CS_DIRENT_H_
+#define CS_COMMON_CS_DIRENT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef CS_ENABLE_SPIFFS
+
+#include
+
+typedef struct {
+ spiffs_DIR dh;
+ struct spiffs_dirent de;
+} DIR;
+
+#define d_name name
+#define dirent spiffs_dirent
+
+int rmdir(const char *path);
+int mkdir(const char *path, mode_t mode);
+
+#endif
+
+#if defined(_WIN32)
+struct dirent {
+ char d_name[MAX_PATH];
+};
+
+typedef struct DIR {
+ HANDLE handle;
+ WIN32_FIND_DATAW info;
+ struct dirent result;
+} DIR;
+#endif
+
+#if defined(_WIN32) || defined(CS_ENABLE_SPIFFS)
+DIR *opendir(const char *dir_name);
+int closedir(DIR *dir);
+struct dirent *readdir(DIR *dir);
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_COMMON_CS_DIRENT_H_ */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/cs_dirent.c"
+#endif
+/*
+ * Copyright (c) 2015 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef EXCLUDE_COMMON
+
+/* Amalgamated: #include "common/cs_dirent.h" */
+
+/*
+ * This file contains POSIX opendir/closedir/readdir API implementation
+ * for systems which do not natively support it (e.g. Windows).
+ */
+
+#ifndef MG_FREE
+#define MG_FREE free
+#endif
+
+#ifndef MG_MALLOC
+#define MG_MALLOC malloc
+#endif
+
+#ifdef _WIN32
+DIR *opendir(const char *name) {
+ DIR *dir = NULL;
+ wchar_t wpath[MAX_PATH];
+ DWORD attrs;
+
+ if (name == NULL) {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ } else if ((dir = (DIR *) MG_MALLOC(sizeof(*dir))) == NULL) {
+ SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+ } else {
+ to_wchar(name, wpath, ARRAY_SIZE(wpath));
+ attrs = GetFileAttributesW(wpath);
+ if (attrs != 0xFFFFFFFF && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
+ (void) wcscat(wpath, L"\\*");
+ dir->handle = FindFirstFileW(wpath, &dir->info);
+ dir->result.d_name[0] = '\0';
+ } else {
+ MG_FREE(dir);
+ dir = NULL;
+ }
+ }
+
+ return dir;
+}
+
+int closedir(DIR *dir) {
+ int result = 0;
+
+ if (dir != NULL) {
+ if (dir->handle != INVALID_HANDLE_VALUE)
+ result = FindClose(dir->handle) ? 0 : -1;
+ MG_FREE(dir);
+ } else {
+ result = -1;
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ }
+
+ return result;
+}
+
+struct dirent *readdir(DIR *dir) {
+ struct dirent *result = NULL;
+
+ if (dir) {
+ if (dir->handle != INVALID_HANDLE_VALUE) {
+ result = &dir->result;
+ (void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1,
+ result->d_name, sizeof(result->d_name), NULL,
+ NULL);
+
+ if (!FindNextFileW(dir->handle, &dir->info)) {
+ (void) FindClose(dir->handle);
+ dir->handle = INVALID_HANDLE_VALUE;
+ }
+
+ } else {
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ }
+ } else {
+ SetLastError(ERROR_BAD_ARGUMENTS);
+ }
+
+ return result;
+}
+#endif
+
+#ifdef CS_ENABLE_SPIFFS
+
+DIR *opendir(const char *dir_name) {
+ DIR *dir = NULL;
+ extern spiffs fs;
+
+ if (dir_name != NULL && (dir = (DIR *) malloc(sizeof(*dir))) != NULL &&
+ SPIFFS_opendir(&fs, (char *) dir_name, &dir->dh) == NULL) {
+ free(dir);
+ dir = NULL;
+ }
+
+ return dir;
+}
+
+int closedir(DIR *dir) {
+ if (dir != NULL) {
+ SPIFFS_closedir(&dir->dh);
+ free(dir);
+ }
+ return 0;
+}
+
+struct dirent *readdir(DIR *dir) {
+ return SPIFFS_readdir(&dir->dh, &dir->de);
+}
+
+/* SPIFFs doesn't support directory operations */
+int rmdir(const char *path) {
+ (void) path;
+ return ENOTDIR;
+}
+
+int mkdir(const char *path, mode_t mode) {
+ (void) path;
+ (void) mode;
+ /* for spiffs supports only root dir, which comes from mongoose as '.' */
+ return (strlen(path) == 1 && *path == '.') ? 0 : ENOTDIR;
+}
+
+#endif /* CS_ENABLE_SPIFFS */
+
+#endif /* EXCLUDE_COMMON */
+
+/* ISO C requires a translation unit to contain at least one declaration */
+typedef int cs_dirent_dummy;
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/cs_time.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Amalgamated: #include "common/cs_time.h" */
+
+#ifndef _WIN32
+#include
+#if !defined(CS_PLATFORM) || \
+ (CS_PLATFORM != CS_P_CC3200 && CS_PLATFORM != CS_P_MSP432)
+#include
+#endif
+#else
+#include
+#endif
+
+double cs_time() {
+ double now;
+#ifndef _WIN32
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL /* tz */) != 0) return 0;
+ now = (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0);
+#else
+ now = GetTickCount() / 1000.0;
+#endif
+ return now;
+}
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../deps/frozen/frozen.c"
+#endif
+/*
+ * Copyright (c) 2004-2013 Sergey Lyubka
+ * Copyright (c) 2013 Cesanta Software Limited
+ * All rights reserved
+ *
+ * This library is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this library 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 library under a commercial
+ * license, as set out in .
+ */
+
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */
+#endif
+
+#include
+#include
+#include
+#include
+/* Amalgamated: #include "frozen.h" */
+
+#ifdef _WIN32
+#define snprintf _snprintf
+#endif
+
+#ifndef FROZEN_REALLOC
+#define FROZEN_REALLOC realloc
+#endif
+
+#ifndef FROZEN_FREE
+#define FROZEN_FREE free
+#endif
+
+struct frozen {
+ const char *end;
+ const char *cur;
+ struct json_token *tokens;
+ int max_tokens;
+ int num_tokens;
+ int do_realloc;
+};
+
+static int parse_object(struct frozen *f);
+static int parse_value(struct frozen *f);
+
+#define EXPECT(cond, err_code) \
+ do { \
+ if (!(cond)) return (err_code); \
+ } while (0)
+#define TRY(expr) \
+ do { \
+ int _n = expr; \
+ if (_n < 0) return _n; \
+ } while (0)
+#define END_OF_STRING (-1)
+
+static int left(const struct frozen *f) {
+ return f->end - f->cur;
+}
+
+static int is_space(int ch) {
+ return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
+}
+
+static void skip_whitespaces(struct frozen *f) {
+ while (f->cur < f->end && is_space(*f->cur)) f->cur++;
+}
+
+static int cur(struct frozen *f) {
+ skip_whitespaces(f);
+ return f->cur >= f->end ? END_OF_STRING : *(unsigned char *) f->cur;
+}
+
+static int test_and_skip(struct frozen *f, int expected) {
+ int ch = cur(f);
+ if (ch == expected) {
+ f->cur++;
+ return 0;
+ }
+ return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
+}
+
+static int test_no_skip(struct frozen *f, int expected) {
+ int ch = cur(f);
+ if (ch == expected) {
+ return 0;
+ }
+ return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
+}
+
+static int is_alpha(int ch) {
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
+}
+
+static int is_digit(int ch) {
+ return ch >= '0' && ch <= '9';
+}
+
+static int is_hex_digit(int ch) {
+ return is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
+}
+
+static int get_escape_len(const char *s, int len) {
+ switch (*s) {
+ case 'u':
+ return len < 6 ? JSON_STRING_INCOMPLETE
+ : is_hex_digit(s[1]) && is_hex_digit(s[2]) &&
+ is_hex_digit(s[3]) && is_hex_digit(s[4])
+ ? 5
+ : JSON_STRING_INVALID;
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ return len < 2 ? JSON_STRING_INCOMPLETE : 1;
+ default:
+ return JSON_STRING_INVALID;
+ }
+}
+
+static int capture_ptr(struct frozen *f, const char *ptr, enum json_type type) {
+ if (f->do_realloc && f->num_tokens >= f->max_tokens) {
+ int new_size = f->max_tokens == 0 ? 100 : f->max_tokens * 2;
+ void *p = FROZEN_REALLOC(f->tokens, new_size * sizeof(f->tokens[0]));
+ if (p == NULL) return JSON_TOKEN_ARRAY_TOO_SMALL;
+ f->max_tokens = new_size;
+ f->tokens = (struct json_token *) p;
+ }
+ if (f->tokens == NULL || f->max_tokens == 0) return 0;
+ if (f->num_tokens >= f->max_tokens) return JSON_TOKEN_ARRAY_TOO_SMALL;
+ f->tokens[f->num_tokens].ptr = ptr;
+ f->tokens[f->num_tokens].type = type;
+ f->num_tokens++;
+ return 0;
+}
+
+static int capture_len(struct frozen *f, int token_index, const char *ptr) {
+ if (f->tokens == 0 || f->max_tokens == 0) return 0;
+ EXPECT(token_index >= 0 && token_index < f->max_tokens, JSON_STRING_INVALID);
+ f->tokens[token_index].len = ptr - f->tokens[token_index].ptr;
+ f->tokens[token_index].num_desc = (f->num_tokens - 1) - token_index;
+ return 0;
+}
+
+/* identifier = letter { letter | digit | '_' } */
+static int parse_identifier(struct frozen *f) {
+ EXPECT(is_alpha(cur(f)), JSON_STRING_INVALID);
+ TRY(capture_ptr(f, f->cur, JSON_TYPE_STRING));
+ while (f->cur < f->end &&
+ (*f->cur == '_' || is_alpha(*f->cur) || is_digit(*f->cur))) {
+ f->cur++;
+ }
+ capture_len(f, f->num_tokens - 1, f->cur);
+ return 0;
+}
+
+static int get_utf8_char_len(unsigned char ch) {
+ if ((ch & 0x80) == 0) return 1;
+ switch (ch & 0xf0) {
+ case 0xf0:
+ return 4;
+ case 0xe0:
+ return 3;
+ default:
+ return 2;
+ }
+}
+
+/* string = '"' { quoted_printable_chars } '"' */
+static int parse_string(struct frozen *f) {
+ int n, ch = 0, len = 0;
+ TRY(test_and_skip(f, '"'));
+ TRY(capture_ptr(f, f->cur, JSON_TYPE_STRING));
+ for (; f->cur < f->end; f->cur += len) {
+ ch = *(unsigned char *) f->cur;
+ len = get_utf8_char_len((unsigned char) ch);
+ EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */
+ EXPECT(len < left(f), JSON_STRING_INCOMPLETE);
+ if (ch == '\\') {
+ EXPECT((n = get_escape_len(f->cur + 1, left(f))) > 0, n);
+ len += n;
+ } else if (ch == '"') {
+ capture_len(f, f->num_tokens - 1, f->cur);
+ f->cur++;
+ break;
+ };
+ }
+ return ch == '"' ? 0 : JSON_STRING_INCOMPLETE;
+}
+
+/* number = [ '-' ] digit+ [ '.' digit+ ] [ ['e'|'E'] ['+'|'-'] digit+ ] */
+static int parse_number(struct frozen *f) {
+ int ch = cur(f);
+ TRY(capture_ptr(f, f->cur, JSON_TYPE_NUMBER));
+ if (ch == '-') f->cur++;
+ EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
+ EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID);
+ while (f->cur < f->end && is_digit(f->cur[0])) f->cur++;
+ if (f->cur < f->end && f->cur[0] == '.') {
+ f->cur++;
+ EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
+ EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID);
+ while (f->cur < f->end && is_digit(f->cur[0])) f->cur++;
+ }
+ if (f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) {
+ f->cur++;
+ EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
+ if ((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++;
+ EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE);
+ EXPECT(is_digit(f->cur[0]), JSON_STRING_INVALID);
+ while (f->cur < f->end && is_digit(f->cur[0])) f->cur++;
+ }
+ capture_len(f, f->num_tokens - 1, f->cur);
+ return 0;
+}
+
+/* array = '[' [ value { ',' value } ] ']' */
+static int parse_array(struct frozen *f) {
+ int ind;
+ TRY(test_and_skip(f, '['));
+ TRY(capture_ptr(f, f->cur - 1, JSON_TYPE_ARRAY));
+ ind = f->num_tokens - 1;
+ while (cur(f) != ']') {
+ TRY(parse_value(f));
+ if (cur(f) == ',') f->cur++;
+ }
+ TRY(test_and_skip(f, ']'));
+ capture_len(f, ind, f->cur);
+ return 0;
+}
+
+static int compare(const char *s, const char *str, int len) {
+ int i = 0;
+ while (i < len && s[i] == str[i]) i++;
+ return i == len ? 1 : 0;
+}
+
+static int expect(struct frozen *f, const char *s, int len, enum json_type t) {
+ int i, n = left(f);
+
+ TRY(capture_ptr(f, f->cur, t));
+ for (i = 0; i < len; i++) {
+ if (i >= n) return JSON_STRING_INCOMPLETE;
+ if (f->cur[i] != s[i]) return JSON_STRING_INVALID;
+ }
+ f->cur += len;
+ TRY(capture_len(f, f->num_tokens - 1, f->cur));
+
+ return 0;
+}
+
+/* value = 'null' | 'true' | 'false' | number | string | array | object */
+static int parse_value(struct frozen *f) {
+ int ch = cur(f);
+
+ switch (ch) {
+ case '"':
+ TRY(parse_string(f));
+ break;
+ case '{':
+ TRY(parse_object(f));
+ break;
+ case '[':
+ TRY(parse_array(f));
+ break;
+ case 'n':
+ TRY(expect(f, "null", 4, JSON_TYPE_NULL));
+ break;
+ case 't':
+ TRY(expect(f, "true", 4, JSON_TYPE_TRUE));
+ break;
+ case 'f':
+ TRY(expect(f, "false", 5, JSON_TYPE_FALSE));
+ break;
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ TRY(parse_number(f));
+ break;
+ default:
+ return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
+ }
+
+ return 0;
+}
+
+/* key = identifier | string */
+static int parse_key(struct frozen *f) {
+ int ch = cur(f);
+#if 0
+ printf("%s 1 [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur);
+#endif
+ if (is_alpha(ch)) {
+ TRY(parse_identifier(f));
+ } else if (ch == '"') {
+ TRY(parse_string(f));
+ } else {
+ return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
+ }
+ return 0;
+}
+
+/* pair = key ':' value */
+static int parse_pair(struct frozen *f) {
+ TRY(parse_key(f));
+ TRY(test_and_skip(f, ':'));
+ TRY(parse_value(f));
+ return 0;
+}
+
+/* object = '{' pair { ',' pair } '}' */
+static int parse_object(struct frozen *f) {
+ int ind;
+ TRY(test_and_skip(f, '{'));
+ TRY(capture_ptr(f, f->cur - 1, JSON_TYPE_OBJECT));
+ ind = f->num_tokens - 1;
+ while (cur(f) != '}') {
+ TRY(parse_pair(f));
+ if (cur(f) == ',') f->cur++;
+ }
+ TRY(test_and_skip(f, '}'));
+ capture_len(f, ind, f->cur);
+ return 0;
+}
+
+static int doit(struct frozen *f) {
+ int ret = 0;
+
+ if (f->cur == 0 || f->end < f->cur) return JSON_STRING_INVALID;
+ if (f->end == f->cur) return JSON_STRING_INCOMPLETE;
+
+ if (0 == (ret = test_no_skip(f, '{'))) {
+ TRY(parse_object(f));
+ } else if (0 == (ret = test_no_skip(f, '['))) {
+ TRY(parse_array(f));
+ } else {
+ return ret;
+ }
+
+ TRY(capture_ptr(f, f->cur, JSON_TYPE_EOF));
+ capture_len(f, f->num_tokens, f->cur);
+ return 0;
+}
+
+/* json = object */
+int parse_json(const char *s, int s_len, struct json_token *arr, int arr_len) {
+ struct frozen frozen;
+
+ memset(&frozen, 0, sizeof(frozen));
+ frozen.end = s + s_len;
+ frozen.cur = s;
+ frozen.tokens = arr;
+ frozen.max_tokens = arr_len;
+
+ TRY(doit(&frozen));
+
+ return frozen.cur - s;
+}
+
+struct json_token *parse_json2(const char *s, int s_len) {
+ struct frozen frozen;
+
+ memset(&frozen, 0, sizeof(frozen));
+ frozen.end = s + s_len;
+ frozen.cur = s;
+ frozen.do_realloc = 1;
+
+ if (doit(&frozen) < 0) {
+ FROZEN_FREE((void *) frozen.tokens);
+ frozen.tokens = NULL;
+ }
+ return frozen.tokens;
+}
+
+static int path_part_len(const char *p) {
+ int i = 0;
+ while (p[i] != '\0' && p[i] != '[' && p[i] != '.') i++;
+ return i;
+}
+
+struct json_token *find_json_token(struct json_token *toks, const char *path) {
+ while (path != 0 && path[0] != '\0') {
+ int i, ind2 = 0, ind = -1, skip = 2, n = path_part_len(path);
+ if (path[0] == '[') {
+ if (toks->type != JSON_TYPE_ARRAY || !is_digit(path[1])) return 0;
+ for (ind = 0, n = 1; path[n] != ']' && path[n] != '\0'; n++) {
+ if (!is_digit(path[n])) return 0;
+ ind *= 10;
+ ind += path[n] - '0';
+ }
+ if (path[n++] != ']') return 0;
+ skip = 1; /* In objects, we skip 2 elems while iterating, in arrays 1. */
+ } else if (toks->type != JSON_TYPE_OBJECT)
+ return 0;
+ toks++;
+ for (i = 0; i < toks[-1].num_desc; i += skip, ind2++) {
+ /* ind == -1 indicated that we're iterating an array, not object */
+ if (ind == -1 && toks[i].type != JSON_TYPE_STRING) return 0;
+ if (ind2 == ind ||
+ (ind == -1 && toks[i].len == n && compare(path, toks[i].ptr, n))) {
+ i += skip - 1;
+ break;
+ };
+ if (toks[i - 1 + skip].type == JSON_TYPE_ARRAY ||
+ toks[i - 1 + skip].type == JSON_TYPE_OBJECT) {
+ i += toks[i - 1 + skip].num_desc;
+ }
+ }
+ if (i == toks[-1].num_desc) return 0;
+ path += n;
+ if (path[0] == '.') path++;
+ if (path[0] == '\0') return &toks[i];
+ toks += i;
+ }
+ return 0;
+}
+
+int json_emit_long(char *buf, int buf_len, long int value) {
+ char tmp[20];
+ int n = snprintf(tmp, sizeof(tmp), "%ld", value);
+ strncpy(buf, tmp, buf_len > 0 ? buf_len : 0);
+ return n;
+}
+
+int json_emit_double(char *buf, int buf_len, double value) {
+ char tmp[20];
+ int n = snprintf(tmp, sizeof(tmp), "%g", value);
+ strncpy(buf, tmp, buf_len > 0 ? buf_len : 0);
+ return n;
+}
+
+int json_emit_quoted_str(char *s, int s_len, const char *str, int len) {
+ const char *begin = s, *end = s + s_len, *str_end = str + len;
+ char ch;
+
+#define EMIT(x) \
+ do { \
+ if (s < end) *s = x; \
+ s++; \
+ } while (0)
+
+ EMIT('"');
+ while (str < str_end) {
+ ch = *str++;
+ switch (ch) {
+ case '"':
+ EMIT('\\');
+ EMIT('"');
+ break;
+ case '\\':
+ EMIT('\\');
+ EMIT('\\');
+ break;
+ case '\b':
+ EMIT('\\');
+ EMIT('b');
+ break;
+ case '\f':
+ EMIT('\\');
+ EMIT('f');
+ break;
+ case '\n':
+ EMIT('\\');
+ EMIT('n');
+ break;
+ case '\r':
+ EMIT('\\');
+ EMIT('r');
+ break;
+ case '\t':
+ EMIT('\\');
+ EMIT('t');
+ break;
+ default:
+ EMIT(ch);
+ }
+ }
+ EMIT('"');
+ if (s < end) {
+ *s = '\0';
+ }
+
+ return s - begin;
+}
+
+int json_emit_unquoted_str(char *buf, int buf_len, const char *str, int len) {
+ if (buf_len > 0 && len > 0) {
+ int n = len < buf_len ? len : buf_len;
+ memcpy(buf, str, n);
+ if (n < buf_len) {
+ buf[n] = '\0';
+ }
+ }
+ return len;
+}
+
+int json_emit_va(char *s, int s_len, const char *fmt, va_list ap) {
+ const char *end = s + s_len, *str, *orig = s;
+ size_t len;
+
+ while (*fmt != '\0') {
+ switch (*fmt) {
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case ',':
+ case ':':
+ case ' ':
+ case '\r':
+ case '\n':
+ case '\t':
+ if (s < end) {
+ *s = *fmt;
+ }
+ s++;
+ break;
+ case 'i':
+ s += json_emit_long(s, end - s, va_arg(ap, long) );
+ break;
+ case 'f':
+ s += json_emit_double(s, end - s, va_arg(ap, double) );
+ break;
+ case 'v':
+ str = va_arg(ap, char *);
+ len = va_arg(ap, size_t);
+ s += json_emit_quoted_str(s, end - s, str, len);
+ break;
+ case 'V':
+ str = va_arg(ap, char *);
+ len = va_arg(ap, size_t);
+ s += json_emit_unquoted_str(s, end - s, str, len);
+ break;
+ case 's':
+ str = va_arg(ap, char *);
+ s += json_emit_quoted_str(s, end - s, str, strlen(str));
+ break;
+ case 'S':
+ str = va_arg(ap, char *);
+ s += json_emit_unquoted_str(s, end - s, str, strlen(str));
+ break;
+ case 'T':
+ s += json_emit_unquoted_str(s, end - s, "true", 4);
+ break;
+ case 'F':
+ s += json_emit_unquoted_str(s, end - s, "false", 5);
+ break;
+ case 'N':
+ s += json_emit_unquoted_str(s, end - s, "null", 4);
+ break;
+ default:
+ return 0;
+ }
+ fmt++;
+ }
+
+ /* Best-effort to 0-terminate generated string */
+ if (s < end) {
+ *s = '\0';
+ }
+
+ return s - orig;
+}
+
+int json_emit(char *buf, int buf_len, const char *fmt, ...) {
+ int len;
+ va_list ap;
+
+ va_start(ap, fmt);
+ len = json_emit_va(buf, buf_len, fmt, ap);
+ va_end(ap);
+
+ return len;
+}
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/md5.c"
+#endif
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#if !defined(DISABLE_MD5) && !defined(EXCLUDE_COMMON)
+
+/* Amalgamated: #include "common/md5.h" */
+
+#ifndef CS_ENABLE_NATIVE_MD5
+static void byteReverse(unsigned char *buf, unsigned longs) {
+/* Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN */
+#if BYTE_ORDER == BIG_ENDIAN
+ do {
+ uint32_t t = (uint32_t)((unsigned) buf[3] << 8 | buf[2]) << 16 |
+ ((unsigned) buf[1] << 8 | buf[0]);
+ *(uint32_t *) buf = t;
+ buf += 4;
+ } while (--longs);
+#else
+ (void) buf;
+ (void) longs;
+#endif
+}
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+ (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5_Init(MD5_CTX *ctx) {
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
+ register uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+void MD5_Update(MD5_CTX *ctx, const unsigned char *buf, size_t len) {
+ uint32_t t;
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;
+ ctx->bits[1] += (uint32_t) len >> 29;
+
+ t = (t >> 3) & 0x3f;
+
+ if (t) {
+ unsigned char *p = (unsigned char *) ctx->in + t;
+
+ t = 64 - t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ memcpy(ctx->in, buf, len);
+}
+
+void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) {
+ unsigned count;
+ unsigned char *p;
+ uint32_t *a;
+
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ p = ctx->in + count;
+ *p++ = 0x80;
+ count = 64 - 1 - count;
+ if (count < 8) {
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ memset(ctx->in, 0, 56);
+ } else {
+ memset(p, 0, count - 8);
+ }
+ byteReverse(ctx->in, 14);
+
+ a = (uint32_t *) ctx->in;
+ a[14] = ctx->bits[0];
+ a[15] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+ byteReverse((unsigned char *) ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset((char *) ctx, 0, sizeof(*ctx));
+}
+#endif /* CS_ENABLE_NATIVE_MD5 */
+
+/*
+ * Stringify binary data. Output buffer size must be 2 * size_of_input + 1
+ * because each byte of input takes 2 bytes in string representation
+ * plus 1 byte for the terminating \0 character.
+ */
+void cs_to_hex(char *to, const unsigned char *p, size_t len) {
+ static const char *hex = "0123456789abcdef";
+
+ for (; len--; p++) {
+ *to++ = hex[p[0] >> 4];
+ *to++ = hex[p[0] & 0x0f];
+ }
+ *to = '\0';
+}
+
+char *cs_md5(char buf[33], ...) {
+ unsigned char hash[16];
+ const unsigned char *p;
+ va_list ap;
+ MD5_CTX ctx;
+
+ MD5_Init(&ctx);
+
+ va_start(ap, buf);
+ while ((p = va_arg(ap, const unsigned char *) ) != NULL) {
+ size_t len = va_arg(ap, size_t);
+ MD5_Update(&ctx, p, len);
+ }
+ va_end(ap);
+
+ MD5_Final(hash, &ctx);
+ cs_to_hex(buf, hash, sizeof(hash));
+
+ return buf;
+}
+
+#endif /* EXCLUDE_COMMON */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/mbuf.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef EXCLUDE_COMMON
+
+#include
+#include
+/* Amalgamated: #include "common/mbuf.h" */
+
+#ifndef MBUF_REALLOC
+#define MBUF_REALLOC realloc
+#endif
+
+#ifndef MBUF_FREE
+#define MBUF_FREE free
+#endif
+
+void mbuf_init(struct mbuf *mbuf, size_t initial_size) {
+ mbuf->len = mbuf->size = 0;
+ mbuf->buf = NULL;
+ mbuf_resize(mbuf, initial_size);
+}
+
+void mbuf_free(struct mbuf *mbuf) {
+ if (mbuf->buf != NULL) {
+ MBUF_FREE(mbuf->buf);
+ mbuf_init(mbuf, 0);
+ }
+}
+
+void mbuf_resize(struct mbuf *a, size_t new_size) {
+ if (new_size > a->size || (new_size < a->size && new_size >= a->len)) {
+ char *buf = (char *) MBUF_REALLOC(a->buf, new_size);
+ /*
+ * In case realloc fails, there's not much we can do, except keep things as
+ * they are. Note that NULL is a valid return value from realloc when
+ * size == 0, but that is covered too.
+ */
+ if (buf == NULL && new_size != 0) return;
+ a->buf = buf;
+ a->size = new_size;
+ }
+}
+
+void mbuf_trim(struct mbuf *mbuf) {
+ mbuf_resize(mbuf, mbuf->len);
+}
+
+size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {
+ char *p = NULL;
+
+ assert(a != NULL);
+ assert(a->len <= a->size);
+ assert(off <= a->len);
+
+ /* check overflow */
+ if (~(size_t) 0 - (size_t) a->buf < len) return 0;
+
+ if (a->len + len <= a->size) {
+ memmove(a->buf + off + len, a->buf + off, a->len - off);
+ if (buf != NULL) {
+ memcpy(a->buf + off, buf, len);
+ }
+ a->len += len;
+ } else {
+ size_t new_size = (a->len + len) * MBUF_SIZE_MULTIPLIER;
+ if ((p = (char *) MBUF_REALLOC(a->buf, new_size)) != NULL) {
+ a->buf = p;
+ memmove(a->buf + off + len, a->buf + off, a->len - off);
+ if (buf != NULL) memcpy(a->buf + off, buf, len);
+ a->len += len;
+ a->size = new_size;
+ } else {
+ len = 0;
+ }
+ }
+
+ return len;
+}
+
+size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {
+ return mbuf_insert(a, a->len, buf, len);
+}
+
+void mbuf_remove(struct mbuf *mb, size_t n) {
+ if (n > 0 && n <= mb->len) {
+ memmove(mb->buf, mb->buf + n, mb->len - n);
+ mb->len -= n;
+ }
+}
+
+#endif /* EXCLUDE_COMMON */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/sha1.c"
+#endif
+/* Copyright(c) By Steve Reid */
+/* 100% Public Domain */
+
+#if !defined(DISABLE_SHA1) && !defined(EXCLUDE_COMMON)
+
+/* Amalgamated: #include "common/sha1.h" */
+
+#define SHA1HANDSOFF
+#if defined(__sun)
+/* Amalgamated: #include "common/solarisfixes.h" */
+#endif
+
+union char64long16 {
+ unsigned char c[64];
+ uint32_t l[16];
+};
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+static uint32_t blk0(union char64long16 *block, int i) {
+/* Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN */
+#if BYTE_ORDER == LITTLE_ENDIAN
+ block->l[i] =
+ (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF);
+#endif
+ return block->l[i];
+}
+
+/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */
+#undef blk
+#undef R0
+#undef R1
+#undef R2
+#undef R3
+#undef R4
+
+#define blk(i) \
+ (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \
+ block->l[(i + 2) & 15] ^ block->l[i & 15], \
+ 1))
+#define R0(v, w, x, y, z, i) \
+ z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \
+ w = rol(w, 30);
+#define R1(v, w, x, y, z, i) \
+ z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
+ w = rol(w, 30);
+#define R2(v, w, x, y, z, i) \
+ z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
+ w = rol(w, 30);
+#define R3(v, w, x, y, z, i) \
+ z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
+ w = rol(w, 30);
+#define R4(v, w, x, y, z, i) \
+ z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
+ w = rol(w, 30);
+
+void cs_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {
+ uint32_t a, b, c, d, e;
+ union char64long16 block[1];
+
+ memcpy(block, buffer, 64);
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ R0(a, b, c, d, e, 0);
+ R0(e, a, b, c, d, 1);
+ R0(d, e, a, b, c, 2);
+ R0(c, d, e, a, b, 3);
+ R0(b, c, d, e, a, 4);
+ R0(a, b, c, d, e, 5);
+ R0(e, a, b, c, d, 6);
+ R0(d, e, a, b, c, 7);
+ R0(c, d, e, a, b, 8);
+ R0(b, c, d, e, a, 9);
+ R0(a, b, c, d, e, 10);
+ R0(e, a, b, c, d, 11);
+ R0(d, e, a, b, c, 12);
+ R0(c, d, e, a, b, 13);
+ R0(b, c, d, e, a, 14);
+ R0(a, b, c, d, e, 15);
+ R1(e, a, b, c, d, 16);
+ R1(d, e, a, b, c, 17);
+ R1(c, d, e, a, b, 18);
+ R1(b, c, d, e, a, 19);
+ R2(a, b, c, d, e, 20);
+ R2(e, a, b, c, d, 21);
+ R2(d, e, a, b, c, 22);
+ R2(c, d, e, a, b, 23);
+ R2(b, c, d, e, a, 24);
+ R2(a, b, c, d, e, 25);
+ R2(e, a, b, c, d, 26);
+ R2(d, e, a, b, c, 27);
+ R2(c, d, e, a, b, 28);
+ R2(b, c, d, e, a, 29);
+ R2(a, b, c, d, e, 30);
+ R2(e, a, b, c, d, 31);
+ R2(d, e, a, b, c, 32);
+ R2(c, d, e, a, b, 33);
+ R2(b, c, d, e, a, 34);
+ R2(a, b, c, d, e, 35);
+ R2(e, a, b, c, d, 36);
+ R2(d, e, a, b, c, 37);
+ R2(c, d, e, a, b, 38);
+ R2(b, c, d, e, a, 39);
+ R3(a, b, c, d, e, 40);
+ R3(e, a, b, c, d, 41);
+ R3(d, e, a, b, c, 42);
+ R3(c, d, e, a, b, 43);
+ R3(b, c, d, e, a, 44);
+ R3(a, b, c, d, e, 45);
+ R3(e, a, b, c, d, 46);
+ R3(d, e, a, b, c, 47);
+ R3(c, d, e, a, b, 48);
+ R3(b, c, d, e, a, 49);
+ R3(a, b, c, d, e, 50);
+ R3(e, a, b, c, d, 51);
+ R3(d, e, a, b, c, 52);
+ R3(c, d, e, a, b, 53);
+ R3(b, c, d, e, a, 54);
+ R3(a, b, c, d, e, 55);
+ R3(e, a, b, c, d, 56);
+ R3(d, e, a, b, c, 57);
+ R3(c, d, e, a, b, 58);
+ R3(b, c, d, e, a, 59);
+ R4(a, b, c, d, e, 60);
+ R4(e, a, b, c, d, 61);
+ R4(d, e, a, b, c, 62);
+ R4(c, d, e, a, b, 63);
+ R4(b, c, d, e, a, 64);
+ R4(a, b, c, d, e, 65);
+ R4(e, a, b, c, d, 66);
+ R4(d, e, a, b, c, 67);
+ R4(c, d, e, a, b, 68);
+ R4(b, c, d, e, a, 69);
+ R4(a, b, c, d, e, 70);
+ R4(e, a, b, c, d, 71);
+ R4(d, e, a, b, c, 72);
+ R4(c, d, e, a, b, 73);
+ R4(b, c, d, e, a, 74);
+ R4(a, b, c, d, e, 75);
+ R4(e, a, b, c, d, 76);
+ R4(d, e, a, b, c, 77);
+ R4(c, d, e, a, b, 78);
+ R4(b, c, d, e, a, 79);
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Erase working structures. The order of operations is important,
+ * used to ensure that compiler doesn't optimize those out. */
+ memset(block, 0, sizeof(block));
+ a = b = c = d = e = 0;
+ (void) a;
+ (void) b;
+ (void) c;
+ (void) d;
+ (void) e;
+}
+
+void cs_sha1_init(cs_sha1_ctx *context) {
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+void cs_sha1_update(cs_sha1_ctx *context, const unsigned char *data,
+ uint32_t len) {
+ uint32_t i, j;
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j) context->count[1]++;
+ context->count[1] += (len >> 29);
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ memcpy(&context->buffer[j], data, (i = 64 - j));
+ cs_sha1_transform(context->state, context->buffer);
+ for (; i + 63 < len; i += 64) {
+ cs_sha1_transform(context->state, &data[i]);
+ }
+ j = 0;
+ } else
+ i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *context) {
+ unsigned i;
+ unsigned char finalcount[8], c;
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>
+ ((3 - (i & 3)) * 8)) &
+ 255);
+ }
+ c = 0200;
+ cs_sha1_update(context, &c, 1);
+ while ((context->count[0] & 504) != 448) {
+ c = 0000;
+ cs_sha1_update(context, &c, 1);
+ }
+ cs_sha1_update(context, finalcount, 8);
+ for (i = 0; i < 20; i++) {
+ digest[i] =
+ (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+ }
+ memset(context, '\0', sizeof(*context));
+ memset(&finalcount, '\0', sizeof(finalcount));
+}
+
+void cs_hmac_sha1(const unsigned char *key, size_t keylen,
+ const unsigned char *data, size_t datalen,
+ unsigned char out[20]) {
+ cs_sha1_ctx ctx;
+ unsigned char buf1[64], buf2[64], tmp_key[20], i;
+
+ if (keylen > sizeof(buf1)) {
+ cs_sha1_init(&ctx);
+ cs_sha1_update(&ctx, key, keylen);
+ cs_sha1_final(tmp_key, &ctx);
+ key = tmp_key;
+ keylen = sizeof(tmp_key);
+ }
+
+ memset(buf1, 0, sizeof(buf1));
+ memset(buf2, 0, sizeof(buf2));
+ memcpy(buf1, key, keylen);
+ memcpy(buf2, key, keylen);
+
+ for (i = 0; i < sizeof(buf1); i++) {
+ buf1[i] ^= 0x36;
+ buf2[i] ^= 0x5c;
+ }
+
+ cs_sha1_init(&ctx);
+ cs_sha1_update(&ctx, buf1, sizeof(buf1));
+ cs_sha1_update(&ctx, data, datalen);
+ cs_sha1_final(out, &ctx);
+
+ cs_sha1_init(&ctx);
+ cs_sha1_update(&ctx, buf2, sizeof(buf2));
+ cs_sha1_update(&ctx, out, 20);
+ cs_sha1_final(out, &ctx);
+}
+
+#endif /* EXCLUDE_COMMON */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/str_util.c"
+#endif
+/*
+ * Copyright (c) 2015 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef EXCLUDE_COMMON
+
+/* Amalgamated: #include "common/platform.h" */
+/* Amalgamated: #include "common/str_util.h" */
+
+size_t c_strnlen(const char *s, size_t maxlen) {
+ size_t l = 0;
+ for (; l < maxlen && s[l] != '\0'; l++) {
+ }
+ return l;
+}
+
+#define C_SNPRINTF_APPEND_CHAR(ch) \
+ do { \
+ if (i < (int) buf_size) buf[i] = ch; \
+ i++; \
+ } while (0)
+
+#define C_SNPRINTF_FLAG_ZERO 1
+
+#ifdef C_DISABLE_BUILTIN_SNPRINTF
+int c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) {
+ return vsnprintf(buf, buf_size, fmt, ap);
+}
+#else
+static int c_itoa(char *buf, size_t buf_size, int64_t num, int base, int flags,
+ int field_width) {
+ char tmp[40];
+ int i = 0, k = 0, neg = 0;
+
+ if (num < 0) {
+ neg++;
+ num = -num;
+ }
+
+ /* Print into temporary buffer - in reverse order */
+ do {
+ int rem = num % base;
+ if (rem < 10) {
+ tmp[k++] = '0' + rem;
+ } else {
+ tmp[k++] = 'a' + (rem - 10);
+ }
+ num /= base;
+ } while (num > 0);
+
+ /* Zero padding */
+ if (flags && C_SNPRINTF_FLAG_ZERO) {
+ while (k < field_width && k < (int) sizeof(tmp) - 1) {
+ tmp[k++] = '0';
+ }
+ }
+
+ /* And sign */
+ if (neg) {
+ tmp[k++] = '-';
+ }
+
+ /* Now output */
+ while (--k >= 0) {
+ C_SNPRINTF_APPEND_CHAR(tmp[k]);
+ }
+
+ return i;
+}
+
+int c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) {
+ int ch, i = 0, len_mod, flags, precision, field_width;
+
+ while ((ch = *fmt++) != '\0') {
+ if (ch != '%') {
+ C_SNPRINTF_APPEND_CHAR(ch);
+ } else {
+ /*
+ * Conversion specification:
+ * zero or more flags (one of: # 0 - + ')
+ * an optional minimum field width (digits)
+ * an optional precision (. followed by digits, or *)
+ * an optional length modifier (one of: hh h l ll L q j z t)
+ * conversion specifier (one of: d i o u x X e E f F g G a A c s p n)
+ */
+ flags = field_width = precision = len_mod = 0;
+
+ /* Flags. only zero-pad flag is supported. */
+ if (*fmt == '0') {
+ flags |= C_SNPRINTF_FLAG_ZERO;
+ }
+
+ /* Field width */
+ while (*fmt >= '0' && *fmt <= '9') {
+ field_width *= 10;
+ field_width += *fmt++ - '0';
+ }
+ /* Dynamic field width */
+ if (*fmt == '*') {
+ field_width = va_arg(ap, int);
+ fmt++;
+ }
+
+ /* Precision */
+ if (*fmt == '.') {
+ fmt++;
+ if (*fmt == '*') {
+ precision = va_arg(ap, int);
+ fmt++;
+ } else {
+ while (*fmt >= '0' && *fmt <= '9') {
+ precision *= 10;
+ precision += *fmt++ - '0';
+ }
+ }
+ }
+
+ /* Length modifier */
+ switch (*fmt) {
+ case 'h':
+ case 'l':
+ case 'L':
+ case 'I':
+ case 'q':
+ case 'j':
+ case 'z':
+ case 't':
+ len_mod = *fmt++;
+ if (*fmt == 'h') {
+ len_mod = 'H';
+ fmt++;
+ }
+ if (*fmt == 'l') {
+ len_mod = 'q';
+ fmt++;
+ }
+ break;
+ }
+
+ ch = *fmt++;
+ if (ch == 's') {
+ const char *s = va_arg(ap, const char *); /* Always fetch parameter */
+ int j;
+ int pad = field_width - (precision >= 0 ? c_strnlen(s, precision) : 0);
+ for (j = 0; j < pad; j++) {
+ C_SNPRINTF_APPEND_CHAR(' ');
+ }
+
+ /* `s` may be NULL in case of %.*s */
+ if (s != NULL) {
+ /* Ignore negative and 0 precisions */
+ for (j = 0; (precision <= 0 || j < precision) && s[j] != '\0'; j++) {
+ C_SNPRINTF_APPEND_CHAR(s[j]);
+ }
+ }
+ } else if (ch == 'c') {
+ ch = va_arg(ap, int); /* Always fetch parameter */
+ C_SNPRINTF_APPEND_CHAR(ch);
+ } else if (ch == 'd' && len_mod == 0) {
+ i += c_itoa(buf + i, buf_size - i, va_arg(ap, int), 10, flags,
+ field_width);
+ } else if (ch == 'd' && len_mod == 'l') {
+ i += c_itoa(buf + i, buf_size - i, va_arg(ap, long), 10, flags,
+ field_width);
+#ifdef SSIZE_MAX
+ } else if (ch == 'd' && len_mod == 'z') {
+ i += c_itoa(buf + i, buf_size - i, va_arg(ap, ssize_t), 10, flags,
+ field_width);
+#endif
+ } else if (ch == 'd' && len_mod == 'q') {
+ i += c_itoa(buf + i, buf_size - i, va_arg(ap, int64_t), 10, flags,
+ field_width);
+ } else if ((ch == 'x' || ch == 'u') && len_mod == 0) {
+ i += c_itoa(buf + i, buf_size - i, va_arg(ap, unsigned),
+ ch == 'x' ? 16 : 10, flags, field_width);
+ } else if ((ch == 'x' || ch == 'u') && len_mod == 'l') {
+ i += c_itoa(buf + i, buf_size - i, va_arg(ap, unsigned long),
+ ch == 'x' ? 16 : 10, flags, field_width);
+ } else if ((ch == 'x' || ch == 'u') && len_mod == 'z') {
+ i += c_itoa(buf + i, buf_size - i, va_arg(ap, size_t),
+ ch == 'x' ? 16 : 10, flags, field_width);
+ } else if (ch == 'p') {
+ unsigned long num = (unsigned long) va_arg(ap, void *);
+ C_SNPRINTF_APPEND_CHAR('0');
+ C_SNPRINTF_APPEND_CHAR('x');
+ i += c_itoa(buf + i, buf_size - i, num, 16, flags, 0);
+ } else {
+#ifndef NO_LIBC
+ /*
+ * TODO(lsm): abort is not nice in a library, remove it
+ * Also, ESP8266 SDK doesn't have it
+ */
+ abort();
+#endif
+ }
+ }
+ }
+
+ /* Zero-terminate the result */
+ if (buf_size > 0) {
+ buf[i < (int) buf_size ? i : (int) buf_size - 1] = '\0';
+ }
+
+ return i;
+}
+#endif
+
+int c_snprintf(char *buf, size_t buf_size, const char *fmt, ...) {
+ int result;
+ va_list ap;
+ va_start(ap, fmt);
+ result = c_vsnprintf(buf, buf_size, fmt, ap);
+ va_end(ap);
+ return result;
+}
+
+#ifdef _WIN32
+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;
+}
+#endif /* _WIN32 */
+
+/* The simplest O(mn) algorithm. Better implementation are GPLed */
+const char *c_strnstr(const char *s, const char *find, size_t slen) {
+ size_t find_length = strlen(find);
+ size_t i;
+
+ for (i = 0; i < slen; i++) {
+ if (i + find_length > slen) {
+ return NULL;
+ }
+
+ if (strncmp(&s[i], find, find_length) == 0) {
+ return &s[i];
+ }
+ }
+
+ return NULL;
+}
+
+#endif /* EXCLUDE_COMMON */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/net.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in .
+ */
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/util.h" */
+/* Amalgamated: #include "mongoose/src/dns.h" */
+/* Amalgamated: #include "mongoose/src/resolv.h" */
+/* Amalgamated: #include "common/cs_time.h" */
+
+#define MG_MAX_HOST_LEN 200
+
+#define MG_COPY_COMMON_CONNECTION_OPTIONS(dst, src) \
+ memcpy(dst, src, sizeof(*dst));
+
+/* Which flags can be pre-set by the user at connection creation time. */
+#define _MG_ALLOWED_CONNECT_FLAGS_MASK \
+ (MG_F_USER_1 | MG_F_USER_2 | MG_F_USER_3 | MG_F_USER_4 | MG_F_USER_5 | \
+ MG_F_USER_6 | MG_F_WEBSOCKET_NO_DEFRAG)
+/* Which flags should be modifiable by user's callbacks. */
+#define _MG_CALLBACK_MODIFIABLE_FLAGS_MASK \
+ (MG_F_USER_1 | MG_F_USER_2 | MG_F_USER_3 | MG_F_USER_4 | MG_F_USER_5 | \
+ MG_F_USER_6 | MG_F_WEBSOCKET_NO_DEFRAG | MG_F_SEND_AND_CLOSE | \
+ MG_F_CLOSE_IMMEDIATELY | MG_F_IS_WEBSOCKET | MG_F_DELETE_CHUNK)
+
+#ifndef intptr_t
+#define intptr_t long
+#endif
+
+int mg_is_error(int n);
+void mg_set_non_blocking_mode(sock_t sock);
+
+extern void mg_ev_mgr_init(struct mg_mgr *mgr);
+extern void mg_ev_mgr_free(struct mg_mgr *mgr);
+extern void mg_ev_mgr_add_conn(struct mg_connection *nc);
+extern void mg_ev_mgr_remove_conn(struct mg_connection *nc);
+
+MG_INTERNAL void mg_add_conn(struct mg_mgr *mgr, struct mg_connection *c) {
+ DBG(("%p %p", mgr, c));
+ c->mgr = mgr;
+ c->next = mgr->active_connections;
+ mgr->active_connections = c;
+ c->prev = NULL;
+ if (c->next != NULL) c->next->prev = c;
+ mg_ev_mgr_add_conn(c);
+}
+
+MG_INTERNAL void mg_remove_conn(struct mg_connection *conn) {
+ if (conn->prev == NULL) conn->mgr->active_connections = conn->next;
+ if (conn->prev) conn->prev->next = conn->next;
+ if (conn->next) conn->next->prev = conn->prev;
+ mg_ev_mgr_remove_conn(conn);
+}
+
+MG_INTERNAL void mg_call(struct mg_connection *nc,
+ mg_event_handler_t ev_handler, int ev, void *ev_data) {
+ if (ev_handler == NULL) {
+ /*
+ * If protocol handler is specified, call it. Otherwise, call user-specified
+ * event handler.
+ */
+ ev_handler = nc->proto_handler ? nc->proto_handler : nc->handler;
+ }
+ DBG(("%p %s ev=%d ev_data=%p flags=%lu rmbl=%d smbl=%d", nc,
+ ev_handler == nc->handler ? "user" : "proto", ev, ev_data, nc->flags,
+ (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+
+#if !defined(NO_LIBC) && !defined(MG_DISABLE_HEXDUMP)
+ /* LCOV_EXCL_START */
+ if (nc->mgr->hexdump_file != NULL && ev != MG_EV_POLL &&
+ ev != MG_EV_SEND /* handled separately */) {
+ if (ev == MG_EV_RECV) {
+ mg_hexdump_connection(nc, nc->mgr->hexdump_file, nc->recv_mbuf.buf,
+ *(int *) ev_data, ev);
+ } else {
+ mg_hexdump_connection(nc, nc->mgr->hexdump_file, NULL, 0, ev);
+ }
+ }
+/* LCOV_EXCL_STOP */
+#endif
+ if (ev_handler != NULL) {
+ unsigned long flags_before = nc->flags;
+ size_t recv_mbuf_before = nc->recv_mbuf.len, recved;
+ ev_handler(nc, ev, ev_data);
+ recved = (recv_mbuf_before - nc->recv_mbuf.len);
+ /* Prevent user handler from fiddling with system flags. */
+ if (ev_handler == nc->handler && nc->flags != flags_before) {
+ nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) |
+ (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK);
+ }
+ if (recved > 0 && !(nc->flags & MG_F_UDP)) {
+ mg_if_recved(nc, recved);
+ }
+ }
+ DBG(("%p after %s flags=%lu rmbl=%d smbl=%d", nc,
+ ev_handler == nc->handler ? "user" : "proto", nc->flags,
+ (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+}
+
+void mg_if_timer(struct mg_connection *c, double now) {
+ if (c->ev_timer_time > 0 && now >= c->ev_timer_time) {
+ double old_value = c->ev_timer_time;
+ mg_call(c, NULL, MG_EV_TIMER, &now);
+ /*
+ * To prevent timer firing all the time, reset the timer after delivery.
+ * However, in case user sets it to new value, do not reset.
+ */
+ if (c->ev_timer_time == old_value) {
+ c->ev_timer_time = 0;
+ }
+ }
+}
+
+void mg_if_poll(struct mg_connection *nc, time_t now) {
+ if (nc->ssl == NULL || (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) {
+ mg_call(nc, NULL, MG_EV_POLL, &now);
+ }
+}
+
+static void mg_destroy_conn(struct mg_connection *conn) {
+ if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) {
+ conn->proto_data_destructor(conn->proto_data);
+ }
+ mg_if_destroy_conn(conn);
+#ifdef MG_ENABLE_SSL
+ if (conn->ssl != NULL) SSL_free(conn->ssl);
+ if (conn->ssl_ctx != NULL) SSL_CTX_free(conn->ssl_ctx);
+#endif
+ mbuf_free(&conn->recv_mbuf);
+ mbuf_free(&conn->send_mbuf);
+
+ memset(conn, 0, sizeof(*conn));
+ MG_FREE(conn);
+}
+
+void mg_close_conn(struct mg_connection *conn) {
+ DBG(("%p %lu", conn, conn->flags));
+ mg_call(conn, NULL, MG_EV_CLOSE, NULL);
+ mg_remove_conn(conn);
+ mg_destroy_conn(conn);
+}
+
+void mg_mgr_init(struct mg_mgr *m, void *user_data) {
+ memset(m, 0, sizeof(*m));
+#ifndef MG_DISABLE_SOCKETPAIR
+ m->ctl[0] = m->ctl[1] = INVALID_SOCKET;
+#endif
+ m->user_data = user_data;
+
+#ifdef _WIN32
+ {
+ WSADATA data;
+ WSAStartup(MAKEWORD(2, 2), &data);
+ }
+#elif defined(__unix__)
+ /* Ignore SIGPIPE signal, so if client cancels the request, it
+ * won't kill the whole process. */
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef MG_ENABLE_SSL
+ {
+ static int init_done;
+ if (!init_done) {
+ SSL_library_init();
+ init_done++;
+ }
+ }
+#endif
+
+ mg_ev_mgr_init(m);
+ DBG(("=================================="));
+ DBG(("init mgr=%p", m));
+}
+
+#ifdef MG_ENABLE_JAVASCRIPT
+static enum v7_err mg_send_js(struct v7 *v7, v7_val_t *res) {
+ v7_val_t arg0 = v7_arg(v7, 0);
+ v7_val_t arg1 = v7_arg(v7, 1);
+ struct mg_connection *c = (struct mg_connection *) v7_get_ptr(arg0);
+ size_t len = 0;
+
+ if (v7_is_string(arg1)) {
+ const char *data = v7_get_string(v7, &arg1, &len);
+ mg_send(c, data, len);
+ }
+
+ *res = v7_mk_number(len);
+
+ return V7_OK;
+}
+
+enum v7_err mg_enable_javascript(struct mg_mgr *m, struct v7 *v7,
+ const char *init_file_name) {
+ v7_val_t v;
+ m->v7 = v7;
+ v7_set_method(v7, v7_get_global(v7), "mg_send", mg_send_js);
+ return v7_exec_file(v7, init_file_name, &v);
+}
+#endif
+
+void mg_mgr_free(struct mg_mgr *m) {
+ struct mg_connection *conn, *tmp_conn;
+
+ DBG(("%p", m));
+ if (m == NULL) return;
+ /* Do one last poll, see https://github.com/cesanta/mongoose/issues/286 */
+ mg_mgr_poll(m, 0);
+
+#ifndef MG_DISABLE_SOCKETPAIR
+ if (m->ctl[0] != INVALID_SOCKET) closesocket(m->ctl[0]);
+ if (m->ctl[1] != INVALID_SOCKET) closesocket(m->ctl[1]);
+ m->ctl[0] = m->ctl[1] = INVALID_SOCKET;
+#endif
+
+ for (conn = m->active_connections; conn != NULL; conn = tmp_conn) {
+ tmp_conn = conn->next;
+ mg_close_conn(conn);
+ }
+
+ mg_ev_mgr_free(m);
+}
+
+int mg_vprintf(struct mg_connection *nc, const char *fmt, va_list ap) {
+ char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
+ int len;
+
+ if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
+ mg_send(nc, buf, len);
+ }
+ if (buf != mem && buf != NULL) {
+ MG_FREE(buf); /* LCOV_EXCL_LINE */
+ } /* LCOV_EXCL_LINE */
+
+ return len;
+}
+
+int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
+ int len;
+ va_list ap;
+ va_start(ap, fmt);
+ len = mg_vprintf(conn, fmt, ap);
+ va_end(ap);
+ return len;
+}
+
+#ifndef MG_DISABLE_SYNC_RESOLVER
+/* TODO(lsm): use non-blocking resolver */
+static int mg_resolve2(const char *host, struct in_addr *ina) {
+#ifdef MG_ENABLE_GETADDRINFO
+ int rv = 0;
+ struct addrinfo hints, *servinfo, *p;
+ struct sockaddr_in *h = NULL;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ if ((rv = getaddrinfo(host, NULL, NULL, &servinfo)) != 0) {
+ DBG(("getaddrinfo(%s) failed: %s", host, strerror(errno)));
+ return 0;
+ }
+ for (p = servinfo; p != NULL; p = p->ai_next) {
+ memcpy(&h, &p->ai_addr, sizeof(struct sockaddr_in *));
+ memcpy(ina, &h->sin_addr, sizeof(ina));
+ }
+ freeaddrinfo(servinfo);
+ return 1;
+#else
+ struct hostent *he;
+ if ((he = gethostbyname(host)) == NULL) {
+ DBG(("gethostbyname(%s) failed: %s", host, strerror(errno)));
+ } else {
+ memcpy(ina, he->h_addr_list[0], sizeof(*ina));
+ return 1;
+ }
+ return 0;
+#endif /* MG_ENABLE_GETADDRINFO */
+}
+
+int mg_resolve(const char *host, char *buf, size_t n) {
+ struct in_addr ad;
+ return mg_resolve2(host, &ad) ? snprintf(buf, n, "%s", inet_ntoa(ad)) : 0;
+}
+#endif /* MG_DISABLE_SYNC_RESOLVER */
+
+MG_INTERNAL struct mg_connection *mg_create_connection_base(
+ struct mg_mgr *mgr, mg_event_handler_t callback,
+ struct mg_add_sock_opts opts) {
+ struct mg_connection *conn;
+
+ if ((conn = (struct mg_connection *) MG_CALLOC(1, sizeof(*conn))) != NULL) {
+ conn->sock = INVALID_SOCKET;
+ conn->handler = callback;
+ conn->mgr = mgr;
+ conn->last_io_time = mg_time();
+ conn->flags = opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK;
+ conn->user_data = opts.user_data;
+ /*
+ * SIZE_MAX is defined as a long long constant in
+ * system headers on some platforms and so it
+ * doesn't compile with pedantic ansi flags.
+ */
+ conn->recv_mbuf_limit = ~0;
+ } else {
+ MG_SET_PTRPTR(opts.error_string, "failed to create connection");
+ }
+
+ return conn;
+}
+
+MG_INTERNAL struct mg_connection *mg_create_connection(
+ struct mg_mgr *mgr, mg_event_handler_t callback,
+ struct mg_add_sock_opts opts) {
+ struct mg_connection *conn = mg_create_connection_base(mgr, callback, opts);
+
+ if (!mg_if_create_conn(conn)) {
+ MG_FREE(conn);
+ conn = NULL;
+ MG_SET_PTRPTR(opts.error_string, "failed to init connection");
+ }
+
+ return conn;
+}
+
+/*
+ * Address format: [PROTO://][HOST]:PORT
+ *
+ * HOST could be IPv4/IPv6 address or a host name.
+ * `host` is a destination buffer to hold parsed HOST part. Shoud be at least
+ * MG_MAX_HOST_LEN bytes long.
+ * `proto` is a returned socket type, either SOCK_STREAM or SOCK_DGRAM
+ *
+ * Return:
+ * -1 on parse error
+ * 0 if HOST needs DNS lookup
+ * >0 length of the address string
+ */
+MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,
+ int *proto, char *host, size_t host_len) {
+ unsigned int a, b, c, d, port = 0;
+ int ch, len = 0;
+#ifdef MG_ENABLE_IPV6
+ char buf[100];
+#endif
+
+ /*
+ * MacOS needs that. If we do not zero it, subsequent bind() will fail.
+ * Also, all-zeroes in the socket address means binding to all addresses
+ * for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
+ */
+ memset(sa, 0, sizeof(*sa));
+ sa->sin.sin_family = AF_INET;
+
+ *proto = SOCK_STREAM;
+
+ if (strncmp(str, "udp://", 6) == 0) {
+ str += 6;
+ *proto = SOCK_DGRAM;
+ } else if (strncmp(str, "tcp://", 6) == 0) {
+ str += 6;
+ }
+
+ if (sscanf(str, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) {
+ /* Bind to a specific IPv4 address, e.g. 192.168.1.5:8080 */
+ sa->sin.sin_addr.s_addr =
+ htonl(((uint32_t) a << 24) | ((uint32_t) b << 16) | c << 8 | d);
+ sa->sin.sin_port = htons((uint16_t) port);
+#ifdef MG_ENABLE_IPV6
+ } else if (sscanf(str, "[%99[^]]]:%u%n", buf, &port, &len) == 2 &&
+ inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) {
+ /* IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080 */
+ sa->sin6.sin6_family = AF_INET6;
+ sa->sin.sin_port = htons((uint16_t) port);
+#endif
+#ifndef MG_DISABLE_RESOLVER
+ } else if (strlen(str) < host_len &&
+ sscanf(str, "%[^ :]:%u%n", host, &port, &len) == 2) {
+ sa->sin.sin_port = htons((uint16_t) port);
+ if (mg_resolve_from_hosts_file(host, sa) != 0) {
+ return 0;
+ }
+#endif
+ } else if (sscanf(str, ":%u%n", &port, &len) == 1 ||
+ sscanf(str, "%u%n", &port, &len) == 1) {
+ /* If only port is specified, bind to IPv4, INADDR_ANY */
+ sa->sin.sin_port = htons((uint16_t) port);
+ } else {
+ return -1;
+ }
+
+ ch = str[len]; /* Character that follows the address */
+ return port < 0xffffUL && (ch == '\0' || ch == ',' || isspace(ch)) ? len : -1;
+}
+
+#ifdef MG_ENABLE_SSL
+/*
+ * Certificate generation script is at
+ * https://github.com/cesanta/mongoose/blob/master/scripts/generate_ssl_certificates.sh
+ */
+
+#ifndef MG_DISABLE_PFS
+/*
+ * Cipher suite options used for TLS negotiation.
+ * https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations
+ */
+static const char mg_s_cipher_list[] =
+#if defined(MG_SSL_CRYPTO_MODERN)
+ "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:"
+ "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:"
+ "DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:"
+ "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
+ "ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:"
+ "ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:"
+ "DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:"
+ "DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:"
+ "!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK"
+#elif defined(MG_SSL_CRYPTO_OLD)
+ "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:"
+ "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:"
+ "DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:"
+ "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
+ "ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:"
+ "ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:"
+ "DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:"
+ "DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:"
+ "ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:"
+ "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:"
+ "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:"
+ "!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
+#else /* Default - intermediate. */
+ "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:"
+ "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:"
+ "DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:"
+ "ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:"
+ "ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:"
+ "ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:"
+ "DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:"
+ "DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:"
+ "AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:"
+ "DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:"
+ "!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
+#endif
+ ;
+
+/*
+ * Default DH params for PFS cipher negotiation. This is a 2048-bit group.
+ * Will be used if none are provided by the user in the certificate file.
+ */
+static const char mg_s_default_dh_params[] =
+ "\
+-----BEGIN DH PARAMETERS-----\n\
+MIIBCAKCAQEAlvbgD/qh9znWIlGFcV0zdltD7rq8FeShIqIhkQ0C7hYFThrBvF2E\n\
+Z9bmgaP+sfQwGpVlv9mtaWjvERbu6mEG7JTkgmVUJrUt/wiRzwTaCXBqZkdUO8Tq\n\
++E6VOEQAilstG90ikN1Tfo+K6+X68XkRUIlgawBTKuvKVwBhuvlqTGerOtnXWnrt\n\
+ym//hd3cd5PBYGBix0i7oR4xdghvfR2WLVu0LgdThTBb6XP7gLd19cQ1JuBtAajZ\n\
+wMuPn7qlUkEFDIkAZy59/Hue/H2Q2vU/JsvVhHWCQBL4F1ofEAt50il6ZxR1QfFK\n\
+9VGKDC4oOgm9DlxwwBoC2FjqmvQlqVV3kwIBAg==\n\
+-----END DH PARAMETERS-----\n";
+#endif
+
+static int mg_use_ca_cert(SSL_CTX *ctx, const char *cert) {
+ if (ctx == NULL) {
+ return -1;
+ } else if (cert == NULL || cert[0] == '\0') {
+ return 0;
+ }
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);
+ return SSL_CTX_load_verify_locations(ctx, cert, NULL) == 1 ? 0 : -2;
+}
+
+static int mg_use_cert(SSL_CTX *ctx, const char *pem_file) {
+ if (ctx == NULL) {
+ return -1;
+ } else if (pem_file == NULL || pem_file[0] == '\0') {
+ return 0;
+ } else if (SSL_CTX_use_certificate_file(ctx, pem_file, 1) == 0 ||
+ SSL_CTX_use_PrivateKey_file(ctx, pem_file, 1) == 0) {
+ return -2;
+ } else {
+#ifndef MG_DISABLE_PFS
+ BIO *bio = NULL;
+ DH *dh = NULL;
+
+ /* Try to read DH parameters from the cert/key file. */
+ bio = BIO_new_file(pem_file, "r");
+ if (bio != NULL) {
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ }
+ /*
+ * If there are no DH params in the file, fall back to hard-coded ones.
+ * Not ideal, but better than nothing.
+ */
+ if (dh == NULL) {
+ bio = BIO_new_mem_buf((void *) mg_s_default_dh_params, -1);
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ BIO_free(bio);
+ }
+ if (dh != NULL) {
+ SSL_CTX_set_tmp_dh(ctx, dh);
+ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
+ DH_free(dh);
+ }
+#endif
+ SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_CTX_use_certificate_chain_file(ctx, pem_file);
+ return 0;
+ }
+}
+
+/*
+ * Turn the connection into SSL mode.
+ * `cert` is the certificate file in PEM format. For listening connections,
+ * certificate file must contain private key and server certificate,
+ * concatenated. It may also contain DH params - these will be used for more
+ * secure key exchange. `ca_cert` is a certificate authority (CA) PEM file, and
+ * it is optional (can be set to NULL). If `ca_cert` is non-NULL, then
+ * the connection is so-called two-way-SSL: other peer's certificate is
+ * checked against the `ca_cert`.
+ *
+ * Handy OpenSSL command to generate test self-signed certificate:
+ *
+ * openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 999
+ *
+ * Return NULL on success, or error message on failure.
+ */
+const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
+ const char *ca_cert) {
+ const char *result = NULL;
+ DBG(("%p %s %s", nc, (cert ? cert : ""), (ca_cert ? ca_cert : "")));
+
+ if (nc->flags & MG_F_UDP) {
+ return "SSL for UDP is not supported";
+ }
+
+ if (nc->ssl != NULL) {
+ SSL_free(nc->ssl);
+ nc->ssl = NULL;
+ }
+ if (nc->ssl_ctx != NULL) {
+ SSL_CTX_free(nc->ssl_ctx);
+ nc->ssl_ctx = NULL;
+ }
+
+ if ((nc->flags & MG_F_LISTENING) &&
+ (nc->ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) {
+ result = "SSL_CTX_new() failed";
+ } else if (!(nc->flags & MG_F_LISTENING) &&
+ (nc->ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) {
+ result = "SSL_CTX_new() failed";
+ } else if (mg_use_cert(nc->ssl_ctx, cert) != 0) {
+ result = "Invalid ssl cert";
+ } else if (mg_use_ca_cert(nc->ssl_ctx, ca_cert) != 0) {
+ result = "Invalid CA cert";
+ } else if (!(nc->flags & MG_F_LISTENING) &&
+ (nc->ssl = SSL_new(nc->ssl_ctx)) == NULL) {
+ result = "SSL_new() failed";
+ } else if (!(nc->flags & MG_F_LISTENING) && nc->sock != INVALID_SOCKET) {
+ /*
+ * Socket is open here only if we are connecting to IP address
+ * and does not open if we are connecting using async DNS resolver
+ */
+ SSL_set_fd(nc->ssl, nc->sock);
+ }
+
+#ifndef MG_DISABLE_PFS
+ SSL_CTX_set_cipher_list(nc->ssl_ctx, mg_s_cipher_list);
+#endif
+ return result;
+}
+#endif /* MG_ENABLE_SSL */
+
+struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {
+ struct mg_add_sock_opts opts;
+ struct mg_connection *nc;
+ memset(&opts, 0, sizeof(opts));
+ nc = mg_create_connection(lc->mgr, lc->handler, opts);
+ if (nc == NULL) return NULL;
+ nc->listener = lc;
+ nc->proto_handler = lc->proto_handler;
+ nc->user_data = lc->user_data;
+ nc->recv_mbuf_limit = lc->recv_mbuf_limit;
+ mg_add_conn(nc->mgr, nc);
+ DBG(("%p %p %d %d, %p %p", lc, nc, nc->sock, (int) nc->flags, lc->ssl_ctx,
+ nc->ssl));
+ return nc;
+}
+
+void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
+ size_t sa_len) {
+ (void) sa_len;
+ nc->sa = *sa;
+ mg_call(nc, NULL, MG_EV_ACCEPT, &nc->sa);
+}
+
+void mg_send(struct mg_connection *nc, const void *buf, int len) {
+ nc->last_io_time = mg_time();
+ if (nc->flags & MG_F_UDP) {
+ mg_if_udp_send(nc, buf, len);
+ } else {
+ mg_if_tcp_send(nc, buf, len);
+ }
+#if !defined(NO_LIBC) && !defined(MG_DISABLE_HEXDUMP)
+ if (nc->mgr && nc->mgr->hexdump_file != NULL) {
+ mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_SEND);
+ }
+#endif
+}
+
+void mg_if_sent_cb(struct mg_connection *nc, int num_sent) {
+ if (num_sent < 0) {
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+ mg_call(nc, NULL, MG_EV_SEND, &num_sent);
+}
+
+static void mg_recv_common(struct mg_connection *nc, void *buf, int len) {
+ DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len));
+ if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {
+ DBG(("%p discarded %d bytes", nc, len));
+ /*
+ * This connection will not survive next poll. Do not deliver events,
+ * send data to /dev/null without acking.
+ */
+ MG_FREE(buf);
+ return;
+ }
+ nc->last_io_time = mg_time();
+ if (nc->recv_mbuf.len == 0) {
+ /* Adopt buf as recv_mbuf's backing store. */
+ mbuf_free(&nc->recv_mbuf);
+ nc->recv_mbuf.buf = (char *) buf;
+ nc->recv_mbuf.size = nc->recv_mbuf.len = len;
+ } else {
+ mbuf_append(&nc->recv_mbuf, buf, len);
+ MG_FREE(buf);
+ }
+ mg_call(nc, NULL, MG_EV_RECV, &len);
+}
+
+void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len) {
+ mg_recv_common(nc, buf, len);
+}
+
+void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
+ union socket_address *sa, size_t sa_len) {
+ assert(nc->flags & MG_F_UDP);
+ DBG(("%p %u", nc, (unsigned int) len));
+ if (nc->flags & MG_F_LISTENING) {
+ struct mg_connection *lc = nc;
+ /*
+ * Do we have an existing connection for this source?
+ * This is very inefficient for long connection lists.
+ */
+ for (nc = mg_next(lc->mgr, NULL); nc != NULL; nc = mg_next(lc->mgr, nc)) {
+ if (memcmp(&nc->sa.sa, &sa->sa, sa_len) == 0 && nc->listener == lc) {
+ break;
+ }
+ }
+ if (nc == NULL) {
+ struct mg_add_sock_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ /* Create fake connection w/out sock initialization */
+ nc = mg_create_connection_base(lc->mgr, lc->handler, opts);
+ if (nc != NULL) {
+ nc->sock = lc->sock;
+ nc->listener = lc;
+ nc->sa = *sa;
+ nc->proto_handler = lc->proto_handler;
+ nc->user_data = lc->user_data;
+ nc->recv_mbuf_limit = lc->recv_mbuf_limit;
+ nc->flags = MG_F_UDP;
+ mg_add_conn(lc->mgr, nc);
+ mg_call(nc, NULL, MG_EV_ACCEPT, &nc->sa);
+ } else {
+ DBG(("OOM"));
+ /* No return here, we still need to drop on the floor */
+ }
+ }
+ }
+ if (nc != NULL) {
+ mg_recv_common(nc, buf, len);
+ } else {
+ /* Drop on the floor. */
+ MG_FREE(buf);
+ mg_if_recved(nc, len);
+ }
+}
+
+/*
+ * Schedules an async connect for a resolved address and proto.
+ * Called from two places: `mg_connect_opt()` and from async resolver.
+ * When called from the async resolver, it must trigger `MG_EV_CONNECT` event
+ * with a failure flag to indicate connection failure.
+ */
+MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,
+ int proto,
+ union socket_address *sa) {
+ DBG(("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp",
+ inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));
+
+ nc->flags |= MG_F_CONNECTING;
+ if (proto == SOCK_DGRAM) {
+ mg_if_connect_udp(nc);
+ } else {
+ mg_if_connect_tcp(nc, sa);
+ }
+ mg_add_conn(nc->mgr, nc);
+ return nc;
+}
+
+void mg_if_connect_cb(struct mg_connection *nc, int err) {
+ DBG(("%p connect, err=%d", nc, err));
+ nc->flags &= ~MG_F_CONNECTING;
+ if (err != 0) {
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+ mg_call(nc, NULL, MG_EV_CONNECT, &err);
+}
+
+#ifndef MG_DISABLE_RESOLVER
+/*
+ * Callback for the async resolver on mg_connect_opt() call.
+ * Main task of this function is to trigger MG_EV_CONNECT event with
+ * either failure (and dealloc the connection)
+ * or success (and proceed with connect()
+ */
+static void resolve_cb(struct mg_dns_message *msg, void *data,
+ enum mg_resolve_err e) {
+ struct mg_connection *nc = (struct mg_connection *) data;
+ int i;
+ int failure = -1;
+
+ nc->flags &= ~MG_F_RESOLVING;
+ if (msg != NULL) {
+ /*
+ * Take the first DNS A answer and run...
+ */
+ for (i = 0; i < msg->num_answers; i++) {
+ if (msg->answers[i].rtype == MG_DNS_A_RECORD) {
+ /*
+ * Async resolver guarantees that there is at least one answer.
+ * TODO(lsm): handle IPv6 answers too
+ */
+ mg_dns_parse_record_data(msg, &msg->answers[i], &nc->sa.sin.sin_addr,
+ 4);
+ mg_do_connect(nc, nc->flags & MG_F_UDP ? SOCK_DGRAM : SOCK_STREAM,
+ &nc->sa);
+ return;
+ }
+ }
+ }
+
+ if (e == MG_RESOLVE_TIMEOUT) {
+ double now = mg_time();
+ mg_call(nc, NULL, MG_EV_TIMER, &now);
+ }
+
+ /*
+ * If we get there was no MG_DNS_A_RECORD in the answer
+ */
+ mg_call(nc, NULL, MG_EV_CONNECT, &failure);
+ mg_call(nc, NULL, MG_EV_CLOSE, NULL);
+ mg_destroy_conn(nc);
+}
+#endif
+
+struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,
+ mg_event_handler_t callback) {
+ struct mg_connect_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ return mg_connect_opt(mgr, address, callback, opts);
+}
+
+struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
+ mg_event_handler_t callback,
+ struct mg_connect_opts opts) {
+ struct mg_connection *nc = NULL;
+ int proto, rc;
+ struct mg_add_sock_opts add_sock_opts;
+ char host[MG_MAX_HOST_LEN];
+
+ MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
+
+ if ((nc = mg_create_connection(mgr, callback, add_sock_opts)) == NULL) {
+ return NULL;
+ } else if ((rc = mg_parse_address(address, &nc->sa, &proto, host,
+ sizeof(host))) < 0) {
+ /* Address is malformed */
+ MG_SET_PTRPTR(opts.error_string, "cannot parse address");
+ mg_destroy_conn(nc);
+ return NULL;
+ }
+ nc->flags |= opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK;
+ nc->flags |= (proto == SOCK_DGRAM) ? MG_F_UDP : 0;
+ nc->user_data = opts.user_data;
+
+#ifdef MG_ENABLE_SSL
+ if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL) {
+ const char *err = mg_set_ssl(nc, opts.ssl_cert, opts.ssl_ca_cert);
+ if (err != NULL) {
+ MG_SET_PTRPTR(opts.error_string, err);
+ mg_destroy_conn(nc);
+ return NULL;
+ }
+ if (opts.ssl_ca_cert != NULL && (opts.ssl_server_name == NULL ||
+ strcmp(opts.ssl_server_name, "*") != 0)) {
+ if (opts.ssl_server_name == NULL) opts.ssl_server_name = host;
+#ifdef SSL_KRYPTON
+ SSL_CTX_kr_set_verify_name(nc->ssl_ctx, opts.ssl_server_name);
+#else
+ /* TODO(rojer): Implement server name verification on OpenSSL. */
+ MG_SET_PTRPTR(opts.error_string,
+ "Server name verification requested but is not supported");
+ mg_destroy_conn(nc);
+ return NULL;
+#endif /* SSL_KRYPTON */
+ }
+ }
+#endif /* MG_ENABLE_SSL */
+
+ if (rc == 0) {
+#ifndef MG_DISABLE_RESOLVER
+ /*
+ * DNS resolution is required for host.
+ * mg_parse_address() fills port in nc->sa, which we pass to resolve_cb()
+ */
+ struct mg_connection *dns_conn = NULL;
+ struct mg_resolve_async_opts o;
+ memset(&o, 0, sizeof(o));
+ o.dns_conn = &dns_conn;
+ if (mg_resolve_async_opt(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc,
+ o) != 0) {
+ MG_SET_PTRPTR(opts.error_string, "cannot schedule DNS lookup");
+ mg_destroy_conn(nc);
+ return NULL;
+ }
+ nc->priv_2 = dns_conn;
+ nc->flags |= MG_F_RESOLVING;
+ return nc;
+#else
+ MG_SET_PTRPTR(opts.error_string, "Resolver is disabled");
+ mg_destroy_conn(nc);
+ return NULL;
+#endif
+ } else {
+ /* Address is parsed and resolved to IP. proceed with connect() */
+ return mg_do_connect(nc, proto, &nc->sa);
+ }
+}
+
+struct mg_connection *mg_bind(struct mg_mgr *srv, const char *address,
+ mg_event_handler_t event_handler) {
+ struct mg_bind_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ return mg_bind_opt(srv, address, event_handler, opts);
+}
+
+struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
+ mg_event_handler_t callback,
+ struct mg_bind_opts opts) {
+ union socket_address sa;
+ struct mg_connection *nc = NULL;
+ int proto, rc;
+ struct mg_add_sock_opts add_sock_opts;
+ char host[MG_MAX_HOST_LEN];
+
+ MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);
+
+ if (mg_parse_address(address, &sa, &proto, host, sizeof(host)) <= 0) {
+ MG_SET_PTRPTR(opts.error_string, "cannot parse address");
+ return NULL;
+ }
+
+ nc = mg_create_connection(mgr, callback, add_sock_opts);
+ if (nc == NULL) {
+ return NULL;
+ }
+
+ nc->sa = sa;
+ nc->flags |= MG_F_LISTENING;
+ if (proto == SOCK_DGRAM) {
+ nc->flags |= MG_F_UDP;
+ rc = mg_if_listen_udp(nc, &nc->sa);
+ } else {
+ rc = mg_if_listen_tcp(nc, &nc->sa);
+ }
+ if (rc != 0) {
+ DBG(("Failed to open listener: %d", rc));
+ MG_SET_PTRPTR(opts.error_string, "failed to open listener");
+ mg_destroy_conn(nc);
+ return NULL;
+ }
+#ifdef MG_ENABLE_SSL
+ if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL) {
+ const char *err = mg_set_ssl(nc, opts.ssl_cert, opts.ssl_ca_cert);
+ if (err != NULL) {
+ MG_SET_PTRPTR(opts.error_string, err);
+ mg_destroy_conn(nc);
+ return NULL;
+ }
+ }
+#endif /* MG_ENABLE_SSL */
+ mg_add_conn(nc->mgr, nc);
+
+ return nc;
+}
+
+struct mg_connection *mg_next(struct mg_mgr *s, struct mg_connection *conn) {
+ return conn == NULL ? s->active_connections : conn->next;
+}
+
+#ifndef MG_DISABLE_SOCKETPAIR
+void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data,
+ size_t len) {
+ struct ctl_msg ctl_msg;
+
+ /*
+ * Mongoose manager has a socketpair, `struct mg_mgr::ctl`,
+ * where `mg_broadcast()` pushes the message.
+ * `mg_mgr_poll()` wakes up, reads a message from the socket pair, and calls
+ * specified callback for each connection. Thus the callback function executes
+ * in event manager thread.
+ */
+ if (mgr->ctl[0] != INVALID_SOCKET && data != NULL &&
+ len < sizeof(ctl_msg.message)) {
+ size_t dummy;
+
+ ctl_msg.callback = cb;
+ memcpy(ctl_msg.message, data, len);
+ dummy = MG_SEND_FUNC(mgr->ctl[0], (char *) &ctl_msg,
+ offsetof(struct ctl_msg, message) + len, 0);
+ dummy = MG_RECV_FUNC(mgr->ctl[0], (char *) &len, 1, 0);
+ (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */
+ }
+}
+#endif /* MG_DISABLE_SOCKETPAIR */
+
+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) | d;
+ *mask = slash ? 0xffffffffU << (32 - slash) : 0;
+ }
+
+ return len;
+}
+
+int mg_check_ip_acl(const char *acl, uint32_t remote_ip) {
+ int allowed, flag;
+ uint32_t net, mask;
+ struct mg_str vec;
+
+ /* If any ACL is set, deny by default */
+ allowed = (acl == NULL || *acl == '\0') ? '+' : '-';
+
+ while ((acl = mg_next_comma_list_entry(acl, &vec, NULL)) != NULL) {
+ flag = vec.p[0];
+ if ((flag != '+' && flag != '-') ||
+ parse_net(&vec.p[1], &net, &mask) == 0) {
+ return -1;
+ }
+
+ if (net == (remote_ip & mask)) {
+ allowed = flag;
+ }
+ }
+
+ DBG(("%08x %c", remote_ip, allowed));
+ return allowed == '+';
+}
+
+/* Move data from one connection to another */
+void mg_forward(struct mg_connection *from, struct mg_connection *to) {
+ mg_send(to, from->recv_mbuf.buf, from->recv_mbuf.len);
+ mbuf_remove(&from->recv_mbuf, from->recv_mbuf.len);
+}
+
+double mg_set_timer(struct mg_connection *c, double timestamp) {
+ double result = c->ev_timer_time;
+ c->ev_timer_time = timestamp;
+ /*
+ * If this connection is resolving, it's not in the list of active
+ * connections, so not processed yet. It has a DNS resolver connection
+ * linked to it. Set up a timer for the DNS connection.
+ */
+ DBG(("%p %p %d -> %lu", c, c->priv_2, c->flags & MG_F_RESOLVING,
+ (unsigned long) timestamp));
+ if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {
+ ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp;
+ }
+ return result;
+}
+
+struct mg_connection *mg_add_sock_opt(struct mg_mgr *s, sock_t sock,
+ mg_event_handler_t callback,
+ struct mg_add_sock_opts opts) {
+ struct mg_connection *nc = mg_create_connection_base(s, callback, opts);
+ if (nc != NULL) {
+ mg_sock_set(nc, sock);
+ mg_add_conn(nc->mgr, nc);
+ }
+ return nc;
+}
+
+struct mg_connection *mg_add_sock(struct mg_mgr *s, sock_t sock,
+ mg_event_handler_t callback) {
+ struct mg_add_sock_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ return mg_add_sock_opt(s, sock, callback, opts);
+}
+
+double mg_time() {
+ return cs_time();
+}
+#ifdef MG_MODULE_LINES
+#line 1 "./src/net_if_socket.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef MG_DISABLE_SOCKET_IF
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/util.h" */
+
+#define MG_TCP_RECV_BUFFER_SIZE 1024
+#define MG_UDP_RECV_BUFFER_SIZE 1500
+
+static sock_t mg_open_listening_socket(union socket_address *sa, int proto);
+#ifdef MG_ENABLE_SSL
+static void mg_ssl_begin(struct mg_connection *nc);
+static int mg_ssl_err(struct mg_connection *conn, int res);
+#endif
+
+void mg_set_non_blocking_mode(sock_t sock) {
+#ifdef _WIN32
+ unsigned long on = 1;
+ ioctlsocket(sock, FIONBIO, &on);
+#elif defined(MG_SOCKET_SIMPLELINK)
+ SlSockNonblocking_t opt;
+ opt.NonblockingEnabled = 1;
+ sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_NONBLOCKING, &opt, sizeof(opt));
+#else
+ int flags = fcntl(sock, F_GETFL, 0);
+ fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+#endif
+}
+
+int mg_is_error(int n) {
+#ifdef MG_SOCKET_SIMPLELINK
+ DBG(("n = %d, errno = %d", n, errno));
+ if (n < 0) errno = n;
+#endif
+ return n == 0 || (n < 0 && errno != EINTR && errno != EINPROGRESS &&
+ errno != EAGAIN && errno != EWOULDBLOCK
+#ifdef MG_SOCKET_SIMPLELINK
+ && errno != SL_EALREADY
+#endif
+#ifdef _WIN32
+ && WSAGetLastError() != WSAEINTR &&
+ WSAGetLastError() != WSAEWOULDBLOCK
+#endif
+ );
+}
+
+void mg_if_connect_tcp(struct mg_connection *nc,
+ const union socket_address *sa) {
+ int rc;
+ nc->sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (nc->sock == INVALID_SOCKET) {
+ nc->err = errno ? errno : 1;
+ return;
+ }
+#if !defined(MG_SOCKET_SIMPLELINK) && !defined(MG_ESP8266)
+ mg_set_non_blocking_mode(nc->sock);
+#endif
+ rc = connect(nc->sock, &sa->sa, sizeof(sa->sin));
+ nc->err = mg_is_error(rc) ? errno : 0;
+ DBG(("%p sock %d err %d", nc, nc->sock, nc->err));
+}
+
+void mg_if_connect_udp(struct mg_connection *nc) {
+ nc->sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (nc->sock == INVALID_SOCKET) {
+ nc->err = errno ? errno : 1;
+ return;
+ }
+ nc->err = 0;
+}
+
+int mg_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {
+ sock_t sock = mg_open_listening_socket(sa, SOCK_STREAM);
+ if (sock == INVALID_SOCKET) {
+ return (errno ? errno : 1);
+ }
+ mg_sock_set(nc, sock);
+ return 0;
+}
+
+int mg_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {
+ sock_t sock = mg_open_listening_socket(sa, SOCK_DGRAM);
+ if (sock == INVALID_SOCKET) return (errno ? errno : 1);
+ mg_sock_set(nc, sock);
+ return 0;
+}
+
+void mg_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {
+ mbuf_append(&nc->send_mbuf, buf, len);
+}
+
+void mg_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) {
+ mbuf_append(&nc->send_mbuf, buf, len);
+}
+
+void mg_if_recved(struct mg_connection *nc, size_t len) {
+ (void) nc;
+ (void) len;
+}
+
+int mg_if_create_conn(struct mg_connection *nc) {
+ (void) nc;
+ return 1;
+}
+
+void mg_if_destroy_conn(struct mg_connection *nc) {
+ if (nc->sock == INVALID_SOCKET) return;
+ if (!(nc->flags & MG_F_UDP)) {
+ closesocket(nc->sock);
+ } else {
+ /* Only close outgoing UDP sockets or listeners. */
+ if (nc->listener == NULL) closesocket(nc->sock);
+ }
+ /*
+ * avoid users accidentally double close a socket
+ * because it can lead to difficult to debug situations.
+ * It would happen only if reusing a destroyed mg_connection
+ * but it's not always possible to run the code through an
+ * address sanitizer.
+ */
+ nc->sock = INVALID_SOCKET;
+}
+
+static int mg_accept_conn(struct mg_connection *lc) {
+ struct mg_connection *nc;
+ union socket_address sa;
+ socklen_t sa_len = sizeof(sa);
+ /* NOTE(lsm): on Windows, sock is always > FD_SETSIZE */
+ sock_t sock = accept(lc->sock, &sa.sa, &sa_len);
+ if (sock == INVALID_SOCKET) {
+ if (mg_is_error(-1)) { DBG(("%p: failed to accept: %d", lc, errno)) };
+ return 0;
+ }
+ nc = mg_if_accept_new_conn(lc);
+ if (nc == NULL) {
+ closesocket(sock);
+ return 0;
+ }
+ DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr),
+ ntohs(sa.sin.sin_port)));
+ mg_sock_set(nc, sock);
+#ifdef MG_ENABLE_SSL
+ if (lc->ssl_ctx != NULL) {
+ nc->ssl = SSL_new(lc->ssl_ctx);
+ if (nc->ssl == NULL || SSL_set_fd(nc->ssl, sock) != 1) {
+ DBG(("SSL error"));
+ mg_close_conn(nc);
+ }
+ } else
+#endif
+ {
+ mg_if_accept_tcp_cb(nc, &sa, sa_len);
+ }
+ return 1;
+}
+
+/* 'sa' must be an initialized address to bind to */
+static sock_t mg_open_listening_socket(union socket_address *sa, int proto) {
+ socklen_t sa_len =
+ (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);
+ sock_t sock = INVALID_SOCKET;
+#if !defined(MG_SOCKET_SIMPLELINK) && !defined(MG_LWIP)
+ int on = 1;
+#endif
+
+ if ((sock = socket(sa->sa.sa_family, proto, 0)) != INVALID_SOCKET &&
+#if !defined(MG_SOCKET_SIMPLELINK) && \
+ !defined(MG_LWIP) /* SimpleLink and LWIP don't support either */
+#if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE)
+ /* "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" http://goo.gl/RmrFTm */
+ !setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *) &on,
+ sizeof(on)) &&
+#endif
+
+#if !defined(_WIN32) || !defined(SO_EXCLUSIVEADDRUSE)
+ /*
+ * SO_RESUSEADDR is not enabled on Windows because the semantics of
+ * SO_REUSEADDR on UNIX and Windows is different. On Windows,
+ * SO_REUSEADDR allows to bind a socket to a port without error even if
+ * the port is already open by another program. This is not the behavior
+ * SO_REUSEADDR was designed for, and leads to hard-to-track failure
+ * scenarios. Therefore, SO_REUSEADDR was disabled on Windows unless
+ * SO_EXCLUSIVEADDRUSE is supported and set on a socket.
+ */
+ !setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) &&
+#endif
+#endif /* !MG_SOCKET_SIMPLELINK && !MG_LWIP */
+
+ !bind(sock, &sa->sa, sa_len) &&
+ (proto == SOCK_DGRAM || listen(sock, SOMAXCONN) == 0)) {
+#if !defined(MG_SOCKET_SIMPLELINK) && \
+ !defined(MG_LWIP) /* TODO(rojer): Fix this. */
+ mg_set_non_blocking_mode(sock);
+ /* In case port was set to 0, get the real port number */
+ (void) getsockname(sock, &sa->sa, &sa_len);
+#endif
+ } else if (sock != INVALID_SOCKET) {
+ closesocket(sock);
+ sock = INVALID_SOCKET;
+ }
+
+ return sock;
+}
+
+static void mg_write_to_socket(struct mg_connection *nc) {
+ struct mbuf *io = &nc->send_mbuf;
+ int n = 0;
+
+#ifdef MG_LWIP
+ /* With LWIP we don't know if the socket is ready */
+ if (io->len == 0) return;
+#endif
+
+ assert(io->len > 0);
+
+ if (nc->flags & MG_F_UDP) {
+ int n =
+ sendto(nc->sock, io->buf, io->len, 0, &nc->sa.sa, sizeof(nc->sa.sin));
+ DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, errno,
+ inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port)));
+ if (n > 0) {
+ mbuf_remove(io, n);
+ }
+ mg_if_sent_cb(nc, n);
+ return;
+ }
+
+#ifdef MG_ENABLE_SSL
+ if (nc->ssl != NULL) {
+ if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {
+ n = SSL_write(nc->ssl, io->buf, io->len);
+ DBG(("%p %d bytes -> %d (SSL)", nc, n, nc->sock));
+ if (n <= 0) {
+ int ssl_err = mg_ssl_err(nc, n);
+ if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) {
+ return; /* Call us again */
+ }
+ } else {
+ /* Successful SSL operation, clear off SSL wait flags */
+ nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
+ }
+ } else {
+ mg_ssl_begin(nc);
+ return;
+ }
+ } else
+#endif
+ {
+ n = (int) MG_SEND_FUNC(nc->sock, io->buf, io->len, 0);
+ DBG(("%p %d bytes -> %d", nc, n, nc->sock));
+ if (n < 0 && !mg_is_error(n)) return;
+ }
+
+ if (n > 0) {
+ mbuf_remove(io, n);
+ }
+ mg_if_sent_cb(nc, n);
+}
+
+MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {
+ size_t avail;
+ if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;
+ avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;
+ return avail > max ? max : avail;
+}
+
+static void mg_read_from_socket(struct mg_connection *conn) {
+ int n = 0;
+ char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
+
+ if (buf == NULL) {
+ DBG(("OOM"));
+ return;
+ }
+
+#ifdef MG_ENABLE_SSL
+ if (conn->ssl != NULL) {
+ if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {
+ /* SSL library may have more bytes ready to read then we ask to read.
+ * Therefore, read in a loop until we read everything. Without the loop,
+ * we skip to the next select() cycle which can just timeout. */
+ while ((n = SSL_read(conn->ssl, buf, MG_TCP_RECV_BUFFER_SIZE)) > 0) {
+ DBG(("%p %d bytes <- %d (SSL)", conn, n, conn->sock));
+ mg_if_recv_tcp_cb(conn, buf, n);
+ buf = NULL;
+ if (conn->flags & MG_F_CLOSE_IMMEDIATELY) break;
+ /* buf has been freed, we need a new one. */
+ buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE);
+ if (buf == NULL) break;
+ }
+ MG_FREE(buf);
+ mg_ssl_err(conn, n);
+ } else {
+ MG_FREE(buf);
+ mg_ssl_begin(conn);
+ return;
+ }
+ } else
+#endif
+ {
+ n = (int) MG_RECV_FUNC(conn->sock, buf,
+ recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0);
+ DBG(("%p %d bytes (PLAIN) <- %d", conn, n, conn->sock));
+ if (n > 0) {
+ mg_if_recv_tcp_cb(conn, buf, n);
+ } else {
+ MG_FREE(buf);
+ }
+ if (n == 0) {
+ /* Orderly shutdown of the socket, try flushing output. */
+ conn->flags |= MG_F_SEND_AND_CLOSE;
+ } else if (mg_is_error(n)) {
+ conn->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+ }
+}
+
+static int mg_recvfrom(struct mg_connection *nc, union socket_address *sa,
+ socklen_t *sa_len, char **buf) {
+ int n;
+ *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE);
+ if (*buf == NULL) {
+ DBG(("Out of memory"));
+ return -ENOMEM;
+ }
+ n = recvfrom(nc->sock, *buf, MG_UDP_RECV_BUFFER_SIZE, 0, &sa->sa, sa_len);
+ if (n <= 0) {
+ DBG(("%p recvfrom: %s", nc, strerror(errno)));
+ MG_FREE(*buf);
+ }
+ return n;
+}
+
+static void mg_handle_udp_read(struct mg_connection *nc) {
+ char *buf = NULL;
+ union socket_address sa;
+ socklen_t sa_len = sizeof(sa);
+ int n = mg_recvfrom(nc, &sa, &sa_len, &buf);
+ DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr),
+ ntohs(nc->sa.sin.sin_port)));
+ mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len);
+}
+
+#ifdef MG_ENABLE_SSL
+static int mg_ssl_err(struct mg_connection *conn, int res) {
+ int ssl_err = SSL_get_error(conn->ssl, res);
+ DBG(("%p %d -> %d", conn, res, ssl_err));
+ if (ssl_err == SSL_ERROR_WANT_READ) {
+ conn->flags |= MG_F_WANT_READ;
+ } else if (ssl_err == SSL_ERROR_WANT_WRITE) {
+ conn->flags |= MG_F_WANT_WRITE;
+ } else {
+ /* There could be an alert to deliver. Try our best. */
+ SSL_write(conn->ssl, "", 0);
+ conn->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+ return ssl_err;
+}
+
+static void mg_ssl_begin(struct mg_connection *nc) {
+ int server_side = (nc->listener != NULL);
+ int res = server_side ? SSL_accept(nc->ssl) : SSL_connect(nc->ssl);
+ DBG(("%p %d res %d %d", nc, server_side, res, errno));
+
+ if (res == 1) {
+ nc->flags |= MG_F_SSL_HANDSHAKE_DONE;
+ nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);
+
+ if (server_side) {
+ union socket_address sa;
+ socklen_t sa_len = sizeof(sa);
+ (void) getpeername(nc->sock, &sa.sa, &sa_len);
+ mg_if_accept_tcp_cb(nc, &sa, sa_len);
+ } else {
+ mg_if_connect_cb(nc, 0);
+ }
+ } else {
+ int ssl_err = mg_ssl_err(nc, res);
+ if (ssl_err != SSL_ERROR_WANT_READ && ssl_err != SSL_ERROR_WANT_WRITE) {
+ if (!server_side) {
+ mg_if_connect_cb(nc, ssl_err);
+ }
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ }
+ }
+}
+#endif /* MG_ENABLE_SSL */
+
+#define _MG_F_FD_CAN_READ 1
+#define _MG_F_FD_CAN_WRITE 1 << 1
+#define _MG_F_FD_ERROR 1 << 2
+
+void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {
+ DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock,
+ fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+
+ if (nc->flags & MG_F_CONNECTING) {
+ if (fd_flags != 0) {
+ int err = 0;
+#if !defined(MG_SOCKET_SIMPLELINK) && !defined(MG_ESP8266)
+ if (!(nc->flags & MG_F_UDP)) {
+ socklen_t len = sizeof(err);
+ int ret =
+ getsockopt(nc->sock, SOL_SOCKET, SO_ERROR, (char *) &err, &len);
+ if (ret != 0) err = 1;
+ }
+#else
+/* On SimpleLink and ESP8266 we use blocking connect. If we got as far as
+ * this, it means connect() was successful.
+ * TODO(rojer): Figure out why it fails where blocking succeeds.
+ */
+#endif
+#ifdef MG_ENABLE_SSL
+ if (nc->ssl != NULL && err == 0) {
+ SSL_set_fd(nc->ssl, nc->sock);
+ mg_ssl_begin(nc);
+ } else {
+ mg_if_connect_cb(nc, err);
+ }
+#else
+ mg_if_connect_cb(nc, err);
+#endif
+ } else if (nc->err != 0) {
+ mg_if_connect_cb(nc, nc->err);
+ }
+ }
+
+ if (fd_flags & _MG_F_FD_CAN_READ) {
+ if (nc->flags & MG_F_UDP) {
+ mg_handle_udp_read(nc);
+ } else {
+ if (nc->flags & MG_F_LISTENING) {
+ /*
+ * We're not looping here, and accepting just one connection at
+ * a time. The reason is that eCos does not respect non-blocking
+ * flag on a listening socket and hangs in a loop.
+ */
+ if (fd_flags & _MG_F_FD_CAN_READ) mg_accept_conn(nc);
+ } else {
+ mg_read_from_socket(nc);
+ }
+ }
+ }
+
+ if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
+ if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) {
+ mg_write_to_socket(nc);
+ }
+
+ if (!(fd_flags & (_MG_F_FD_CAN_READ | _MG_F_FD_CAN_WRITE))) {
+ mg_if_poll(nc, now);
+ }
+ mg_if_timer(nc, now);
+ }
+
+ DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags,
+ (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));
+}
+
+#ifndef MG_DISABLE_SOCKETPAIR
+static void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) {
+ struct ctl_msg ctl_msg;
+ int len =
+ (int) MG_RECV_FUNC(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0);
+ size_t dummy = MG_SEND_FUNC(mgr->ctl[1], ctl_msg.message, 1, 0);
+ DBG(("read %d from ctl socket", len));
+ (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */
+ if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {
+ struct mg_connection *nc;
+ for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
+ ctl_msg.callback(nc, MG_EV_POLL, ctl_msg.message);
+ }
+ }
+}
+#endif
+
+/* Associate a socket to a connection. */
+void mg_sock_set(struct mg_connection *nc, sock_t sock) {
+ mg_set_non_blocking_mode(sock);
+ mg_set_close_on_exec(sock);
+ nc->sock = sock;
+ DBG(("%p %d", nc, sock));
+}
+
+void mg_ev_mgr_init(struct mg_mgr *mgr) {
+ (void) mgr;
+ DBG(("%p using select()", mgr));
+#ifndef MG_DISABLE_SOCKETPAIR
+ do {
+ mg_socketpair(mgr->ctl, SOCK_DGRAM);
+ } while (mgr->ctl[0] == INVALID_SOCKET);
+#endif
+}
+
+void mg_ev_mgr_free(struct mg_mgr *mgr) {
+ (void) mgr;
+}
+
+void mg_ev_mgr_add_conn(struct mg_connection *nc) {
+ (void) nc;
+}
+
+void mg_ev_mgr_remove_conn(struct mg_connection *nc) {
+ (void) nc;
+}
+
+void mg_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
+ if (sock != INVALID_SOCKET
+#ifdef __unix__
+ && sock < FD_SETSIZE
+#endif
+ ) {
+ FD_SET(sock, set);
+ if (*max_fd == INVALID_SOCKET || sock > *max_fd) {
+ *max_fd = sock;
+ }
+ }
+}
+
+time_t mg_mgr_poll(struct mg_mgr *mgr, int timeout_ms) {
+ double now = mg_time();
+ double min_timer;
+ struct mg_connection *nc, *tmp;
+ struct timeval tv;
+ fd_set read_set, write_set, err_set;
+ sock_t max_fd = INVALID_SOCKET;
+ int num_fds, num_ev, num_timers = 0;
+#ifdef __unix__
+ int try_dup = 1;
+#endif
+
+ FD_ZERO(&read_set);
+ FD_ZERO(&write_set);
+ FD_ZERO(&err_set);
+#ifndef MG_DISABLE_SOCKETPAIR
+ mg_add_to_set(mgr->ctl[1], &read_set, &max_fd);
+#endif
+
+ /*
+ * Note: it is ok to have connections with sock == INVALID_SOCKET in the list,
+ * e.g. timer-only "connections".
+ */
+ min_timer = 0;
+ for (nc = mgr->active_connections, num_fds = 0; nc != NULL; nc = tmp) {
+ tmp = nc->next;
+
+ if (nc->sock != INVALID_SOCKET) {
+ num_fds++;
+
+#ifdef __unix__
+ /* A hack to make sure all our file descriptos fit into FD_SETSIZE. */
+ if (nc->sock >= FD_SETSIZE && try_dup) {
+ int new_sock = dup(nc->sock);
+ if (new_sock >= 0 && new_sock < FD_SETSIZE) {
+ closesocket(nc->sock);
+ DBG(("new sock %d -> %d", nc->sock, new_sock));
+ nc->sock = new_sock;
+ } else {
+ try_dup = 0;
+ }
+ }
+#endif
+
+ if (!(nc->flags & MG_F_WANT_WRITE) &&
+ nc->recv_mbuf.len < nc->recv_mbuf_limit &&
+ (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {
+ mg_add_to_set(nc->sock, &read_set, &max_fd);
+ }
+
+ if (((nc->flags & MG_F_CONNECTING) && !(nc->flags & MG_F_WANT_READ)) ||
+ (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING))) {
+ mg_add_to_set(nc->sock, &write_set, &max_fd);
+ mg_add_to_set(nc->sock, &err_set, &max_fd);
+ }
+ }
+
+ if (nc->ev_timer_time > 0) {
+ if (num_timers == 0 || nc->ev_timer_time < min_timer) {
+ min_timer = nc->ev_timer_time;
+ }
+ num_timers++;
+ }
+ }
+
+ /*
+ * If there is a timer to be fired earlier than the requested timeout,
+ * adjust the timeout.
+ */
+ if (num_timers > 0) {
+ double timer_timeout_ms = (min_timer - mg_time()) * 1000 + 1 /* rounding */;
+ if (timer_timeout_ms < timeout_ms) {
+ timeout_ms = timer_timeout_ms;
+ }
+ }
+ if (timeout_ms < 0) timeout_ms = 0;
+
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+
+ num_ev = select((int) max_fd + 1, &read_set, &write_set, &err_set, &tv);
+ now = mg_time();
+ DBG(("select @ %ld num_ev=%d of %d, timeout=%d", (long) now, num_ev, num_fds,
+ timeout_ms));
+
+#ifndef MG_DISABLE_SOCKETPAIR
+ if (num_ev > 0 && mgr->ctl[1] != INVALID_SOCKET &&
+ FD_ISSET(mgr->ctl[1], &read_set)) {
+ mg_mgr_handle_ctl_sock(mgr);
+ }
+#endif
+
+ for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
+ int fd_flags = 0;
+ if (nc->sock != INVALID_SOCKET) {
+ if (num_ev > 0) {
+ fd_flags = (FD_ISSET(nc->sock, &read_set) &&
+ (!(nc->flags & MG_F_UDP) || nc->listener == NULL)
+ ? _MG_F_FD_CAN_READ
+ : 0) |
+ (FD_ISSET(nc->sock, &write_set) ? _MG_F_FD_CAN_WRITE : 0) |
+ (FD_ISSET(nc->sock, &err_set) ? _MG_F_FD_ERROR : 0);
+ }
+#ifdef MG_SOCKET_SIMPLELINK
+ /* SimpleLink does not report UDP sockets as writeable. */
+ if (nc->flags & MG_F_UDP &&
+ (nc->send_mbuf.len > 0 || nc->flags & MG_F_CONNECTING)) {
+ fd_flags |= _MG_F_FD_CAN_WRITE;
+ }
+#endif
+#ifdef MG_LWIP
+ /* With LWIP socket emulation layer, we don't get write events */
+ fd_flags |= _MG_F_FD_CAN_WRITE;
+#endif
+ }
+ tmp = nc->next;
+ mg_mgr_handle_conn(nc, fd_flags, now);
+ }
+
+ for (nc = mgr->active_connections; nc != NULL; nc = tmp) {
+ tmp = nc->next;
+ if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||
+ (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {
+ mg_close_conn(nc);
+ }
+ }
+
+ return now;
+}
+
+#ifndef MG_DISABLE_SOCKETPAIR
+int mg_socketpair(sock_t sp[2], int sock_type) {
+ union socket_address sa;
+ sock_t sock;
+ socklen_t len = sizeof(sa.sin);
+ int ret = 0;
+
+ sock = sp[0] = sp[1] = INVALID_SOCKET;
+
+ (void) memset(&sa, 0, sizeof(sa));
+ sa.sin.sin_family = AF_INET;
+ sa.sin.sin_port = htons(0);
+ sa.sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
+
+ if ((sock = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {
+ } else if (bind(sock, &sa.sa, len) != 0) {
+ } else if (sock_type == SOCK_STREAM && listen(sock, 1) != 0) {
+ } else if (getsockname(sock, &sa.sa, &len) != 0) {
+ } else if ((sp[0] = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {
+ } else if (connect(sp[0], &sa.sa, len) != 0) {
+ } else if (sock_type == SOCK_DGRAM &&
+ (getsockname(sp[0], &sa.sa, &len) != 0 ||
+ connect(sock, &sa.sa, len) != 0)) {
+ } else if ((sp[1] = (sock_type == SOCK_DGRAM ? sock
+ : accept(sock, &sa.sa, &len))) ==
+ INVALID_SOCKET) {
+ } else {
+ mg_set_close_on_exec(sp[0]);
+ mg_set_close_on_exec(sp[1]);
+ if (sock_type == SOCK_STREAM) closesocket(sock);
+ ret = 1;
+ }
+
+ if (!ret) {
+ if (sp[0] != INVALID_SOCKET) closesocket(sp[0]);
+ if (sp[1] != INVALID_SOCKET) closesocket(sp[1]);
+ if (sock != INVALID_SOCKET) closesocket(sock);
+ sock = sp[0] = sp[1] = INVALID_SOCKET;
+ }
+
+ return ret;
+}
+#endif /* MG_DISABLE_SOCKETPAIR */
+
+#ifndef MG_SOCKET_SIMPLELINK
+static void mg_sock_get_addr(sock_t sock, int remote,
+ union socket_address *sa) {
+ socklen_t slen = sizeof(*sa);
+ memset(sa, 0, slen);
+ if (remote) {
+ getpeername(sock, &sa->sa, &slen);
+ } else {
+ getsockname(sock, &sa->sa, &slen);
+ }
+}
+
+void mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags) {
+ union socket_address sa;
+ mg_sock_get_addr(sock, flags & MG_SOCK_STRINGIFY_REMOTE, &sa);
+ mg_sock_addr_to_str(&sa, buf, len, flags);
+}
+#endif
+
+void mg_if_get_conn_addr(struct mg_connection *nc, int remote,
+ union socket_address *sa) {
+#ifndef MG_SOCKET_SIMPLELINK
+ mg_sock_get_addr(nc->sock, remote, sa);
+#else
+ /* SimpleLink does not provide a way to get socket's peer address after
+ * accept or connect. Address hould have been preserved in the connection,
+ * so we do our best here by using it. */
+ if (remote) memcpy(sa, &nc->sa, sizeof(*sa));
+#endif
+}
+
+#endif /* !MG_DISABLE_SOCKET_IF */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/multithreading.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/util.h" */
+
+#ifdef MG_ENABLE_THREADS
+
+static void multithreaded_ev_handler(struct mg_connection *c, int ev, void *p);
+
+/*
+ * This thread function executes user event handler.
+ * It runs an event manager that has only one connection, until that
+ * connection is alive.
+ */
+static void *per_connection_thread_function(void *param) {
+ struct mg_connection *c = (struct mg_connection *) param;
+ struct mg_mgr m;
+
+ mg_mgr_init(&m, NULL);
+ mg_add_conn(&m, c);
+ while (m.active_connections != NULL) {
+ mg_mgr_poll(&m, 1000);
+ }
+ mg_mgr_free(&m);
+
+ return param;
+}
+
+static void link_conns(struct mg_connection *c1, struct mg_connection *c2) {
+ c1->priv_2 = c2;
+ c2->priv_2 = c1;
+}
+
+static void unlink_conns(struct mg_connection *c) {
+ struct mg_connection *peer = (struct mg_connection *) c->priv_2;
+ if (peer != NULL) {
+ peer->flags |= MG_F_SEND_AND_CLOSE;
+ peer->priv_2 = NULL;
+ }
+ c->priv_2 = NULL;
+}
+
+static void forwarder_ev_handler(struct mg_connection *c, int ev, void *p) {
+ (void) p;
+ if (ev == MG_EV_RECV && c->priv_2) {
+ mg_forward(c, (struct mg_connection *) c->priv_2);
+ } else if (ev == MG_EV_CLOSE) {
+ unlink_conns(c);
+ }
+}
+
+static void spawn_handling_thread(struct mg_connection *nc) {
+ struct mg_mgr dummy;
+ sock_t sp[2];
+ struct mg_connection *c[2];
+
+ /*
+ * Create a socket pair, and wrap each socket into the connection with
+ * dummy event manager.
+ * c[0] stays in this thread, c[1] goes to another thread.
+ */
+ mg_socketpair(sp, SOCK_STREAM);
+ memset(&dummy, 0, sizeof(dummy));
+ c[0] = mg_add_sock(&dummy, sp[0], forwarder_ev_handler);
+ c[1] = mg_add_sock(&dummy, sp[1], nc->listener->priv_1.f);
+
+ /* Interlink client connection with c[0] */
+ link_conns(c[0], nc);
+
+ /*
+ * Switch c[0] manager from the dummy one to the real one. c[1] manager
+ * will be set in another thread, allocated on stack of that thread.
+ */
+ mg_add_conn(nc->mgr, c[0]);
+
+ /*
+ * Dress c[1] as nc.
+ * TODO(lsm): code in accept_conn() looks similar. Refactor.
+ */
+ c[1]->listener = nc->listener;
+ c[1]->proto_handler = nc->proto_handler;
+ c[1]->proto_data = nc->proto_data;
+ c[1]->user_data = nc->user_data;
+
+ mg_start_thread(per_connection_thread_function, c[1]);
+}
+
+static void multithreaded_ev_handler(struct mg_connection *c, int ev, void *p) {
+ (void) p;
+ if (ev == MG_EV_ACCEPT) {
+ spawn_handling_thread(c);
+ c->handler = forwarder_ev_handler;
+ }
+}
+
+void mg_enable_multithreading(struct mg_connection *nc) {
+ /* Wrap user event handler into our multithreaded_ev_handler */
+ nc->priv_1.f = nc->handler;
+ nc->handler = multithreaded_ev_handler;
+}
+#endif
+#ifdef MG_MODULE_LINES
+#line 1 "./src/uri.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/uri.h" */
+
+/*
+ * scan string until `sep`, keeping track of component boundaries in `res`.
+ *
+ * `p` will point to the char after the separator or it will be `end`.
+ */
+static void parse_uri_component(const char **p, const char *end, char sep,
+ struct mg_str *res) {
+ res->p = *p;
+ for (; *p < end; (*p)++) {
+ if (**p == sep) {
+ break;
+ }
+ }
+ res->len = (*p) - res->p;
+ if (*p < end) (*p)++;
+}
+
+int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
+ struct mg_str *user_info, struct mg_str *host,
+ unsigned int *port, struct mg_str *path, struct mg_str *query,
+ struct mg_str *fragment) {
+ struct mg_str rscheme = {0, 0}, ruser_info = {0, 0}, rhost = {0, 0},
+ rpath = {0, 0}, rquery = {0, 0}, rfragment = {0, 0};
+ unsigned int rport = 0;
+ enum {
+ P_START,
+ P_SCHEME_OR_PORT,
+ P_USER_INFO,
+ P_HOST,
+ P_PORT,
+ P_REST
+ } state = P_START;
+
+ const char *p = uri.p, *end = p + uri.len;
+ while (p < end) {
+ switch (state) {
+ case P_START:
+ /*
+ * expecting on of:
+ * - `scheme://xxxx`
+ * - `xxxx:port`
+ * - `xxxx/path`
+ */
+ for (; p < end; p++) {
+ if (*p == ':') {
+ state = P_SCHEME_OR_PORT;
+ break;
+ } else if (*p == '/') {
+ state = P_REST;
+ break;
+ }
+ }
+ if (state == P_START || state == P_REST) {
+ rhost.p = uri.p;
+ rhost.len = p - uri.p;
+ }
+ break;
+ case P_SCHEME_OR_PORT:
+ if (end - p >= 3 && memcmp(p, "://", 3) == 0) {
+ rscheme.p = uri.p;
+ rscheme.len = p - uri.p;
+ state = P_USER_INFO;
+ p += 2; /* point to last separator char */
+ } else {
+ rhost.p = uri.p;
+ rhost.len = p - uri.p;
+ state = P_PORT;
+ }
+ break;
+ case P_USER_INFO:
+ p++;
+ ruser_info.p = p;
+ for (; p < end; p++) {
+ if (*p == '@') {
+ state = P_HOST;
+ break;
+ } else if (*p == '/') {
+ break;
+ }
+ }
+ if (p == end || *p == '/') {
+ /* backtrack and parse as host */
+ state = P_HOST;
+ p = ruser_info.p;
+ }
+ ruser_info.len = p - ruser_info.p;
+ break;
+ case P_HOST:
+ if (*p == '@') p++;
+ rhost.p = p;
+ for (; p < end; p++) {
+ if (*p == ':') {
+ state = P_PORT;
+ break;
+ } else if (*p == '/') {
+ state = P_REST;
+ break;
+ }
+ }
+ rhost.len = p - rhost.p;
+ break;
+ case P_PORT:
+ p++;
+ for (; p < end; p++) {
+ if (*p == '/') {
+ state = P_REST;
+ break;
+ }
+ rport *= 10;
+ rport += *p - '0';
+ }
+ break;
+ case P_REST:
+ /* `p` points to separator. `path` includes the separator */
+ parse_uri_component(&p, end, '?', &rpath);
+ parse_uri_component(&p, end, '#', &rquery);
+ parse_uri_component(&p, end, '\0', &rfragment);
+ break;
+ }
+ }
+
+ if (scheme != 0) *scheme = rscheme;
+ if (user_info != 0) *user_info = ruser_info;
+ if (host != 0) *host = rhost;
+ if (port != 0) *port = rport;
+ if (path != 0) *path = rpath;
+ if (query != 0) *query = rquery;
+ if (fragment != 0) *fragment = rfragment;
+
+ return 0;
+}
+
+/* Normalize the URI path. Remove/resolve "." and "..". */
+int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) {
+ const char *s = in->p, *se = s + in->len;
+ char *cp = (char *) out->p, *d;
+
+ if (in->len == 0 || *s != '/') {
+ out->len = 0;
+ return 0;
+ }
+
+ d = cp;
+
+ while (s < se) {
+ const char *next = s;
+ struct mg_str component;
+ parse_uri_component(&next, se, '/', &component);
+ if (mg_vcmp(&component, ".") == 0) {
+ /* Yum. */
+ } else if (mg_vcmp(&component, "..") == 0) {
+ /* Backtrack to previous slash. */
+ if (d > cp + 1 && *(d - 1) == '/') d--;
+ while (d > cp && *(d - 1) != '/') d--;
+ } else {
+ memmove(d, s, next - s);
+ d += next - s;
+ }
+ s = next;
+ }
+ if (d == cp) *d++ = '/';
+
+ out->p = cp;
+ out->len = d - cp;
+ return 1;
+}
+#ifdef MG_MODULE_LINES
+#line 1 "./src/http.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef MG_DISABLE_HTTP
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/util.h" */
+/* Amalgamated: #include "common/sha1.h" */
+/* Amalgamated: #include "common/md5.h" */
+
+#ifndef MG_DISABLE_HTTP_WEBSOCKET
+#define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1)
+#endif
+
+/* CGI requires socketpair. */
+#if defined(MG_DISABLE_SOCKETPAIR) && !defined(MG_DISABLE_CGI)
+#define MG_DISABLE_CGI 1
+#endif
+
+static const char *mg_version_header = "Mongoose/" MG_VERSION;
+
+enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT };
+
+struct mg_http_proto_data_file {
+ FILE *fp; /* Opened file. */
+ int64_t cl; /* Content-Length. How many bytes to send. */
+ int64_t sent; /* How many bytes have been already sent. */
+ int keepalive; /* Keep connection open after sending. */
+ enum mg_http_proto_data_type type;
+};
+
+struct mg_http_proto_data_cgi {
+ struct mg_connection *cgi_nc;
+};
+
+struct mg_http_proto_data_chuncked {
+ int64_t body_len; /* How many bytes of chunked body was reassembled. */
+};
+
+struct mg_http_endpoint {
+ struct mg_http_endpoint *next;
+ const char *name;
+ size_t name_len;
+ mg_event_handler_t handler;
+};
+
+enum mg_http_multipart_stream_state {
+ MPS_BEGIN,
+ MPS_WAITING_FOR_BOUNDARY,
+ MPS_WAITING_FOR_CHUNK,
+ MPS_GOT_CHUNK,
+ MPS_GOT_BOUNDARY,
+ MPS_FINALIZE,
+ MPS_FINISHED
+};
+
+struct mg_http_multipart_stream {
+ const char *boundary;
+ int boundary_len;
+ const char *var_name;
+ const char *file_name;
+ void *user_data;
+ int prev_io_len;
+ enum mg_http_multipart_stream_state state;
+ int processing_part;
+};
+
+struct mg_http_proto_data {
+#ifndef MG_DISABLE_FILESYSTEM
+ struct mg_http_proto_data_file file;
+#endif
+#ifndef MG_DISABLE_CGI
+ struct mg_http_proto_data_cgi cgi;
+#endif
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+ struct mg_http_multipart_stream mp_stream;
+#endif
+ struct mg_http_proto_data_chuncked chunk;
+ struct mg_http_endpoint *endpoints;
+ mg_event_handler_t endpoint_handler;
+};
+
+static void mg_http_conn_destructor(void *proto_data);
+
+static struct mg_http_proto_data *mg_http_get_proto_data(
+ struct mg_connection *c) {
+ if (c->proto_data == NULL) {
+ c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data));
+ c->proto_data_destructor = mg_http_conn_destructor;
+ }
+
+ return (struct mg_http_proto_data *) c->proto_data;
+}
+
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+static void mg_http_free_proto_data_mp_stream(
+ struct mg_http_multipart_stream *mp) {
+ free((void *) mp->boundary);
+ mp->boundary = NULL;
+ free((void *) mp->var_name);
+ mp->var_name = NULL;
+ free((void *) mp->file_name);
+ mp->file_name = NULL;
+}
+#endif
+
+#ifndef MG_DISABLE_FILESYSTEM
+static void mg_http_free_proto_data_file(struct mg_http_proto_data_file *d) {
+ if (d != NULL) {
+ if (d->fp != NULL) {
+ fclose(d->fp);
+ }
+ memset(d, 0, sizeof(struct mg_http_proto_data_file));
+ }
+}
+#endif
+
+#ifndef MG_DISABLE_CGI
+static void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) {
+ if (d != NULL) {
+ if (d->cgi_nc != NULL) d->cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ memset(d, 0, sizeof(struct mg_http_proto_data_cgi));
+ }
+}
+#endif
+
+static void mg_http_free_proto_data_endpoints(struct mg_http_endpoint **ep) {
+ struct mg_http_endpoint *current = *ep;
+
+ while (current != NULL) {
+ struct mg_http_endpoint *tmp = current->next;
+ free((void *) current->name);
+ free(current);
+ current = tmp;
+ }
+
+ ep = NULL;
+}
+
+static void mg_http_conn_destructor(void *proto_data) {
+ struct mg_http_proto_data *pd = (struct mg_http_proto_data *) proto_data;
+#ifndef MG_DISABLE_FILESYSTEM
+ mg_http_free_proto_data_file(&pd->file);
+#endif
+#ifndef MG_DISABLE_CGI
+ mg_http_free_proto_data_cgi(&pd->cgi);
+#endif
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+ mg_http_free_proto_data_mp_stream(&pd->mp_stream);
+#endif
+ mg_http_free_proto_data_endpoints(&pd->endpoints);
+ free(proto_data);
+}
+
+/*
+ * This structure helps to create an environment for the spawned CGI program.
+ * Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
+ * last element must be NULL.
+ * However, on Windows there is a requirement that all these VARIABLE=VALUE\0
+ * strings must reside in a contiguous buffer. The end of the buffer is
+ * marked by two '\0' characters.
+ * We satisfy both worlds: we create an envp array (which is vars), all
+ * entries are actually pointers inside buf.
+ */
+struct mg_cgi_env_block {
+ struct mg_connection *nc;
+ char buf[MG_CGI_ENVIRONMENT_SIZE]; /* Environment buffer */
+ const char *vars[MG_MAX_CGI_ENVIR_VARS]; /* char *envp[] */
+ int len; /* Space taken */
+ int nvars; /* Number of variables in envp[] */
+};
+
+#ifndef MG_DISABLE_FILESYSTEM
+
+#define MIME_ENTRY(_ext, _type) \
+ { _ext, sizeof(_ext) - 1, _type }
+static const struct {
+ const char *extension;
+ size_t ext_len;
+ const char *mime_type;
+} mg_static_builtin_mime_types[] = {
+ MIME_ENTRY("html", "text/html"),
+ MIME_ENTRY("html", "text/html"),
+ MIME_ENTRY("htm", "text/html"),
+ MIME_ENTRY("shtm", "text/html"),
+ MIME_ENTRY("shtml", "text/html"),
+ MIME_ENTRY("css", "text/css"),
+ MIME_ENTRY("js", "application/x-javascript"),
+ MIME_ENTRY("ico", "image/x-icon"),
+ MIME_ENTRY("gif", "image/gif"),
+ MIME_ENTRY("jpg", "image/jpeg"),
+ MIME_ENTRY("jpeg", "image/jpeg"),
+ MIME_ENTRY("png", "image/png"),
+ MIME_ENTRY("svg", "image/svg+xml"),
+ MIME_ENTRY("txt", "text/plain"),
+ MIME_ENTRY("torrent", "application/x-bittorrent"),
+ MIME_ENTRY("wav", "audio/x-wav"),
+ MIME_ENTRY("mp3", "audio/x-mp3"),
+ MIME_ENTRY("mid", "audio/mid"),
+ MIME_ENTRY("m3u", "audio/x-mpegurl"),
+ MIME_ENTRY("ogg", "application/ogg"),
+ MIME_ENTRY("ram", "audio/x-pn-realaudio"),
+ MIME_ENTRY("xml", "text/xml"),
+ MIME_ENTRY("ttf", "application/x-font-ttf"),
+ MIME_ENTRY("json", "application/json"),
+ MIME_ENTRY("xslt", "application/xml"),
+ MIME_ENTRY("xsl", "application/xml"),
+ MIME_ENTRY("ra", "audio/x-pn-realaudio"),
+ MIME_ENTRY("doc", "application/msword"),
+ MIME_ENTRY("exe", "application/octet-stream"),
+ MIME_ENTRY("zip", "application/x-zip-compressed"),
+ MIME_ENTRY("xls", "application/excel"),
+ MIME_ENTRY("tgz", "application/x-tar-gz"),
+ MIME_ENTRY("tar", "application/x-tar"),
+ MIME_ENTRY("gz", "application/x-gunzip"),
+ MIME_ENTRY("arj", "application/x-arj-compressed"),
+ MIME_ENTRY("rar", "application/x-rar-compressed"),
+ MIME_ENTRY("rtf", "application/rtf"),
+ MIME_ENTRY("pdf", "application/pdf"),
+ MIME_ENTRY("swf", "application/x-shockwave-flash"),
+ MIME_ENTRY("mpg", "video/mpeg"),
+ MIME_ENTRY("webm", "video/webm"),
+ MIME_ENTRY("mpeg", "video/mpeg"),
+ MIME_ENTRY("mov", "video/quicktime"),
+ MIME_ENTRY("mp4", "video/mp4"),
+ MIME_ENTRY("m4v", "video/x-m4v"),
+ MIME_ENTRY("asf", "video/x-ms-asf"),
+ MIME_ENTRY("avi", "video/x-msvideo"),
+ MIME_ENTRY("bmp", "image/bmp"),
+ {NULL, 0, NULL}};
+
+#ifndef MG_DISABLE_DAV
+static int mg_mkdir(const char *path, uint32_t mode) {
+#ifndef _WIN32
+ return mkdir(path, mode);
+#else
+ (void) mode;
+ return _mkdir(path);
+#endif
+}
+#endif
+
+static struct mg_str mg_get_mime_type(const char *path, const char *dflt,
+ const struct mg_serve_http_opts *opts) {
+ const char *ext, *overrides;
+ size_t i, path_len;
+ struct mg_str r, k, v;
+
+ path_len = strlen(path);
+
+ overrides = opts->custom_mime_types;
+ while ((overrides = mg_next_comma_list_entry(overrides, &k, &v)) != NULL) {
+ ext = path + (path_len - k.len);
+ if (path_len > k.len && mg_vcasecmp(&k, ext) == 0) {
+ return v;
+ }
+ }
+
+ for (i = 0; mg_static_builtin_mime_types[i].extension != NULL; i++) {
+ ext = path + (path_len - mg_static_builtin_mime_types[i].ext_len);
+ if (path_len > mg_static_builtin_mime_types[i].ext_len && ext[-1] == '.' &&
+ mg_casecmp(ext, mg_static_builtin_mime_types[i].extension) == 0) {
+ r.p = mg_static_builtin_mime_types[i].mime_type;
+ r.len = strlen(r.p);
+ return r;
+ }
+ }
+
+ r.p = dflt;
+ r.len = strlen(r.p);
+ return r;
+}
+#endif
+
+/*
+ * Check whether full request is buffered. Return:
+ * -1 if request is malformed
+ * 0 if request is not yet fully buffered
+ * >0 actual request length, including last \r\n\r\n
+ */
+static int mg_http_get_request_len(const char *s, int buf_len) {
+ const unsigned char *buf = (unsigned char *) s;
+ int i;
+
+ for (i = 0; i < buf_len; i++) {
+ if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) {
+ return -1;
+ } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
+ return i + 2;
+ } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
+ buf[i + 2] == '\n') {
+ return i + 3;
+ }
+ }
+
+ return 0;
+}
+
+static const char *mg_http_parse_headers(const char *s, const char *end,
+ int len, struct http_message *req) {
+ int i;
+ for (i = 0; i < (int) ARRAY_SIZE(req->header_names) - 1; i++) {
+ struct mg_str *k = &req->header_names[i], *v = &req->header_values[i];
+
+ s = mg_skip(s, end, ": ", k);
+ s = mg_skip(s, end, "\r\n", v);
+
+ while (v->len > 0 && v->p[v->len - 1] == ' ') {
+ v->len--; /* Trim trailing spaces in header value */
+ }
+
+ if (k->len == 0 || v->len == 0) {
+ k->p = v->p = NULL;
+ k->len = v->len = 0;
+ break;
+ }
+
+ if (!mg_ncasecmp(k->p, "Content-Length", 14)) {
+ req->body.len = to64(v->p);
+ req->message.len = len + req->body.len;
+ }
+ }
+
+ return s;
+}
+
+int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req) {
+ const char *end, *qs;
+ int len = mg_http_get_request_len(s, n);
+
+ if (len <= 0) return len;
+
+ memset(hm, 0, sizeof(*hm));
+ hm->message.p = s;
+ hm->body.p = s + len;
+ hm->message.len = hm->body.len = (size_t) ~0;
+ end = s + len;
+
+ /* Request is fully buffered. Skip leading whitespaces. */
+ while (s < end && isspace(*(unsigned char *) s)) s++;
+
+ if (is_req) {
+ /* Parse request line: method, URI, proto */
+ s = mg_skip(s, end, " ", &hm->method);
+ s = mg_skip(s, end, " ", &hm->uri);
+ s = mg_skip(s, end, "\r\n", &hm->proto);
+ if (hm->uri.p <= hm->method.p || hm->proto.p <= hm->uri.p) return -1;
+
+ /* If URI contains '?' character, initialize query_string */
+ if ((qs = (char *) memchr(hm->uri.p, '?', hm->uri.len)) != NULL) {
+ hm->query_string.p = qs + 1;
+ hm->query_string.len = &hm->uri.p[hm->uri.len] - (qs + 1);
+ hm->uri.len = qs - hm->uri.p;
+ }
+ } else {
+ s = mg_skip(s, end, " ", &hm->proto);
+ if (end - s < 4 || s[3] != ' ') return -1;
+ hm->resp_code = atoi(s);
+ if (hm->resp_code < 100 || hm->resp_code >= 600) return -1;
+ s += 4;
+ s = mg_skip(s, end, "\r\n", &hm->resp_status_msg);
+ }
+
+ s = mg_http_parse_headers(s, end, len, hm);
+
+ /*
+ * mg_parse_http() 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.
+ */
+ if (hm->body.len == (size_t) ~0 && is_req &&
+ mg_vcasecmp(&hm->method, "PUT") != 0 &&
+ mg_vcasecmp(&hm->method, "POST") != 0) {
+ hm->body.len = 0;
+ hm->message.len = len;
+ }
+
+ return len;
+}
+
+struct mg_str *mg_get_http_header(struct http_message *hm, const char *name) {
+ size_t i, len = strlen(name);
+
+ for (i = 0; hm->header_names[i].len > 0; i++) {
+ struct mg_str *h = &hm->header_names[i], *v = &hm->header_values[i];
+ if (h->p != NULL && h->len == len && !mg_ncasecmp(h->p, name, len))
+ return v;
+ }
+
+ return NULL;
+}
+
+#ifndef MG_DISABLE_HTTP_WEBSOCKET
+
+static int mg_is_ws_fragment(unsigned char flags) {
+ return (flags & 0x80) == 0 || (flags & 0x0f) == 0;
+}
+
+static int mg_is_ws_first_fragment(unsigned char flags) {
+ return (flags & 0x80) == 0 && (flags & 0x0f) != 0;
+}
+
+static void mg_handle_incoming_websocket_frame(struct mg_connection *nc,
+ struct websocket_message *wsm) {
+ if (wsm->flags & 0x8) {
+ mg_call(nc, nc->handler, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm);
+ } else {
+ mg_call(nc, nc->handler, MG_EV_WEBSOCKET_FRAME, wsm);
+ }
+}
+
+static int mg_deliver_websocket_data(struct mg_connection *nc) {
+ /* Using unsigned char *, cause of integer arithmetic below */
+ uint64_t i, data_len = 0, frame_len = 0, buf_len = nc->recv_mbuf.len, len,
+ mask_len = 0, header_len = 0;
+ unsigned char *p = (unsigned char *) nc->recv_mbuf.buf, *buf = p,
+ *e = p + buf_len;
+ unsigned *sizep = (unsigned *) &p[1]; /* Size ptr for defragmented frames */
+ int ok, reass = buf_len > 0 && mg_is_ws_fragment(p[0]) &&
+ !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG);
+
+ /* If that's a continuation frame that must be reassembled, handle it */
+ if (reass && !mg_is_ws_first_fragment(p[0]) &&
+ buf_len >= 1 + sizeof(*sizep) && buf_len >= 1 + sizeof(*sizep) + *sizep) {
+ buf += 1 + sizeof(*sizep) + *sizep;
+ buf_len -= 1 + sizeof(*sizep) + *sizep;
+ }
+
+ if (buf_len >= 2) {
+ len = buf[1] & 127;
+ mask_len = buf[1] & 128 ? 4 : 0;
+ if (len < 126 && buf_len >= mask_len) {
+ data_len = len;
+ header_len = 2 + mask_len;
+ } else if (len == 126 && buf_len >= 4 + mask_len) {
+ header_len = 4 + mask_len;
+ data_len = ntohs(*(uint16_t *) &buf[2]);
+ } else if (buf_len >= 10 + mask_len) {
+ header_len = 10 + mask_len;
+ data_len = (((uint64_t) ntohl(*(uint32_t *) &buf[2])) << 32) +
+ ntohl(*(uint32_t *) &buf[6]);
+ }
+ }
+
+ frame_len = header_len + data_len;
+ ok = frame_len > 0 && frame_len <= buf_len;
+
+ if (ok) {
+ struct websocket_message wsm;
+
+ wsm.size = (size_t) data_len;
+ wsm.data = buf + header_len;
+ wsm.flags = buf[0];
+
+ /* Apply mask if necessary */
+ if (mask_len > 0) {
+ for (i = 0; i < data_len; i++) {
+ buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4];
+ }
+ }
+
+ if (reass) {
+ /* On first fragmented frame, nullify size */
+ if (mg_is_ws_first_fragment(wsm.flags)) {
+ mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.size + sizeof(*sizep));
+ p[0] &= ~0x0f; /* Next frames will be treated as continuation */
+ buf = p + 1 + sizeof(*sizep);
+ *sizep = 0; /* TODO(lsm): fix. this can stomp over frame data */
+ }
+
+ /* Append this frame to the reassembled buffer */
+ memmove(buf, wsm.data, e - wsm.data);
+ (*sizep) += wsm.size;
+ nc->recv_mbuf.len -= wsm.data - buf;
+
+ /* On last fragmented frame - call user handler and remove data */
+ if (wsm.flags & 0x80) {
+ wsm.data = p + 1 + sizeof(*sizep);
+ wsm.size = *sizep;
+ mg_handle_incoming_websocket_frame(nc, &wsm);
+ mbuf_remove(&nc->recv_mbuf, 1 + sizeof(*sizep) + *sizep);
+ }
+ } else {
+ /* TODO(lsm): properly handle OOB control frames during defragmentation */
+ mg_handle_incoming_websocket_frame(nc, &wsm);
+ mbuf_remove(&nc->recv_mbuf, (size_t) frame_len); /* Cleanup frame */
+ }
+
+ /* If client closes, close too */
+ if ((buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) {
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ }
+ }
+
+ return ok;
+}
+
+struct ws_mask_ctx {
+ size_t pos; /* zero means unmasked */
+ uint32_t mask;
+};
+
+static uint32_t mg_ws_random_mask(void) {
+ uint32_t mask;
+/*
+ * The spec requires WS client to generate hard to
+ * guess mask keys. From RFC6455, Section 5.3:
+ *
+ * The unpredictability of the masking key is essential to prevent
+ * authors of malicious applications from selecting the bytes that appear on
+ * the wire.
+ *
+ * Hence this feature is essential when the actual end user of this API
+ * is untrusted code that wouldn't have access to a lower level net API
+ * anyway (e.g. web browsers). Hence this feature is low prio for most
+ * mongoose use cases and thus can be disabled, e.g. when porting to a platform
+ * that lacks random().
+ */
+#ifdef MG_DISABLE_WS_RANDOM_MASK
+ mask = 0xefbeadde; /* generated with a random number generator, I swear */
+#else
+ if (sizeof(long) >= 4) {
+ mask = (uint32_t) random();
+ } else if (sizeof(long) == 2) {
+ mask = (uint32_t) random() << 16 | (uint32_t) random();
+ }
+#endif
+ return mask;
+}
+
+static void mg_send_ws_header(struct mg_connection *nc, int op, size_t len,
+ struct ws_mask_ctx *ctx) {
+ int header_len;
+ unsigned char header[10];
+
+ header[0] = (op & WEBSOCKET_DONT_FIN ? 0x0 : 0x80) + (op & 0x0f);
+ if (len < 126) {
+ header[1] = len;
+ header_len = 2;
+ } else if (len < 65535) {
+ uint16_t tmp = htons((uint16_t) len);
+ header[1] = 126;
+ memcpy(&header[2], &tmp, sizeof(tmp));
+ header_len = 4;
+ } else {
+ uint32_t tmp;
+ header[1] = 127;
+ tmp = htonl((uint32_t)((uint64_t) len >> 32));
+ memcpy(&header[2], &tmp, sizeof(tmp));
+ tmp = htonl((uint32_t)(len & 0xffffffff));
+ memcpy(&header[6], &tmp, sizeof(tmp));
+ header_len = 10;
+ }
+
+ /* client connections enable masking */
+ if (nc->listener == NULL) {
+ header[1] |= 1 << 7; /* set masking flag */
+ mg_send(nc, header, header_len);
+ ctx->mask = mg_ws_random_mask();
+ mg_send(nc, &ctx->mask, sizeof(ctx->mask));
+ ctx->pos = nc->send_mbuf.len;
+ } else {
+ mg_send(nc, header, header_len);
+ ctx->pos = 0;
+ }
+}
+
+static void mg_ws_mask_frame(struct mbuf *mbuf, struct ws_mask_ctx *ctx) {
+ size_t i;
+ if (ctx->pos == 0) return;
+ for (i = 0; i < (mbuf->len - ctx->pos); i++) {
+ mbuf->buf[ctx->pos + i] ^= ((char *) &ctx->mask)[i % 4];
+ }
+}
+
+void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data,
+ size_t len) {
+ struct ws_mask_ctx ctx;
+ DBG(("%p %d %d", nc, op, (int) len));
+ mg_send_ws_header(nc, op, len, &ctx);
+ mg_send(nc, data, len);
+
+ mg_ws_mask_frame(&nc->send_mbuf, &ctx);
+
+ if (op == WEBSOCKET_OP_CLOSE) {
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ }
+}
+
+void mg_send_websocket_framev(struct mg_connection *nc, int op,
+ const struct mg_str *strv, int strvcnt) {
+ struct ws_mask_ctx ctx;
+ int i;
+ int len = 0;
+ for (i = 0; i < strvcnt; i++) {
+ len += strv[i].len;
+ }
+
+ mg_send_ws_header(nc, op, len, &ctx);
+
+ for (i = 0; i < strvcnt; i++) {
+ mg_send(nc, strv[i].p, strv[i].len);
+ }
+
+ mg_ws_mask_frame(&nc->send_mbuf, &ctx);
+
+ if (op == WEBSOCKET_OP_CLOSE) {
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ }
+}
+
+void mg_printf_websocket_frame(struct mg_connection *nc, int op,
+ const char *fmt, ...) {
+ char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
+ va_list ap;
+ int len;
+
+ va_start(ap, fmt);
+ if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
+ mg_send_websocket_frame(nc, op, buf, len);
+ }
+ va_end(ap);
+
+ if (buf != mem && buf != NULL) {
+ MG_FREE(buf);
+ }
+}
+
+static void mg_websocket_handler(struct mg_connection *nc, int ev,
+ void *ev_data) {
+ mg_call(nc, nc->handler, ev, ev_data);
+
+ switch (ev) {
+ case MG_EV_RECV:
+ do {
+ } while (mg_deliver_websocket_data(nc));
+ break;
+ case MG_EV_POLL:
+ /* Ping idle websocket connections */
+ {
+ time_t now = *(time_t *) ev_data;
+ if (nc->flags & MG_F_IS_WEBSOCKET &&
+ now > nc->last_io_time + MG_WEBSOCKET_PING_INTERVAL_SECONDS) {
+ mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, "", 0);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void mg_ws_handshake(struct mg_connection *nc,
+ const struct mg_str *key) {
+ static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ char buf[MG_VPRINTF_BUFFER_SIZE], sha[20], b64_sha[sizeof(sha) * 2];
+ cs_sha1_ctx sha_ctx;
+
+ snprintf(buf, sizeof(buf), "%.*s%s", (int) key->len, key->p, magic);
+
+ cs_sha1_init(&sha_ctx);
+ cs_sha1_update(&sha_ctx, (unsigned char *) buf, strlen(buf));
+ cs_sha1_final((unsigned char *) sha, &sha_ctx);
+
+ mg_base64_encode((unsigned char *) sha, sizeof(sha), b64_sha);
+ mg_printf(nc, "%s%s%s",
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: ",
+ b64_sha, "\r\n\r\n");
+ DBG(("%p %.*s %s", nc, (int) key->len, key->p, b64_sha));
+}
+
+#endif /* MG_DISABLE_HTTP_WEBSOCKET */
+
+#ifndef MG_DISABLE_FILESYSTEM
+static void mg_http_transfer_file_data(struct mg_connection *nc) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ char buf[MG_MAX_HTTP_SEND_MBUF];
+ int64_t left = pd->file.cl - pd->file.sent;
+ size_t n = 0, to_read = 0;
+
+ if (pd->file.type == DATA_FILE) {
+ struct mbuf *io = &nc->send_mbuf;
+ if (io->len < sizeof(buf)) {
+ to_read = sizeof(buf) - io->len;
+ }
+
+ if (left > 0 && to_read > (size_t) left) {
+ to_read = left;
+ }
+
+ if (to_read == 0) {
+ /* Rate limiting. send_mbuf is too full, wait until it's drained. */
+ } else if (pd->file.sent < pd->file.cl &&
+ (n = fread(buf, 1, to_read, pd->file.fp)) > 0) {
+ mg_send(nc, buf, n);
+ pd->file.sent += n;
+ } else {
+ if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
+ mg_http_free_proto_data_file(&pd->file);
+ }
+ } else if (pd->file.type == DATA_PUT) {
+ struct mbuf *io = &nc->recv_mbuf;
+ size_t to_write =
+ left <= 0 ? 0 : left < (int64_t) io->len ? (size_t) left : io->len;
+ size_t n = fwrite(io->buf, 1, to_write, pd->file.fp);
+ if (n > 0) {
+ mbuf_remove(io, n);
+ pd->file.sent += n;
+ }
+ if (n == 0 || pd->file.sent >= pd->file.cl) {
+ if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
+ mg_http_free_proto_data_file(&pd->file);
+ }
+ }
+#ifndef MG_DISABLE_CGI
+ else if (pd->cgi.cgi_nc != NULL) {
+ /* This is POST data that needs to be forwarded to the CGI process */
+ if (pd->cgi.cgi_nc != NULL) {
+ mg_forward(nc, pd->cgi.cgi_nc);
+ } else {
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ }
+ }
+#endif
+}
+#endif /* MG_DISABLE_FILESYSTEM */
+
+/*
+ * Parse chunked-encoded buffer. Return 0 if the buffer is not encoded, or
+ * if it's incomplete. If the chunk is fully buffered, return total number of
+ * bytes in a chunk, and store data in `data`, `data_len`.
+ */
+static size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data,
+ size_t *chunk_len) {
+ unsigned char *s = (unsigned char *) buf;
+ size_t n = 0; /* scanned chunk length */
+ size_t i = 0; /* index in s */
+
+ /* Scan chunk length. That should be a hexadecimal number. */
+ while (i < len && isxdigit(s[i])) {
+ n *= 16;
+ n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10;
+ i++;
+ }
+
+ /* Skip new line */
+ if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
+ return 0;
+ }
+ i += 2;
+
+ /* Record where the data is */
+ *chunk_data = (char *) s + i;
+ *chunk_len = n;
+
+ /* Skip data */
+ i += n;
+
+ /* Skip new line */
+ if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
+ return 0;
+ }
+ return i + 2;
+}
+
+MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
+ struct http_message *hm, char *buf,
+ size_t blen) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ char *data;
+ size_t i, n, data_len, body_len, zero_chunk_received = 0;
+
+ /* Find out piece of received data that is not yet reassembled */
+ body_len = pd->chunk.body_len;
+ assert(blen >= body_len);
+
+ /* Traverse all fully buffered chunks */
+ for (i = body_len;
+ (n = mg_http_parse_chunk(buf + i, blen - i, &data, &data_len)) > 0;
+ i += n) {
+ /* Collapse chunk data to the rest of HTTP body */
+ memmove(buf + body_len, data, data_len);
+ body_len += data_len;
+ hm->body.len = body_len;
+
+ if (data_len == 0) {
+ zero_chunk_received = 1;
+ i += n;
+ break;
+ }
+ }
+
+ if (i > body_len) {
+ /* Shift unparsed content to the parsed body */
+ assert(i <= blen);
+ memmove(buf + body_len, buf + i, blen - i);
+ memset(buf + body_len + blen - i, 0, i - body_len);
+ nc->recv_mbuf.len -= i - body_len;
+ pd->chunk.body_len = body_len;
+
+ /* Send MG_EV_HTTP_CHUNK event */
+ nc->flags &= ~MG_F_DELETE_CHUNK;
+ mg_call(nc, nc->handler, MG_EV_HTTP_CHUNK, hm);
+
+ /* Delete processed data if user set MG_F_DELETE_CHUNK flag */
+ if (nc->flags & MG_F_DELETE_CHUNK) {
+ memset(buf, 0, body_len);
+ memmove(buf, buf + body_len, blen - i);
+ nc->recv_mbuf.len -= body_len;
+ hm->body.len = pd->chunk.body_len = 0;
+ }
+
+ if (zero_chunk_received) {
+ hm->message.len = pd->chunk.body_len + blen - i;
+ }
+ }
+
+ return body_len;
+}
+
+static mg_event_handler_t mg_http_get_endpoint_handler(
+ struct mg_connection *nc, struct mg_str *uri_path) {
+ struct mg_http_proto_data *pd;
+ mg_event_handler_t ret = NULL;
+ int matched, matched_max = 0;
+ struct mg_http_endpoint *ep;
+
+ if (nc == NULL) {
+ return NULL;
+ }
+
+ pd = mg_http_get_proto_data(nc);
+
+ ep = pd->endpoints;
+ while (ep != NULL) {
+ const struct mg_str name_s = {ep->name, ep->name_len};
+ if ((matched = mg_match_prefix_n(name_s, *uri_path)) != -1) {
+ if (matched > matched_max) {
+ /* Looking for the longest suitable handler */
+ ret = ep->handler;
+ matched_max = matched;
+ }
+ }
+
+ ep = ep->next;
+ }
+
+ return ret;
+}
+
+static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
+ struct http_message *hm) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+
+ if (pd->endpoint_handler == NULL || ev == MG_EV_HTTP_REQUEST) {
+ pd->endpoint_handler =
+ ev == MG_EV_HTTP_REQUEST
+ ? mg_http_get_endpoint_handler(nc->listener, &hm->uri)
+ : NULL;
+ }
+ mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler, ev,
+ hm);
+}
+
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+static void mg_http_multipart_continue(struct mg_connection *nc);
+
+static void mg_http_multipart_begin(struct mg_connection *nc,
+ struct http_message *hm, int req_len);
+
+#endif
+
+/*
+ * lx106 compiler has a bug (TODO(mkm) report and insert tracking bug here)
+ * If a big structure is declared in a big function, lx106 gcc will make it
+ * even bigger (round up to 4k, from 700 bytes of actual size).
+ */
+#ifdef __xtensa__
+static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
+ struct http_message *hm) __attribute__((noinline));
+
+void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ struct http_message hm;
+ mg_http_handler2(nc, ev, ev_data, &hm);
+}
+
+static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
+ struct http_message *hm) {
+#else /* !__XTENSA__ */
+void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ struct http_message shm;
+ struct http_message *hm = &shm;
+#endif /* __XTENSA__ */
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ struct mbuf *io = &nc->recv_mbuf;
+ int req_len;
+ const int is_req = (nc->listener != NULL);
+#ifndef MG_DISABLE_HTTP_WEBSOCKET
+ struct mg_str *vec;
+#endif
+ if (ev == MG_EV_CLOSE) {
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+ if (pd->mp_stream.boundary != NULL) {
+ /*
+ * Multipart message is in progress, but we get close
+ * MG_EV_HTTP_PART_END with error flag
+ */
+ struct mg_http_multipart_part mp;
+ memset(&mp, 0, sizeof(mp));
+
+ mp.status = -1;
+ mp.var_name = pd->mp_stream.var_name;
+ mp.file_name = pd->mp_stream.file_name;
+ mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),
+ MG_EV_HTTP_PART_END, &mp);
+ } else
+#endif
+ if (io->len > 0 && mg_parse_http(io->buf, io->len, hm, is_req) > 0) {
+ /*
+ * For HTTP messages without Content-Length, always send HTTP message
+ * before MG_EV_CLOSE message.
+ */
+ int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
+ hm->message.len = io->len;
+ hm->body.len = io->buf + io->len - hm->body.p;
+ mg_http_call_endpoint_handler(nc, ev2, hm);
+ }
+ }
+
+#ifndef MG_DISABLE_FILESYSTEM
+ if (pd->file.fp != NULL) {
+ mg_http_transfer_file_data(nc);
+ }
+#endif
+
+ mg_call(nc, nc->handler, ev, ev_data);
+
+ if (ev == MG_EV_RECV) {
+ struct mg_str *s;
+
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+ if (pd->mp_stream.boundary != NULL) {
+ mg_http_multipart_continue(nc);
+ return;
+ }
+#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
+
+ req_len = mg_parse_http(io->buf, io->len, hm, is_req);
+
+ if (req_len > 0 &&
+ (s = mg_get_http_header(hm, "Transfer-Encoding")) != NULL &&
+ mg_vcasecmp(s, "chunked") == 0) {
+ mg_handle_chunked(nc, hm, io->buf + req_len, io->len - req_len);
+ }
+
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+ if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL &&
+ s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) {
+ mg_http_multipart_begin(nc, hm, req_len);
+ mg_http_multipart_continue(nc);
+ return;
+ }
+#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
+
+ /* TODO(alashkin): refactor this ifelseifelseifelseifelse */
+ if ((req_len < 0 ||
+ (req_len == 0 && io->len >= MG_MAX_HTTP_REQUEST_SIZE))) {
+ DBG(("invalid request"));
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ } else if (req_len == 0) {
+ /* Do nothing, request is not yet fully buffered */
+ }
+#ifndef MG_DISABLE_HTTP_WEBSOCKET
+ else if (nc->listener == NULL &&
+ mg_get_http_header(hm, "Sec-WebSocket-Accept")) {
+ /* We're websocket client, got handshake response from server. */
+ /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */
+ mbuf_remove(io, req_len);
+ nc->proto_handler = mg_websocket_handler;
+ nc->flags |= MG_F_IS_WEBSOCKET;
+ mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
+ mg_websocket_handler(nc, MG_EV_RECV, ev_data);
+ } else if (nc->listener != NULL &&
+ (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) {
+ /* This is a websocket request. Switch protocol handlers. */
+ mbuf_remove(io, req_len);
+ nc->proto_handler = mg_websocket_handler;
+ nc->flags |= MG_F_IS_WEBSOCKET;
+
+ /* Send handshake */
+ mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST, hm);
+ if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
+ if (nc->send_mbuf.len == 0) {
+ mg_ws_handshake(nc, vec);
+ }
+ mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
+ mg_websocket_handler(nc, MG_EV_RECV, ev_data);
+ }
+#endif /* MG_DISABLE_HTTP_WEBSOCKET */
+ } else if (hm->message.len <= io->len) {
+ int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
+
+/* Whole HTTP message is fully buffered, call event handler */
+
+#ifdef MG_ENABLE_JAVASCRIPT
+ v7_val_t v1, v2, headers, req, args, res;
+ struct v7 *v7 = nc->mgr->v7;
+ const char *ev_name = trigger_ev == MG_EV_HTTP_REPLY ? "onsnd" : "onrcv";
+ int i, js_callback_handled_request = 0;
+
+ if (v7 != NULL) {
+ /* Lookup JS callback */
+ v1 = v7_get(v7, v7_get_global(v7), "Http", ~0);
+ v2 = v7_get(v7, v1, ev_name, ~0);
+
+ /* Create callback params. TODO(lsm): own/disown those */
+ args = v7_mk_array(v7);
+ req = v7_mk_object(v7);
+ headers = v7_mk_object(v7);
+
+ /* Populate request object */
+ v7_set(v7, req, "method", ~0,
+ v7_mk_string(v7, hm->method.p, hm->method.len, 1));
+ v7_set(v7, req, "uri", ~0, v7_mk_string(v7, hm->uri.p, hm->uri.len, 1));
+ v7_set(v7, req, "body", ~0,
+ v7_mk_string(v7, hm->body.p, hm->body.len, 1));
+ v7_set(v7, req, "headers", ~0, headers);
+ for (i = 0; hm->header_names[i].len > 0; i++) {
+ const struct mg_str *name = &hm->header_names[i];
+ const struct mg_str *value = &hm->header_values[i];
+ v7_set(v7, headers, name->p, name->len,
+ v7_mk_string(v7, value->p, value->len, 1));
+ }
+
+ /* Invoke callback. TODO(lsm): report errors */
+ v7_array_push(v7, args, v7_mk_foreign(nc));
+ v7_array_push(v7, args, req);
+ if (v7_apply(v7, v2, v7_mk_undefined(), args, &res) == V7_OK &&
+ v7_is_truthy(v7, res)) {
+ js_callback_handled_request++;
+ }
+ }
+
+ /* If JS callback returns true, stop request processing */
+ if (js_callback_handled_request) {
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ } else {
+ mg_http_call_endpoint_handler(nc, trigger_ev, hm);
+ }
+#else
+ mg_http_call_endpoint_handler(nc, trigger_ev, hm);
+#endif
+ mbuf_remove(io, hm->message.len);
+ }
+ }
+ (void) pd;
+}
+
+static size_t mg_get_line_len(const char *buf, size_t buf_len) {
+ size_t len = 0;
+ while (len < buf_len && buf[len] != '\n') len++;
+ return len == buf_len ? 0 : len + 1;
+}
+
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+static void mg_http_multipart_begin(struct mg_connection *nc,
+ struct http_message *hm, int req_len) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ struct mg_str *ct;
+ struct mbuf *io = &nc->recv_mbuf;
+
+ char boundary[100];
+ int boundary_len;
+
+ if (nc->listener == NULL) {
+ /* No streaming for replies now */
+ goto exit_mp;
+ }
+
+ ct = mg_get_http_header(hm, "Content-Type");
+ if (ct == NULL) {
+ /* We need more data - or it isn't multipart mesage */
+ goto exit_mp;
+ }
+
+ /* Content-type should start with "multipart" */
+ if (ct->len < 9 || strncmp(ct->p, "multipart", 9) != 0) {
+ goto exit_mp;
+ }
+
+ boundary_len =
+ mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));
+ if (boundary_len == 0) {
+ /*
+ * Content type is multipart, but there is no boundary,
+ * probably malformed request
+ */
+ nc->flags = MG_F_CLOSE_IMMEDIATELY;
+ DBG(("invalid request"));
+ goto exit_mp;
+ }
+
+ /* If we reach this place - that is multipart request */
+
+ if (pd->mp_stream.boundary != NULL) {
+ /*
+ * Another streaming request was in progress,
+ * looks like protocol error
+ */
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ } else {
+ pd->mp_stream.boundary = strdup(boundary);
+ pd->mp_stream.boundary_len = strlen(boundary);
+ pd->mp_stream.var_name = pd->mp_stream.file_name = NULL;
+
+ pd->endpoint_handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
+ if (pd->endpoint_handler == NULL) {
+ pd->endpoint_handler = nc->handler;
+ }
+
+ mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_MULTIPART_REQUEST, hm);
+
+ mbuf_remove(io, req_len);
+ }
+exit_mp:
+ ;
+}
+
+#define CONTENT_DISPOSITION "Content-Disposition: "
+
+static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
+ const char *data, size_t data_len) {
+ struct mg_http_multipart_part mp;
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+ memset(&mp, 0, sizeof(mp));
+
+ mp.var_name = pd->mp_stream.var_name;
+ mp.file_name = pd->mp_stream.file_name;
+ mp.user_data = pd->mp_stream.user_data;
+ mp.data.p = data;
+ mp.data.len = data_len;
+ mg_call(c, pd->endpoint_handler, ev, &mp);
+ pd->mp_stream.user_data = mp.user_data;
+}
+
+static int mg_http_multipart_got_chunk(struct mg_connection *c) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+ struct mbuf *io = &c->recv_mbuf;
+
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf,
+ pd->mp_stream.prev_io_len);
+ mbuf_remove(io, pd->mp_stream.prev_io_len);
+ pd->mp_stream.prev_io_len = 0;
+ pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
+
+ return 0;
+}
+
+static int mg_http_multipart_finalize(struct mg_connection *c) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+ mg_http_free_proto_data_mp_stream(&pd->mp_stream);
+ pd->mp_stream.state = MPS_FINISHED;
+
+ return 1;
+}
+
+static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
+ const char *boundary;
+ struct mbuf *io = &c->recv_mbuf;
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+
+ if ((int) io->len < pd->mp_stream.boundary_len + 2) {
+ return 0;
+ }
+
+ boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+ if (boundary != NULL) {
+ if (io->len - (boundary - io->buf) < 4) {
+ return 0;
+ }
+ if (memcmp(boundary + pd->mp_stream.boundary_len, "--", 2) == 0) {
+ pd->mp_stream.state = MPS_FINALIZE;
+ } else {
+ pd->mp_stream.state = MPS_GOT_BOUNDARY;
+ }
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int mg_http_multipart_process_boundary(struct mg_connection *c) {
+ int data_size;
+ const char *boundary, *block_begin;
+ struct mbuf *io = &c->recv_mbuf;
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+ char file_name[100], var_name[100];
+ int line_len;
+ boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+ block_begin = boundary + pd->mp_stream.boundary_len + 2;
+ data_size = io->len - (block_begin - io->buf);
+
+ while (data_size > 0 &&
+ (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
+ if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
+ mg_ncasecmp(block_begin, CONTENT_DISPOSITION,
+ sizeof(CONTENT_DISPOSITION) - 1) == 0) {
+ struct mg_str header;
+
+ header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
+ header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
+ mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2);
+ mg_http_parse_header(&header, "filename", file_name,
+ sizeof(file_name) - 2);
+ block_begin += line_len;
+ data_size -= line_len;
+ continue;
+ }
+
+ if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) {
+ mbuf_remove(io, block_begin - io->buf + 2);
+
+ if (pd->mp_stream.processing_part != 0) {
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+ }
+
+ free((void *) pd->mp_stream.file_name);
+ pd->mp_stream.file_name = strdup(file_name);
+ free((void *) pd->mp_stream.var_name);
+ pd->mp_stream.var_name = strdup(var_name);
+
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
+ pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
+ pd->mp_stream.processing_part++;
+ return 1;
+ }
+
+ block_begin += line_len;
+ }
+
+ pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+
+ return 0;
+}
+
+static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+ struct mbuf *io = &c->recv_mbuf;
+
+ const char *boundary;
+ if ((int) io->len < pd->mp_stream.boundary_len + 6 /* \r\n, --, -- */) {
+ return 0;
+ }
+
+ boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+ if (boundary == NULL && pd->mp_stream.prev_io_len == 0) {
+ pd->mp_stream.prev_io_len = io->len;
+ return 0;
+ } else if (boundary == NULL &&
+ (int) io->len >
+ pd->mp_stream.prev_io_len + pd->mp_stream.boundary_len + 4) {
+ pd->mp_stream.state = MPS_GOT_CHUNK;
+ return 1;
+ } else if (boundary != NULL) {
+ int data_size = (boundary - io->buf - 4);
+ mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, data_size);
+ mbuf_remove(io, (boundary - io->buf));
+ pd->mp_stream.prev_io_len = 0;
+ pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+static void mg_http_multipart_continue(struct mg_connection *c) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+ while (1) {
+ switch (pd->mp_stream.state) {
+ case MPS_BEGIN: {
+ pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+ break;
+ }
+ case MPS_WAITING_FOR_BOUNDARY: {
+ if (mg_http_multipart_wait_for_boundary(c) == 0) {
+ return;
+ }
+ break;
+ }
+ case MPS_GOT_BOUNDARY: {
+ if (mg_http_multipart_process_boundary(c) == 0) {
+ return;
+ }
+ break;
+ }
+ case MPS_WAITING_FOR_CHUNK: {
+ if (mg_http_multipart_continue_wait_for_chunk(c) == 0) {
+ return;
+ }
+ break;
+ }
+ case MPS_GOT_CHUNK: {
+ if (mg_http_multipart_got_chunk(c) == 0) {
+ return;
+ }
+ break;
+ }
+ case MPS_FINALIZE: {
+ if (mg_http_multipart_finalize(c) == 0) {
+ return;
+ }
+ break;
+ }
+ case MPS_FINISHED: {
+ mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len);
+ return;
+ }
+ }
+ }
+}
+
+struct file_upload_state {
+ char *lfn;
+ size_t num_recd;
+ FILE *fp;
+};
+
+void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
+ mg_fu_fname_fn local_name_fn) {
+ switch (ev) {
+ case MG_EV_HTTP_PART_BEGIN: {
+ struct mg_http_multipart_part *mp =
+ (struct mg_http_multipart_part *) ev_data;
+ struct file_upload_state *fus =
+ (struct file_upload_state *) calloc(1, sizeof(*fus));
+ mp->user_data = NULL;
+
+ struct mg_str lfn = local_name_fn(nc, mg_mk_str(mp->file_name));
+ if (lfn.p == NULL || lfn.len == 0) {
+ LOG(LL_ERROR, ("%p Not allowed to upload %s", nc, mp->file_name));
+ mg_printf(nc,
+ "HTTP/1.1 403 Not Allowed\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n"
+ "Not allowed to upload %s\r\n",
+ mp->file_name);
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ return;
+ }
+ fus->lfn = (char *) malloc(lfn.len + 1);
+ memcpy(fus->lfn, lfn.p, lfn.len);
+ fus->lfn[lfn.len] = '\0';
+ if (lfn.p != mp->file_name) free((char *) lfn.p);
+ LOG(LL_DEBUG,
+ ("%p Receiving file %s -> %s", nc, mp->file_name, fus->lfn));
+ fus->fp = fopen(fus->lfn, "w");
+ if (fus->fp == NULL) {
+ mg_printf(nc,
+ "HTTP/1.1 500 Internal Server Error\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n");
+ LOG(LL_ERROR, ("Failed to open %s: %d\n", fus->lfn, errno));
+ mg_printf(nc, "Failed to open %s: %d\n", fus->lfn, errno);
+ /* Do not close the connection just yet, discard remainder of the data.
+ * This is because at the time of writing some browsers (Chrome) fail to
+ * render response before all the data is sent. */
+ }
+ mp->user_data = (void *) fus;
+ break;
+ }
+ case MG_EV_HTTP_PART_DATA: {
+ struct mg_http_multipart_part *mp =
+ (struct mg_http_multipart_part *) ev_data;
+ struct file_upload_state *fus =
+ (struct file_upload_state *) mp->user_data;
+ if (fus == NULL || fus->fp == NULL) break;
+ if (fwrite(mp->data.p, 1, mp->data.len, fus->fp) != mp->data.len) {
+ LOG(LL_ERROR, ("Failed to write to %s: %d, wrote %d", fus->lfn, errno,
+ (int) fus->num_recd));
+ if (errno == ENOSPC
+#ifdef SPIFFS_ERR_FULL
+ || errno == SPIFFS_ERR_FULL
+#endif
+ ) {
+ mg_printf(nc,
+ "HTTP/1.1 413 Payload Too Large\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n");
+ mg_printf(nc, "Failed to write to %s: no space left; wrote %d\r\n",
+ fus->lfn, (int) fus->num_recd);
+ } else {
+ mg_printf(nc,
+ "HTTP/1.1 500 Internal Server Error\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n");
+ mg_printf(nc, "Failed to write to %s: %d, wrote %d", mp->file_name,
+ errno, (int) fus->num_recd);
+ }
+ fclose(fus->fp);
+ remove(fus->lfn);
+ fus->fp = NULL;
+ /* Do not close the connection just yet, discard remainder of the data.
+ * This is because at the time of writing some browsers (Chrome) fail to
+ * render response before all the data is sent. */
+ return;
+ }
+ fus->num_recd += mp->data.len;
+ LOG(LL_DEBUG, ("%p rec'd %d bytes, %d total", nc, (int) mp->data.len,
+ (int) fus->num_recd));
+ break;
+ }
+ case MG_EV_HTTP_PART_END: {
+ struct mg_http_multipart_part *mp =
+ (struct mg_http_multipart_part *) ev_data;
+ struct file_upload_state *fus =
+ (struct file_upload_state *) mp->user_data;
+ if (fus == NULL) break;
+ if (mp->status >= 0 && fus->fp != NULL) {
+ LOG(LL_DEBUG, ("%p Uploaded %s (%s), %d bytes", nc, mp->file_name,
+ fus->lfn, (int) fus->num_recd));
+ mg_printf(nc,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Connection: close\r\n\r\n"
+ "Ok, %s - %d bytes.\r\n",
+ mp->file_name, (int) fus->num_recd);
+ } else {
+ LOG(LL_ERROR, ("Failed to store %s (%s)", mp->file_name, fus->lfn));
+ /*
+ * mp->status < 0 means connection was terminated, so no reason to send
+ * HTTP reply
+ */
+ }
+ if (fus->fp != NULL) fclose(fus->fp);
+ free(fus->lfn);
+ free(fus);
+ mp->user_data = NULL;
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ break;
+ }
+ }
+}
+
+#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
+
+void mg_set_protocol_http_websocket(struct mg_connection *nc) {
+ nc->proto_handler = mg_http_handler;
+}
+
+#ifndef MG_DISABLE_HTTP_WEBSOCKET
+
+void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,
+ const char *host, const char *protocol,
+ const char *extra_headers) {
+ /* pretty poor source of randomness, TODO fix */
+ unsigned long random = (unsigned long) path;
+ char key[sizeof(random) * 3];
+
+ mg_base64_encode((unsigned char *) &random, sizeof(random), key);
+ mg_printf(nc,
+ "GET %s HTTP/1.1\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "Sec-WebSocket-Key: %s\r\n",
+ path, key);
+
+ /* TODO(mkm): take default hostname from http proto data if host == NULL */
+ if (host != MG_WS_NO_HOST_HEADER_MAGIC) {
+ mg_printf(nc, "Host: %s\r\n", host);
+ }
+ if (protocol != NULL) {
+ mg_printf(nc, "Sec-WebSocket-Protocol: %s\r\n", protocol);
+ }
+ if (extra_headers != NULL) {
+ mg_printf(nc, "%s", extra_headers);
+ }
+ mg_printf(nc, "\r\n");
+}
+
+void mg_send_websocket_handshake(struct mg_connection *nc, const char *path,
+ const char *extra_headers) {
+ mg_send_websocket_handshake2(nc, path, MG_WS_NO_HOST_HEADER_MAGIC, NULL,
+ extra_headers);
+}
+
+#endif /* MG_DISABLE_HTTP_WEBSOCKET */
+
+void mg_send_response_line(struct mg_connection *nc, int status_code,
+ const char *extra_headers) {
+ const char *status_message = "OK";
+ switch (status_code) {
+ case 206:
+ status_message = "Partial Content";
+ break;
+ case 301:
+ status_message = "Moved";
+ break;
+ case 302:
+ status_message = "Found";
+ break;
+ case 401:
+ status_message = "Unauthorized";
+ break;
+ case 403:
+ status_message = "Forbidden";
+ break;
+ case 404:
+ status_message = "Not Found";
+ break;
+ case 416:
+ status_message = "Requested range not satisfiable";
+ break;
+ case 418:
+ status_message = "I'm a teapot";
+ break;
+ case 500:
+ status_message = "Internal Server Error";
+ break;
+ }
+ mg_printf(nc, "HTTP/1.1 %d %s\r\nServer: %s\r\n", status_code, status_message,
+ mg_version_header);
+ if (extra_headers != NULL) {
+ mg_printf(nc, "%s\r\n", extra_headers);
+ }
+}
+
+void mg_send_head(struct mg_connection *c, int status_code,
+ int64_t content_length, const char *extra_headers) {
+ mg_send_response_line(c, status_code, extra_headers);
+ if (content_length < 0) {
+ mg_printf(c, "%s", "Transfer-Encoding: chunked\r\n");
+ } else {
+ mg_printf(c, "Content-Length: %" INT64_FMT "\r\n", content_length);
+ }
+ mg_send(c, "\r\n", 2);
+}
+
+#ifdef MG_DISABLE_FILESYSTEM
+void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
+ struct mg_serve_http_opts opts) {
+ mg_send_head(nc, 501, 0, NULL);
+}
+#else
+static void mg_http_send_error(struct mg_connection *nc, int code,
+ const char *reason) {
+ if (!reason) reason = "";
+ DBG(("%p %d %s", nc, code, reason));
+ mg_send_head(nc, code, strlen(reason),
+ "Content-Type: text/plain\r\nConnection: close");
+ mg_send(nc, reason, strlen(reason));
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+}
+#ifndef MG_DISABLE_SSI
+static void mg_send_ssi_file(struct mg_connection *, const char *, FILE *, int,
+ const struct mg_serve_http_opts *);
+
+static void mg_send_file_data(struct mg_connection *nc, FILE *fp) {
+ char buf[BUFSIZ];
+ size_t n;
+ while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
+ mg_send(nc, buf, n);
+ }
+}
+
+static void mg_do_ssi_include(struct mg_connection *nc, const char *ssi,
+ char *tag, int include_level,
+ const struct mg_serve_http_opts *opts) {
+ char file_name[BUFSIZ], path[MAX_PATH_SIZE], *p;
+ FILE *fp;
+
+ /*
+ * sscanf() is safe here, since send_ssi_file() also uses buffer
+ * of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
+ */
+ if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
+ /* File name is relative to the webserver root */
+ snprintf(path, sizeof(path), "%s/%s", opts->document_root, file_name);
+ } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) {
+ /*
+ * File name is relative to the webserver working directory
+ * or it is absolute system path
+ */
+ snprintf(path, sizeof(path), "%s", file_name);
+ } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 ||
+ sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
+ /* File name is relative to the currect document */
+ snprintf(path, sizeof(path), "%s", ssi);
+ if ((p = strrchr(path, DIRSEP)) != NULL) {
+ p[1] = '\0';
+ }
+ snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", file_name);
+ } else {
+ mg_printf(nc, "Bad SSI #include: [%s]", tag);
+ return;
+ }
+
+ if ((fp = fopen(path, "rb")) == NULL) {
+ mg_printf(nc, "SSI include error: fopen(%s): %s", path, strerror(errno));
+ } else {
+ mg_set_close_on_exec(fileno(fp));
+ if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) >
+ 0) {
+ mg_send_ssi_file(nc, path, fp, include_level + 1, opts);
+ } else {
+ mg_send_file_data(nc, fp);
+ }
+ fclose(fp);
+ }
+}
+
+#ifndef MG_DISABLE_POPEN
+static void do_ssi_exec(struct mg_connection *nc, char *tag) {
+ char cmd[BUFSIZ];
+ FILE *fp;
+
+ if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
+ mg_printf(nc, "Bad SSI #exec: [%s]", tag);
+ } else if ((fp = popen(cmd, "r")) == NULL) {
+ mg_printf(nc, "Cannot SSI #exec: [%s]: %s", cmd, strerror(errno));
+ } else {
+ mg_send_file_data(nc, fp);
+ pclose(fp);
+ }
+}
+#endif /* !MG_DISABLE_POPEN */
+
+static void mg_do_ssi_call(struct mg_connection *nc, char *tag) {
+ mg_call(nc, NULL, MG_EV_SSI_CALL, tag);
+}
+
+/*
+ * SSI directive has the following format:
+ *
+ */
+static void mg_send_ssi_file(struct mg_connection *nc, const char *path,
+ FILE *fp, int include_level,
+ const struct mg_serve_http_opts *opts) {
+ static const struct mg_str btag = MG_MK_STR(" */
+ buf[i--] = '\0';
+ while (i > 0 && buf[i] == ' ') {
+ buf[i--] = '\0';
+ }
+
+ /* Handle known SSI directives */
+ if (memcmp(p, d_include.p, d_include.len) == 0) {
+ mg_do_ssi_include(nc, path, p + d_include.len + 1, include_level, opts);
+ } else if (memcmp(p, d_call.p, d_call.len) == 0) {
+ mg_do_ssi_call(nc, p + d_call.len + 1);
+#ifndef MG_DISABLE_POPEN
+ } else if (memcmp(p, d_exec.p, d_exec.len) == 0) {
+ do_ssi_exec(nc, p + d_exec.len + 1);
+#endif
+ } else {
+ /* Silently ignore unknown SSI directive. */
+ }
+ len = 0;
+ } else if (ch == '<') {
+ in_ssi_tag = 1;
+ if (len > 0) {
+ mg_send(nc, buf, (size_t) len);
+ }
+ len = 0;
+ buf[len++] = ch & 0xff;
+ } else if (in_ssi_tag) {
+ if (len == (int) btag.len && memcmp(buf, btag.p, btag.len) != 0) {
+ /* Not an SSI tag */
+ in_ssi_tag = 0;
+ } else if (len == (int) sizeof(buf) - 2) {
+ mg_printf(nc, "%s: SSI tag is too large", path);
+ len = 0;
+ }
+ buf[len++] = ch & 0xff;
+ } else {
+ buf[len++] = ch & 0xff;
+ if (len == (int) sizeof(buf)) {
+ mg_send(nc, buf, (size_t) len);
+ len = 0;
+ }
+ }
+ }
+
+ /* Send the rest of buffered data */
+ if (len > 0) {
+ mg_send(nc, buf, (size_t) len);
+ }
+}
+
+static void mg_handle_ssi_request(struct mg_connection *nc, const char *path,
+ const struct mg_serve_http_opts *opts) {
+ FILE *fp;
+ struct mg_str mime_type;
+
+ if ((fp = fopen(path, "rb")) == NULL) {
+ mg_http_send_error(nc, 404, NULL);
+ } else {
+ mg_set_close_on_exec(fileno(fp));
+
+ mime_type = mg_get_mime_type(path, "text/plain", opts);
+ mg_send_response_line(nc, 200, opts->extra_headers);
+ mg_printf(nc,
+ "Content-Type: %.*s\r\n"
+ "Connection: close\r\n\r\n",
+ (int) mime_type.len, mime_type.p);
+ mg_send_ssi_file(nc, path, fp, 0, opts);
+ fclose(fp);
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ }
+}
+#else
+static void mg_handle_ssi_request(struct mg_connection *nc, const char *path,
+ const struct mg_serve_http_opts *opts) {
+ (void) path;
+ (void) opts;
+ mg_http_send_error(nc, 500, "SSI disabled");
+}
+#endif /* MG_DISABLE_SSI */
+
+static void mg_http_construct_etag(char *buf, size_t buf_len,
+ const cs_stat_t *st) {
+ snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", (unsigned long) st->st_mtime,
+ (int64_t) st->st_size);
+}
+static void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t) {
+ strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
+}
+
+static int mg_http_parse_range_header(const struct mg_str *header, int64_t *a,
+ int64_t *b) {
+ /*
+ * There is no snscanf. Headers are not guaranteed to be NUL-terminated,
+ * so we have this. Ugh.
+ */
+ int result;
+ char *p = (char *) MG_MALLOC(header->len + 1);
+ if (p == NULL) return 0;
+ memcpy(p, header->p, header->len);
+ p[header->len] = '\0';
+ result = sscanf(p, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
+ MG_FREE(p);
+ return result;
+}
+
+static void mg_http_send_file2(struct mg_connection *nc, const char *path,
+ cs_stat_t *st, struct http_message *hm,
+ struct mg_serve_http_opts *opts) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ struct mg_str mime_type;
+
+ DBG(("%p [%s]", nc, path));
+ mg_http_free_proto_data_file(&pd->file);
+ if ((pd->file.fp = fopen(path, "rb")) == NULL) {
+ int code;
+ switch (errno) {
+ case EACCES:
+ code = 403;
+ break;
+ case ENOENT:
+ code = 404;
+ break;
+ default:
+ code = 500;
+ };
+ mg_http_send_error(nc, code, "Open failed");
+ } else if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern),
+ path) > 0) {
+ mg_handle_ssi_request(nc, path, opts);
+ } else {
+ char etag[50], current_time[50], last_modified[50], range[70];
+ time_t t = time(NULL);
+ int64_t r1 = 0, r2 = 0, cl = st->st_size;
+ struct mg_str *range_hdr = mg_get_http_header(hm, "Range");
+ int n, status_code = 200;
+
+ /* Handle Range header */
+ range[0] = '\0';
+ if (range_hdr != NULL &&
+ (n = mg_http_parse_range_header(range_hdr, &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_code = 416;
+ cl = 0;
+ snprintf(range, sizeof(range),
+ "Content-Range: bytes */%" INT64_FMT "\r\n",
+ (int64_t) st->st_size);
+ } else {
+ status_code = 206;
+ cl = r2 - r1 + 1;
+ snprintf(range, sizeof(range), "Content-Range: bytes %" INT64_FMT
+ "-%" INT64_FMT "/%" INT64_FMT "\r\n",
+ r1, r1 + cl - 1, (int64_t) st->st_size);
+#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \
+ _XOPEN_SOURCE >= 600
+ fseeko(pd->file.fp, r1, SEEK_SET);
+#else
+ fseek(pd->file.fp, r1, SEEK_SET);
+#endif
+ }
+ }
+
+#ifndef MG_DISABLE_HTTP_KEEP_ALIVE
+ {
+ struct mg_str *conn_hdr = mg_get_http_header(hm, "Connection");
+ if (conn_hdr != NULL) {
+ pd->file.keepalive = (mg_vcasecmp(conn_hdr, "keep-alive") == 0);
+ } else {
+ pd->file.keepalive = (mg_vcmp(&hm->proto, "HTTP/1.1") == 0);
+ }
+ }
+#endif
+
+ mg_http_construct_etag(etag, sizeof(etag), st);
+ mg_gmt_time_string(current_time, sizeof(current_time), &t);
+ mg_gmt_time_string(last_modified, sizeof(last_modified), &st->st_mtime);
+ mime_type = mg_get_mime_type(path, "text/plain", opts);
+ /*
+ * Content length casted to size_t because:
+ * 1) that's the maximum buffer size anyway
+ * 2) ESP8266 RTOS SDK newlib vprintf cannot contain a 64bit arg at non-last
+ * position
+ * TODO(mkm): fix ESP8266 RTOS SDK
+ */
+ mg_send_response_line(nc, status_code, opts->extra_headers);
+ mg_printf(nc,
+ "Date: %s\r\n"
+ "Last-Modified: %s\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Content-Type: %.*s\r\n"
+ "Connection: %s\r\n"
+ "Content-Length: %" SIZE_T_FMT
+ "\r\n"
+ "%sEtag: %s\r\n\r\n",
+ current_time, last_modified, (int) mime_type.len, mime_type.p,
+ (pd->file.keepalive ? "keep-alive" : "close"), (size_t) cl, range,
+ etag);
+
+ pd->file.cl = cl;
+ pd->file.type = DATA_FILE;
+ mg_http_transfer_file_data(nc);
+ }
+}
+
+#endif
+
+int mg_url_decode(const char *src, int src_len, char *dst, int dst_len,
+ int is_form_url_encoded) {
+ int i, j, a, b;
+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
+
+ for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
+ if (src[i] == '%') {
+ if (i < src_len - 2 && isxdigit(*(const unsigned char *) (src + i + 1)) &&
+ isxdigit(*(const unsigned char *) (src + i + 2))) {
+ a = tolower(*(const unsigned char *) (src + i + 1));
+ b = tolower(*(const unsigned char *) (src + i + 2));
+ dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
+ i += 2;
+ } else {
+ return -1;
+ }
+ } else if (is_form_url_encoded && src[i] == '+') {
+ dst[j] = ' ';
+ } else {
+ dst[j] = src[i];
+ }
+ }
+
+ dst[j] = '\0'; /* Null-terminate the destination */
+
+ return i >= src_len ? j : -1;
+}
+
+int mg_get_http_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;
+ } else if (buf->p == NULL || name == NULL || buf->len == 0) {
+ len = -1;
+ dst[0] = '\0';
+ } else {
+ name_len = strlen(name);
+ e = buf->p + buf->len;
+ len = -1;
+ dst[0] = '\0';
+
+ for (p = buf->p; p + name_len < e; p++) {
+ if ((p == buf->p || p[-1] == '&') && p[name_len] == '=' &&
+ !mg_ncasecmp(name, p, name_len)) {
+ p += name_len + 1;
+ s = (const char *) memchr(p, '&', (size_t)(e - p));
+ if (s == NULL) {
+ s = e;
+ }
+ len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
+ if (len == -1) {
+ len = -2;
+ }
+ break;
+ }
+ }
+ }
+
+ return len;
+}
+
+void mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len) {
+ char chunk_size[50];
+ int n;
+
+ n = snprintf(chunk_size, sizeof(chunk_size), "%lX\r\n", (unsigned long) len);
+ mg_send(nc, chunk_size, n);
+ mg_send(nc, buf, len);
+ mg_send(nc, "\r\n", 2);
+}
+
+void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...) {
+ char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
+ int len;
+ va_list ap;
+
+ va_start(ap, fmt);
+ len = mg_avprintf(&buf, sizeof(mem), fmt, ap);
+ va_end(ap);
+
+ if (len >= 0) {
+ mg_send_http_chunk(nc, buf, len);
+ }
+
+ /* LCOV_EXCL_START */
+ if (buf != mem && buf != NULL) {
+ MG_FREE(buf);
+ }
+ /* LCOV_EXCL_STOP */
+}
+
+void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) {
+ char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
+ int i, j, len;
+ va_list ap;
+
+ va_start(ap, fmt);
+ len = mg_avprintf(&buf, sizeof(mem), fmt, ap);
+ va_end(ap);
+
+ if (len >= 0) {
+ for (i = j = 0; i < len; i++) {
+ if (buf[i] == '<' || buf[i] == '>') {
+ mg_send(nc, buf + j, i - j);
+ mg_send(nc, buf[i] == '<' ? "<" : ">", 4);
+ j = i + 1;
+ }
+ }
+ mg_send(nc, buf + j, i - j);
+ }
+
+ /* LCOV_EXCL_START */
+ if (buf != mem && buf != NULL) {
+ MG_FREE(buf);
+ }
+ /* LCOV_EXCL_STOP */
+}
+
+int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
+ size_t buf_size) {
+ int ch = ' ', ch1 = ',', len = 0, n = strlen(var_name);
+ const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL;
+
+ if (buf != NULL && buf_size > 0) buf[0] = '\0';
+ if (hdr == NULL) return 0;
+
+ /* Find where variable starts */
+ for (s = hdr->p; s != NULL && s + n < end; s++) {
+ if ((s == hdr->p || s[-1] == ch || s[-1] == ch1) && s[n] == '=' &&
+ !memcmp(s, var_name, n))
+ break;
+ }
+
+ if (s != NULL && &s[n + 1] < end) {
+ s += n + 1;
+ if (*s == '"' || *s == '\'') {
+ ch = ch1 = *s++;
+ }
+ p = s;
+ while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) {
+ if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++;
+ buf[len++] = *p++;
+ }
+ if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
+ len = 0;
+ } else {
+ if (len > 0 && s[len - 1] == ',') len--;
+ if (len > 0 && s[len - 1] == ';') len--;
+ buf[len] = '\0';
+ }
+ }
+
+ return len;
+}
+
+#ifndef MG_DISABLE_FILESYSTEM
+static int mg_is_file_hidden(const char *path,
+ const struct mg_serve_http_opts *opts,
+ int exclude_specials) {
+ const char *p1 = opts->per_directory_auth_file;
+ const char *p2 = opts->hidden_file_pattern;
+
+ /* Strip directory path from the file name */
+ const char *pdir = strrchr(path, DIRSEP);
+ if (pdir != NULL) {
+ path = pdir + 1;
+ }
+
+ return (exclude_specials && (!strcmp(path, ".") || !strcmp(path, ".."))) ||
+ (p1 != NULL &&
+ mg_match_prefix(p1, strlen(p1), path) == (int) strlen(p1)) ||
+ (p2 != NULL && mg_match_prefix(p2, strlen(p2), path) > 0);
+}
+
+#ifndef MG_DISABLE_HTTP_DIGEST_AUTH
+static void mg_mkmd5resp(const char *method, size_t method_len, const char *uri,
+ size_t uri_len, const char *ha1, size_t ha1_len,
+ const char *nonce, size_t nonce_len, const char *nc,
+ size_t nc_len, const char *cnonce, size_t cnonce_len,
+ const char *qop, size_t qop_len, char *resp) {
+ static const char colon[] = ":";
+ static const size_t one = 1;
+ char ha2[33];
+
+ cs_md5(ha2, method, method_len, colon, one, uri, uri_len, NULL);
+ cs_md5(resp, ha1, ha1_len, colon, one, nonce, nonce_len, colon, one, nc,
+ nc_len, colon, one, cnonce, cnonce_len, colon, one, qop, qop_len,
+ colon, one, ha2, sizeof(ha2) - 1, NULL);
+}
+
+int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
+ const char *method, const char *uri,
+ const char *auth_domain, const char *user,
+ const char *passwd) {
+ static const char colon[] = ":", qop[] = "auth";
+ static const size_t one = 1;
+ char ha1[33], resp[33], cnonce[40];
+
+ snprintf(cnonce, sizeof(cnonce), "%x", (unsigned int) time(NULL));
+ cs_md5(ha1, user, (size_t) strlen(user), colon, one, auth_domain,
+ (size_t) strlen(auth_domain), colon, one, passwd,
+ (size_t) strlen(passwd), NULL);
+ mg_mkmd5resp(method, strlen(method), uri, strlen(uri), ha1, sizeof(ha1) - 1,
+ cnonce, strlen(cnonce), "1", one, cnonce, strlen(cnonce), qop,
+ sizeof(qop) - 1, resp);
+ return snprintf(buf, buf_len,
+ "Authorization: Digest username=\"%s\","
+ "realm=\"%s\",uri=\"%s\",qop=%s,nc=1,cnonce=%s,"
+ "nonce=%s,response=%s\r\n",
+ user, auth_domain, uri, qop, cnonce, cnonce, resp);
+}
+
+/*
+ * Check for authentication timeout.
+ * Clients send time stamp encoded in nonce. Make sure it is not too old,
+ * to prevent replay attacks.
+ * Assumption: nonce is a hexadecimal number of seconds since 1970.
+ */
+static int mg_check_nonce(const char *nonce) {
+ unsigned long now = (unsigned long) time(NULL);
+ unsigned long val = (unsigned long) strtoul(nonce, NULL, 16);
+ return now < val || now - val < 3600;
+}
+
+int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
+ FILE *fp) {
+ struct mg_str *hdr;
+ char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];
+ char user[50], cnonce[33], response[40], uri[200], qop[20], nc[20], nonce[30];
+ char expected_response[33];
+
+ /* Parse "Authorization:" header, fail fast on parse error */
+ if (hm == NULL || fp == NULL ||
+ (hdr = mg_get_http_header(hm, "Authorization")) == NULL ||
+ mg_http_parse_header(hdr, "username", user, sizeof(user)) == 0 ||
+ mg_http_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce)) == 0 ||
+ mg_http_parse_header(hdr, "response", response, sizeof(response)) == 0 ||
+ mg_http_parse_header(hdr, "uri", uri, sizeof(uri)) == 0 ||
+ mg_http_parse_header(hdr, "qop", qop, sizeof(qop)) == 0 ||
+ mg_http_parse_header(hdr, "nc", nc, sizeof(nc)) == 0 ||
+ mg_http_parse_header(hdr, "nonce", nonce, sizeof(nonce)) == 0 ||
+ mg_check_nonce(nonce) == 0) {
+ return 0;
+ }
+
+ /*
+ * Read passwords file line by line. If should have htdigest format,
+ * i.e. each line should be a colon-separated sequence:
+ * USER_NAME:DOMAIN_NAME:HA1_HASH_OF_USER_DOMAIN_AND_PASSWORD
+ */
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (sscanf(buf, "%[^:]:%[^:]:%s", f_user, f_domain, f_ha1) == 3 &&
+ strcmp(user, f_user) == 0 &&
+ /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
+ strcmp(auth_domain, f_domain) == 0) {
+ /* User and domain matched, check the password */
+ mg_mkmd5resp(
+ hm->method.p, hm->method.len, hm->uri.p,
+ hm->uri.len + (hm->query_string.len ? hm->query_string.len + 1 : 0),
+ f_ha1, strlen(f_ha1), nonce, strlen(nonce), nc, strlen(nc), cnonce,
+ strlen(cnonce), qop, strlen(qop), expected_response);
+ return mg_casecmp(response, expected_response) == 0;
+ }
+ }
+
+ /* None of the entries in the passwords file matched - return failure */
+ return 0;
+}
+
+static int mg_is_authorized(struct http_message *hm, const char *path,
+ int is_directory, const char *domain,
+ const char *passwords_file,
+ int is_global_pass_file) {
+ char buf[MG_MAX_PATH];
+ const char *p;
+ FILE *fp;
+ int authorized = 1;
+
+ if (domain != NULL && passwords_file != NULL) {
+ if (is_global_pass_file) {
+ fp = fopen(passwords_file, "r");
+ } else if (is_directory) {
+ snprintf(buf, sizeof(buf), "%s%c%s", path, DIRSEP, passwords_file);
+ fp = fopen(buf, "r");
+ } else {
+ p = strrchr(path, DIRSEP);
+ if (p == NULL) p = path;
+ snprintf(buf, sizeof(buf), "%.*s%c%s", (int) (p - path), path, DIRSEP,
+ passwords_file);
+ fp = fopen(buf, "r");
+ }
+
+ if (fp != NULL) {
+ authorized = mg_http_check_digest_auth(hm, domain, fp);
+ fclose(fp);
+ }
+ }
+
+ DBG(("%s %s %d %d", path, passwords_file ? passwords_file : "",
+ is_global_pass_file, authorized));
+ return authorized;
+}
+#else
+static int mg_is_authorized(struct http_message *hm, const char *path,
+ int is_directory, const char *domain,
+ const char *passwords_file,
+ int is_global_pass_file) {
+ (void) hm;
+ (void) path;
+ (void) is_directory;
+ (void) domain;
+ (void) passwords_file;
+ (void) is_global_pass_file;
+ return 1;
+}
+#endif
+
+#ifndef MG_DISABLE_DIRECTORY_LISTING
+static size_t mg_url_encode(const char *src, size_t s_len, char *dst,
+ size_t dst_len) {
+ static const char *dont_escape = "._-$,;~()/";
+ static const char *hex = "0123456789abcdef";
+ size_t i = 0, j = 0;
+
+ for (i = j = 0; dst_len > 0 && i < s_len && j + 2 < dst_len - 1; i++, j++) {
+ if (isalnum(*(const unsigned char *) (src + i)) ||
+ strchr(dont_escape, *(const unsigned char *) (src + i)) != NULL) {
+ dst[j] = src[i];
+ } else if (j + 3 < dst_len) {
+ dst[j] = '%';
+ dst[j + 1] = hex[(*(const unsigned char *) (src + i)) >> 4];
+ dst[j + 2] = hex[(*(const unsigned char *) (src + i)) & 0xf];
+ j += 2;
+ }
+ }
+
+ dst[j] = '\0';
+ return j;
+}
+
+static void mg_escape(const char *src, char *dst, size_t dst_len) {
+ size_t n = 0;
+ while (*src != '\0' && n + 5 < dst_len) {
+ unsigned char ch = *(unsigned char *) src++;
+ if (ch == '<') {
+ n += snprintf(dst + n, dst_len - n, "%s", "<");
+ } else {
+ dst[n++] = ch;
+ }
+ }
+ dst[n] = '\0';
+}
+
+static void mg_print_dir_entry(struct mg_connection *nc, const char *file_name,
+ cs_stat_t *stp) {
+ char size[64], mod[64], href[MAX_PATH_SIZE * 3], path[MAX_PATH_SIZE];
+ int64_t fsize = stp->st_size;
+ int is_dir = S_ISDIR(stp->st_mode);
+ const char *slash = is_dir ? "/" : "";
+
+ if (is_dir) {
+ snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
+ } else {
+ /*
+ * We use (double) cast below because MSVC 6 compiler cannot
+ * convert unsigned __int64 to double.
+ */
+ if (fsize < 1024) {
+ snprintf(size, sizeof(size), "%d", (int) fsize);
+ } else if (fsize < 0x100000) {
+ snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
+ } else if (fsize < 0x40000000) {
+ snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
+ } else {
+ snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
+ }
+ }
+ strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&stp->st_mtime));
+ mg_escape(file_name, path, sizeof(path));
+ mg_url_encode(file_name, strlen(file_name), href, sizeof(href));
+ mg_printf_http_chunk(nc,
+ "%s%s | "
+ "%s | %s |
\n",
+ href, slash, path, slash, mod, is_dir ? -1 : fsize,
+ size);
+}
+
+static void mg_scan_directory(struct mg_connection *nc, const char *dir,
+ const struct mg_serve_http_opts *opts,
+ void (*func)(struct mg_connection *, const char *,
+ cs_stat_t *)) {
+ char path[MAX_PATH_SIZE];
+ cs_stat_t st;
+ struct dirent *dp;
+ DIR *dirp;
+
+ DBG(("%p [%s]", nc, dir));
+ if ((dirp = (opendir(dir))) != NULL) {
+ while ((dp = readdir(dirp)) != NULL) {
+ /* Do not show current dir and hidden files */
+ if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) {
+ continue;
+ }
+ snprintf(path, sizeof(path), "%s/%s", dir, dp->d_name);
+ if (mg_stat(path, &st) == 0) {
+ func(nc, (const char *) dp->d_name, &st);
+ }
+ }
+ closedir(dirp);
+ } else {
+ DBG(("%p opendir(%s) -> %d", nc, dir, errno));
+ }
+}
+
+static void mg_send_directory_listing(struct mg_connection *nc, const char *dir,
+ struct http_message *hm,
+ struct mg_serve_http_opts *opts) {
+ static const char *sort_js_code =
+ "";
+
+ mg_send_response_line(nc, 200, opts->extra_headers);
+ mg_printf(nc, "%s: %s\r\n%s: %s\r\n\r\n", "Transfer-Encoding", "chunked",
+ "Content-Type", "text/html; charset=utf-8");
+
+ mg_printf_http_chunk(
+ nc,
+ "Index of %.*s%s%s"
+ "\n"
+ "Index of %.*s
\n"
+ "Name | "
+ "Modified"
+ " | Size |
"
+ "
|
\n"
+ "\n"
+ "",
+ (int) hm->uri.len, hm->uri.p, sort_js_code, sort_js_code2,
+ (int) hm->uri.len, hm->uri.p);
+ mg_scan_directory(nc, dir, opts, mg_print_dir_entry);
+ mg_printf_http_chunk(nc,
+ "
|
\n"
+ "
\n"
+ "%s\n"
+ "",
+ mg_version_header);
+ mg_send_http_chunk(nc, "", 0);
+ /* TODO(rojer): Remove when cesanta/dev/issues/197 is fixed. */
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+}
+#endif /* MG_DISABLE_DIRECTORY_LISTING */
+
+#ifndef MG_DISABLE_DAV
+static void mg_print_props(struct mg_connection *nc, const char *name,
+ cs_stat_t *stp) {
+ char mtime[64], buf[MAX_PATH_SIZE * 3];
+ time_t t = stp->st_mtime; /* store in local variable for NDK compile */
+ mg_gmt_time_string(mtime, sizeof(mtime), &t);
+ mg_url_encode(name, strlen(name), buf, sizeof(buf));
+ mg_printf(nc,
+ ""
+ "%s"
+ ""
+ ""
+ "%s"
+ "%" INT64_FMT
+ ""
+ "%s"
+ ""
+ "HTTP/1.1 200 OK"
+ ""
+ "\n",
+ buf, S_ISDIR(stp->st_mode) ? "" : "",
+ (int64_t) stp->st_size, mtime);
+}
+
+static void mg_handle_propfind(struct mg_connection *nc, const char *path,
+ cs_stat_t *stp, struct http_message *hm,
+ struct mg_serve_http_opts *opts) {
+ static const char header[] =
+ "HTTP/1.1 207 Multi-Status\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n\r\n"
+ ""
+ "\n";
+ static const char footer[] = "\n";
+ const struct mg_str *depth = mg_get_http_header(hm, "Depth");
+
+ /* Print properties for the requested resource itself */
+ if (S_ISDIR(stp->st_mode) &&
+ strcmp(opts->enable_directory_listing, "yes") != 0) {
+ mg_printf(nc, "%s", "HTTP/1.1 403 Directory Listing Denied\r\n\r\n");
+ } else {
+ char uri[MAX_PATH_SIZE];
+ mg_send(nc, header, sizeof(header) - 1);
+ snprintf(uri, sizeof(uri), "%.*s", (int) hm->uri.len, hm->uri.p);
+ mg_print_props(nc, uri, stp);
+ if (S_ISDIR(stp->st_mode) && (depth == NULL || mg_vcmp(depth, "0") != 0)) {
+ mg_scan_directory(nc, path, opts, mg_print_props);
+ }
+ mg_send(nc, footer, sizeof(footer) - 1);
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ }
+}
+
+#ifdef MG_ENABLE_FAKE_DAVLOCK
+/*
+ * Windows explorer (probably there are another WebDav clients like it)
+ * requires LOCK support in webdav. W/out this, it still works, but fails
+ * to save file: shows error message and offers "Save As".
+ * "Save as" works, but this message is very annoying.
+ * This is fake lock, which doesn't lock something, just returns LOCK token,
+ * UNLOCK always answers "OK".
+ * With this fake LOCK Windows Explorer looks happy and saves file.
+ * NOTE: that is not DAV LOCK imlementation, it is just a way to shut up
+ * Windows native DAV client. This is why FAKE LOCK is not enabed by default
+ */
+static void mg_handle_lock(struct mg_connection *nc, const char *path) {
+ static const char *reply =
+ "HTTP/1.1 207 Multi-Status\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/xml; charset=utf-8\r\n\r\n"
+ ""
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "opaquelocktoken:%s%u"
+ ""
+ ""
+ "\n"
+ ""
+ "\n";
+ mg_printf(nc, reply, path, (unsigned int) time(NULL));
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+}
+#endif
+
+static void mg_handle_mkcol(struct mg_connection *nc, const char *path,
+ struct http_message *hm) {
+ int status_code = 500;
+ if (hm->body.len != (size_t) ~0 && hm->body.len > 0) {
+ status_code = 415;
+ } else if (!mg_mkdir(path, 0755)) {
+ status_code = 201;
+ } else if (errno == EEXIST) {
+ status_code = 405;
+ } else if (errno == EACCES) {
+ status_code = 403;
+ } else if (errno == ENOENT) {
+ status_code = 409;
+ } else {
+ status_code = 500;
+ }
+ mg_http_send_error(nc, status_code, NULL);
+}
+
+static int mg_remove_directory(const struct mg_serve_http_opts *opts,
+ const char *dir) {
+ char path[MAX_PATH_SIZE];
+ struct dirent *dp;
+ cs_stat_t st;
+ DIR *dirp;
+
+ if ((dirp = opendir(dir)) == NULL) return 0;
+
+ while ((dp = readdir(dirp)) != NULL) {
+ if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) {
+ continue;
+ }
+ snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
+ mg_stat(path, &st);
+ if (S_ISDIR(st.st_mode)) {
+ mg_remove_directory(opts, path);
+ } else {
+ remove(path);
+ }
+ }
+ closedir(dirp);
+ rmdir(dir);
+
+ return 1;
+}
+
+static void mg_handle_move(struct mg_connection *c,
+ const struct mg_serve_http_opts *opts,
+ const char *path, struct http_message *hm) {
+ const struct mg_str *dest = mg_get_http_header(hm, "Destination");
+ if (dest == NULL) {
+ mg_http_send_error(c, 411, NULL);
+ } else {
+ const char *p = (char *) memchr(dest->p, '/', dest->len);
+ if (p != NULL && p[1] == '/' &&
+ (p = (char *) memchr(p + 2, '/', dest->p + dest->len - p)) != NULL) {
+ char buf[MAX_PATH_SIZE];
+ snprintf(buf, sizeof(buf), "%s%.*s", opts->dav_document_root,
+ (int) (dest->p + dest->len - p), p);
+ if (rename(path, buf) == 0) {
+ mg_http_send_error(c, 200, NULL);
+ } else {
+ mg_http_send_error(c, 418, NULL);
+ }
+ } else {
+ mg_http_send_error(c, 500, NULL);
+ }
+ }
+}
+
+static void mg_handle_delete(struct mg_connection *nc,
+ const struct mg_serve_http_opts *opts,
+ const char *path) {
+ cs_stat_t st;
+ if (mg_stat(path, &st) != 0) {
+ mg_http_send_error(nc, 404, NULL);
+ } else if (S_ISDIR(st.st_mode)) {
+ mg_remove_directory(opts, path);
+ mg_http_send_error(nc, 204, NULL);
+ } else if (remove(path) == 0) {
+ mg_http_send_error(nc, 204, NULL);
+ } else {
+ mg_http_send_error(nc, 423, NULL);
+ }
+}
+
+/* Return -1 on error, 1 on success. */
+static int mg_create_itermediate_directories(const char *path) {
+ const char *s;
+
+ /* Create intermediate directories if they do not exist */
+ for (s = path + 1; *s != '\0'; s++) {
+ if (*s == '/') {
+ char buf[MAX_PATH_SIZE];
+ cs_stat_t st;
+ snprintf(buf, sizeof(buf), "%.*s", (int) (s - path), path);
+ buf[sizeof(buf) - 1] = '\0';
+ if (mg_stat(buf, &st) != 0 && mg_mkdir(buf, 0755) != 0) {
+ return -1;
+ }
+ }
+ }
+
+ return 1;
+}
+
+static void mg_handle_put(struct mg_connection *nc, const char *path,
+ struct http_message *hm) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ cs_stat_t st;
+ const struct mg_str *cl_hdr = mg_get_http_header(hm, "Content-Length");
+ int rc, status_code = mg_stat(path, &st) == 0 ? 200 : 201;
+
+ mg_http_free_proto_data_file(&pd->file);
+ if ((rc = mg_create_itermediate_directories(path)) == 0) {
+ mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code);
+ } else if (rc == -1) {
+ mg_http_send_error(nc, 500, NULL);
+ } else if (cl_hdr == NULL) {
+ mg_http_send_error(nc, 411, NULL);
+ } else if ((pd->file.fp = fopen(path, "w+b")) == NULL) {
+ mg_http_send_error(nc, 500, NULL);
+ } else {
+ const struct mg_str *range_hdr = mg_get_http_header(hm, "Content-Range");
+ int64_t r1 = 0, r2 = 0;
+ pd->file.type = DATA_PUT;
+ mg_set_close_on_exec(fileno(pd->file.fp));
+ pd->file.cl = to64(cl_hdr->p);
+ if (range_hdr != NULL &&
+ mg_http_parse_range_header(range_hdr, &r1, &r2) > 0) {
+ status_code = 206;
+ fseeko(pd->file.fp, r1, SEEK_SET);
+ pd->file.cl = r2 > r1 ? r2 - r1 + 1 : pd->file.cl - r1;
+ }
+ mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code);
+ /* Remove HTTP request from the mbuf, leave only payload */
+ mbuf_remove(&nc->recv_mbuf, hm->message.len - hm->body.len);
+ mg_http_transfer_file_data(nc);
+ }
+}
+#endif /* MG_DISABLE_DAV */
+
+static int mg_is_dav_request(const struct mg_str *s) {
+ static const char *methods[] = {"PUT", "DELETE", "MKCOL", "PROPFIND", "MOVE"
+#ifdef MG_ENABLE_FAKE_DAVLOCK
+ ,
+ "LOCK", "UNLOCK"
+#endif
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(methods); i++) {
+ if (mg_vcmp(s, methods[i]) == 0) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Given a directory path, find one of the files specified in the
+ * comma-separated list of index files `list`.
+ * First found index file wins. If an index file is found, then gets
+ * appended to the `path`, stat-ed, and result of `stat()` passed to `stp`.
+ * If index file is not found, then `path` and `stp` remain unchanged.
+ */
+MG_INTERNAL void mg_find_index_file(const char *path, const char *list,
+ char **index_file, cs_stat_t *stp) {
+ struct mg_str vec;
+ size_t path_len = strlen(path);
+ int found = 0;
+ *index_file = NULL;
+
+ /* Traverse index files list. For each entry, append it to the given */
+ /* path and see if the file exists. If it exists, break the loop */
+ while ((list = mg_next_comma_list_entry(list, &vec, NULL)) != NULL) {
+ cs_stat_t st;
+ size_t len = path_len + 1 + vec.len + 1;
+ *index_file = (char *) MG_REALLOC(*index_file, len);
+ if (*index_file == NULL) break;
+ snprintf(*index_file, len, "%s%c%.*s", path, DIRSEP, (int) vec.len, vec.p);
+
+ /* Does it exist? Is it a file? */
+ if (mg_stat(*index_file, &st) == 0 && S_ISREG(st.st_mode)) {
+ /* Yes it does, break the loop */
+ *stp = st;
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ MG_FREE(*index_file);
+ *index_file = NULL;
+ }
+ DBG(("[%s] [%s]", path, (*index_file ? *index_file : "")));
+}
+
+static int mg_http_send_port_based_redirect(
+ struct mg_connection *c, struct http_message *hm,
+ const struct mg_serve_http_opts *opts) {
+ const char *rewrites = opts->url_rewrites;
+ struct mg_str a, b;
+ char local_port[20] = {'%'};
+
+ mg_conn_addr_to_str(c, local_port + 1, sizeof(local_port) - 1,
+ MG_SOCK_STRINGIFY_PORT);
+
+ while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {
+ if (mg_vcmp(&a, local_port) == 0) {
+ mg_send_response_line(c, 301, NULL);
+ mg_printf(c, "Content-Length: 0\r\nLocation: %.*s%.*s\r\n\r\n",
+ (int) b.len, b.p, (int) (hm->proto.p - hm->uri.p - 1),
+ hm->uri.p);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
+ const struct mg_serve_http_opts *opts,
+ char **local_path,
+ struct mg_str *remainder) {
+ int ok = 1;
+ const char *cp = hm->uri.p, *cp_end = hm->uri.p + hm->uri.len;
+ struct mg_str root = {NULL, 0};
+ const char *file_uri_start = cp;
+ *local_path = NULL;
+ remainder->p = NULL;
+ remainder->len = 0;
+
+ { /* 1. Determine which root to use. */
+ const char *rewrites = opts->url_rewrites;
+ struct mg_str *hh = mg_get_http_header(hm, "Host");
+ struct mg_str a, b;
+ /* Check rewrites first. */
+ while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {
+ if (a.len > 1 && a.p[0] == '@') {
+ /* Host rewrite. */
+ if (hh != NULL && hh->len == a.len - 1 &&
+ mg_ncasecmp(a.p + 1, hh->p, a.len - 1) == 0) {
+ root = b;
+ break;
+ }
+ } else {
+ /* Regular rewrite, URI=directory */
+ int match_len = mg_match_prefix_n(a, hm->uri);
+ if (match_len > 0) {
+ file_uri_start = hm->uri.p + match_len;
+ if (*file_uri_start == '/' || file_uri_start == cp_end) {
+ /* Match ended at component boundary, ok. */
+ } else if (*(file_uri_start - 1) == '/') {
+ /* Pattern ends with '/', backtrack. */
+ file_uri_start--;
+ } else {
+ /* No match: must fall on the component boundary. */
+ continue;
+ }
+ root = b;
+ break;
+ }
+ }
+ }
+ /* If no rewrite rules matched, use DAV or regular document root. */
+ if (root.p == NULL) {
+#ifndef MG_DISABLE_DAV
+ if (opts->dav_document_root != NULL && mg_is_dav_request(&hm->method)) {
+ root.p = opts->dav_document_root;
+ root.len = strlen(opts->dav_document_root);
+ } else
+#endif
+ {
+ root.p = opts->document_root;
+ root.len = strlen(opts->document_root);
+ }
+ }
+ assert(root.p != NULL && root.len > 0);
+ }
+
+ { /* 2. Find where in the canonical URI path the local path ends. */
+ const char *u = file_uri_start + 1;
+ char *lp = (char *) MG_MALLOC(root.len + hm->uri.len + 1);
+ char *lp_end = lp + root.len + hm->uri.len + 1;
+ char *p = lp, *ps;
+ int exists = 1;
+ if (lp == NULL) {
+ ok = 0;
+ goto out;
+ }
+ memcpy(p, root.p, root.len);
+ p += root.len;
+ if (*(p - 1) == DIRSEP) p--;
+ *p = '\0';
+ ps = p;
+
+ /* Chop off URI path components one by one and build local path. */
+ while (u <= cp_end) {
+ const char *next = u;
+ struct mg_str component;
+ if (exists) {
+ cs_stat_t st;
+ exists = (mg_stat(lp, &st) == 0);
+ if (exists && S_ISREG(st.st_mode)) {
+ /* We found the terminal, the rest of the URI (if any) is path_info.
+ */
+ if (*(u - 1) == '/') u--;
+ break;
+ }
+ }
+ if (u >= cp_end) break;
+ parse_uri_component((const char **) &next, cp_end, '/', &component);
+ if (component.len > 0) {
+ int len;
+ memmove(p + 1, component.p, component.len);
+ len = mg_url_decode(p + 1, component.len, p + 1, lp_end - p - 1, 0);
+ if (len <= 0) {
+ ok = 0;
+ break;
+ }
+ component.p = p + 1;
+ component.len = len;
+ if (mg_vcmp(&component, ".") == 0) {
+ /* Yum. */
+ } else if (mg_vcmp(&component, "..") == 0) {
+ while (p > ps && *p != DIRSEP) p--;
+ *p = '\0';
+ } else {
+ size_t i;
+#ifdef _WIN32
+ /* On Windows, make sure it's valid Unicode (no funny stuff). */
+ wchar_t buf[MG_MAX_PATH * 2];
+ if (to_wchar(component.p, buf, MG_MAX_PATH) == 0) {
+ DBG(("[%.*s] smells funny", (int) component.len, component.p));
+ ok = 0;
+ break;
+ }
+#endif
+ *p++ = DIRSEP;
+ /* No NULs and DIRSEPs in the component (percent-encoded). */
+ for (i = 0; i < component.len; i++, p++) {
+ if (*p == '\0' || *p == DIRSEP
+#ifdef _WIN32
+ /* On Windows, "/" is also accepted, so check for that too. */
+ ||
+ *p == '/'
+#endif
+ ) {
+ ok = 0;
+ break;
+ }
+ }
+ }
+ }
+ u = next;
+ }
+ if (ok) {
+ *local_path = lp;
+ remainder->p = u;
+ remainder->len = cp_end - u;
+ } else {
+ MG_FREE(lp);
+ }
+ }
+
+out:
+ DBG(("'%.*s' -> '%s' + '%.*s'", (int) hm->uri.len, hm->uri.p,
+ *local_path ? *local_path : "", (int) remainder->len, remainder->p));
+ return ok;
+}
+
+#ifndef MG_DISABLE_CGI
+#ifdef _WIN32
+struct mg_threadparam {
+ sock_t s;
+ HANDLE hPipe;
+};
+
+static int mg_wait_until_ready(sock_t sock, int for_read) {
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(sock, &set);
+ return select(sock + 1, for_read ? &set : 0, for_read ? 0 : &set, 0, 0) == 1;
+}
+
+static void *mg_push_to_stdin(void *arg) {
+ struct mg_threadparam *tp = (struct mg_threadparam *) arg;
+ int n, sent, stop = 0;
+ DWORD k;
+ char buf[BUFSIZ];
+
+ while (!stop && mg_wait_until_ready(tp->s, 1) &&
+ (n = recv(tp->s, buf, sizeof(buf), 0)) > 0) {
+ if (n == -1 && GetLastError() == WSAEWOULDBLOCK) continue;
+ for (sent = 0; !stop && sent < n; sent += k) {
+ if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) stop = 1;
+ }
+ }
+ DBG(("%s", "FORWARED EVERYTHING TO CGI"));
+ CloseHandle(tp->hPipe);
+ MG_FREE(tp);
+ _endthread();
+ return NULL;
+}
+
+static void *mg_pull_from_stdout(void *arg) {
+ struct mg_threadparam *tp = (struct mg_threadparam *) arg;
+ int k = 0, stop = 0;
+ DWORD n, sent;
+ char buf[BUFSIZ];
+
+ while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {
+ for (sent = 0; !stop && sent < n; sent += k) {
+ if (mg_wait_until_ready(tp->s, 0) &&
+ (k = send(tp->s, buf + sent, n - sent, 0)) <= 0)
+ stop = 1;
+ }
+ }
+ DBG(("%s", "EOF FROM CGI"));
+ CloseHandle(tp->hPipe);
+ shutdown(tp->s, 2); // Without this, IO thread may get truncated data
+ closesocket(tp->s);
+ MG_FREE(tp);
+ _endthread();
+ return NULL;
+}
+
+static void mg_spawn_stdio_thread(sock_t sock, HANDLE hPipe,
+ void *(*func)(void *)) {
+ struct mg_threadparam *tp = (struct mg_threadparam *) MG_MALLOC(sizeof(*tp));
+ if (tp != NULL) {
+ tp->s = sock;
+ tp->hPipe = hPipe;
+ mg_start_thread(func, tp);
+ }
+}
+
+static void mg_abs_path(const char *utf8_path, char *abs_path, size_t len) {
+ wchar_t buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE];
+ to_wchar(utf8_path, buf, ARRAY_SIZE(buf));
+ GetFullPathNameW(buf, ARRAY_SIZE(buf2), buf2, NULL);
+ WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 0);
+}
+
+static pid_t mg_start_process(const char *interp, const char *cmd,
+ const char *env, const char *envp[],
+ const char *dir, sock_t sock) {
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+ HANDLE a[2], b[2], me = GetCurrentProcess();
+ wchar_t wcmd[MAX_PATH_SIZE], full_dir[MAX_PATH_SIZE];
+ char buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE], buf5[MAX_PATH_SIZE],
+ buf4[MAX_PATH_SIZE], cmdline[MAX_PATH_SIZE];
+ DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
+ FILE *fp;
+
+ memset(&si, 0, sizeof(si));
+ memset(&pi, 0, sizeof(pi));
+
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+ si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+
+ CreatePipe(&a[0], &a[1], NULL, 0);
+ CreatePipe(&b[0], &b[1], NULL, 0);
+ DuplicateHandle(me, a[0], me, &si.hStdInput, 0, TRUE, flags);
+ DuplicateHandle(me, b[1], me, &si.hStdOutput, 0, TRUE, flags);
+
+ if (interp == NULL && (fp = fopen(cmd, "r")) != NULL) {
+ buf[0] = buf[1] = '\0';
+ fgets(buf, sizeof(buf), fp);
+ buf[sizeof(buf) - 1] = '\0';
+ if (buf[0] == '#' && buf[1] == '!') {
+ interp = buf + 2;
+ /* Trim leading spaces: https://github.com/cesanta/mongoose/issues/489 */
+ while (*interp != '\0' && isspace(*(unsigned char *) interp)) {
+ interp++;
+ }
+ }
+ fclose(fp);
+ }
+
+ snprintf(buf, sizeof(buf), "%s/%s", dir, cmd);
+ mg_abs_path(buf, buf2, ARRAY_SIZE(buf2));
+
+ mg_abs_path(dir, buf5, ARRAY_SIZE(buf5));
+ to_wchar(dir, full_dir, ARRAY_SIZE(full_dir));
+
+ if (interp != NULL) {
+ mg_abs_path(interp, buf4, ARRAY_SIZE(buf4));
+ snprintf(cmdline, sizeof(cmdline), "%s \"%s\"", buf4, buf2);
+ } else {
+ snprintf(cmdline, sizeof(cmdline), "\"%s\"", buf2);
+ }
+ to_wchar(cmdline, wcmd, ARRAY_SIZE(wcmd));
+
+ if (CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP,
+ (void *) env, full_dir, &si, &pi) != 0) {
+ mg_spawn_stdio_thread(sock, a[1], mg_push_to_stdin);
+ mg_spawn_stdio_thread(sock, b[0], mg_pull_from_stdout);
+ } else {
+ CloseHandle(a[1]);
+ CloseHandle(b[0]);
+ closesocket(sock);
+ }
+ DBG(("CGI command: [%ls] -> %p", wcmd, pi.hProcess));
+
+ /* Not closing a[0] and b[1] because we've used DUPLICATE_CLOSE_SOURCE */
+ CloseHandle(si.hStdOutput);
+ CloseHandle(si.hStdInput);
+ /* TODO(lsm): check if we need close process and thread handles too */
+ /* CloseHandle(pi.hThread); */
+ /* CloseHandle(pi.hProcess); */
+
+ return pi.hProcess;
+}
+#else
+static pid_t mg_start_process(const char *interp, const char *cmd,
+ const char *env, const char *envp[],
+ const char *dir, sock_t sock) {
+ char buf[500];
+ pid_t pid = fork();
+ (void) env;
+
+ if (pid == 0) {
+ /*
+ * In Linux `chdir` declared with `warn_unused_result` attribute
+ * To shutup compiler we have yo use result in some way
+ */
+ int tmp = chdir(dir);
+ (void) tmp;
+ (void) dup2(sock, 0);
+ (void) dup2(sock, 1);
+ closesocket(sock);
+
+ /*
+ * After exec, all signal handlers are restored to their default values,
+ * with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
+ * implementation, SIGCHLD's handler will leave unchanged after exec
+ * if it was set to be ignored. Restore it to default action.
+ */
+ signal(SIGCHLD, SIG_DFL);
+
+ if (interp == NULL) {
+ execle(cmd, cmd, (char *) 0, envp); /* (char *) 0 to squash warning */
+ } else {
+ execle(interp, interp, cmd, (char *) 0, envp);
+ }
+ snprintf(buf, sizeof(buf),
+ "Status: 500\r\n\r\n"
+ "500 Server Error: %s%s%s: %s",
+ interp == NULL ? "" : interp, interp == NULL ? "" : " ", cmd,
+ strerror(errno));
+ send(1, buf, strlen(buf), 0);
+ exit(EXIT_FAILURE); /* exec call failed */
+ }
+
+ return pid;
+}
+#endif /* _WIN32 */
+
+/*
+ * Append VARIABLE=VALUE\0 string to the buffer, and add a respective
+ * pointer into the vars array.
+ */
+static char *mg_addenv(struct mg_cgi_env_block *block, const char *fmt, ...) {
+ int n, space;
+ char *added = block->buf + block->len;
+ va_list ap;
+
+ /* Calculate how much space is left in the buffer */
+ space = sizeof(block->buf) - (block->len + 2);
+ if (space > 0) {
+ /* Copy VARIABLE=VALUE\0 string into the free space */
+ va_start(ap, fmt);
+ n = vsnprintf(added, (size_t) space, fmt, ap);
+ va_end(ap);
+
+ /* Make sure we do not overflow buffer and the envp array */
+ if (n > 0 && n + 1 < space &&
+ block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {
+ /* Append a pointer to the added string into the envp array */
+ block->vars[block->nvars++] = added;
+ /* Bump up used length counter. Include \0 terminator */
+ block->len += n + 1;
+ }
+ }
+
+ return added;
+}
+
+static void mg_addenv2(struct mg_cgi_env_block *blk, const char *name) {
+ const char *s;
+ if ((s = getenv(name)) != NULL) mg_addenv(blk, "%s=%s", name, s);
+}
+
+static void mg_prepare_cgi_environment(struct mg_connection *nc,
+ const char *prog,
+ const struct mg_str *path_info,
+ const struct http_message *hm,
+ const struct mg_serve_http_opts *opts,
+ struct mg_cgi_env_block *blk) {
+ const char *s;
+ struct mg_str *h;
+ char *p;
+ size_t i;
+ char buf[100];
+
+ blk->len = blk->nvars = 0;
+ blk->nc = nc;
+
+ if ((s = getenv("SERVER_NAME")) != NULL) {
+ mg_addenv(blk, "SERVER_NAME=%s", s);
+ } else {
+ mg_sock_to_str(nc->sock, buf, sizeof(buf), 3);
+ mg_addenv(blk, "SERVER_NAME=%s", buf);
+ }
+ mg_addenv(blk, "SERVER_ROOT=%s", opts->document_root);
+ mg_addenv(blk, "DOCUMENT_ROOT=%s", opts->document_root);
+ mg_addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", MG_VERSION);
+
+ /* Prepare the environment block */
+ mg_addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
+ mg_addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
+ mg_addenv(blk, "%s", "REDIRECT_STATUS=200"); /* For PHP */
+
+ mg_addenv(blk, "REQUEST_METHOD=%.*s", (int) hm->method.len, hm->method.p);
+
+ mg_addenv(blk, "REQUEST_URI=%.*s%s%.*s", (int) hm->uri.len, hm->uri.p,
+ hm->query_string.len == 0 ? "" : "?", (int) hm->query_string.len,
+ hm->query_string.p);
+
+ mg_conn_addr_to_str(nc, buf, sizeof(buf),
+ MG_SOCK_STRINGIFY_REMOTE | MG_SOCK_STRINGIFY_IP);
+ mg_addenv(blk, "REMOTE_ADDR=%s", buf);
+ mg_conn_addr_to_str(nc, buf, sizeof(buf), MG_SOCK_STRINGIFY_PORT);
+ mg_addenv(blk, "SERVER_PORT=%s", buf);
+
+ s = hm->uri.p + hm->uri.len - path_info->len - 1;
+ if (*s == '/') {
+ const char *base_name = strrchr(prog, DIRSEP);
+ mg_addenv(blk, "SCRIPT_NAME=%.*s/%s", (int) (s - hm->uri.p), hm->uri.p,
+ (base_name != NULL ? base_name + 1 : prog));
+ } else {
+ mg_addenv(blk, "SCRIPT_NAME=%.*s", (int) (s - hm->uri.p + 1), hm->uri.p);
+ }
+ mg_addenv(blk, "SCRIPT_FILENAME=%s", prog);
+
+ if (path_info != NULL && path_info->len > 0) {
+ mg_addenv(blk, "PATH_INFO=%.*s", (int) path_info->len, path_info->p);
+ /* Not really translated... */
+ mg_addenv(blk, "PATH_TRANSLATED=%.*s", (int) path_info->len, path_info->p);
+ }
+
+ mg_addenv(blk, "HTTPS=%s", nc->ssl != NULL ? "on" : "off");
+
+ if ((h = mg_get_http_header((struct http_message *) hm, "Content-Type")) !=
+ NULL) {
+ mg_addenv(blk, "CONTENT_TYPE=%.*s", (int) h->len, h->p);
+ }
+
+ if (hm->query_string.len > 0) {
+ mg_addenv(blk, "QUERY_STRING=%.*s", (int) hm->query_string.len,
+ hm->query_string.p);
+ }
+
+ if ((h = mg_get_http_header((struct http_message *) hm, "Content-Length")) !=
+ NULL) {
+ mg_addenv(blk, "CONTENT_LENGTH=%.*s", (int) h->len, h->p);
+ }
+
+ mg_addenv2(blk, "PATH");
+ mg_addenv2(blk, "TMP");
+ mg_addenv2(blk, "TEMP");
+ mg_addenv2(blk, "TMPDIR");
+ mg_addenv2(blk, "PERLLIB");
+ mg_addenv2(blk, MG_ENV_EXPORT_TO_CGI);
+
+#if defined(_WIN32)
+ mg_addenv2(blk, "COMSPEC");
+ mg_addenv2(blk, "SYSTEMROOT");
+ mg_addenv2(blk, "SystemDrive");
+ mg_addenv2(blk, "ProgramFiles");
+ mg_addenv2(blk, "ProgramFiles(x86)");
+ mg_addenv2(blk, "CommonProgramFiles(x86)");
+#else
+ mg_addenv2(blk, "LD_LIBRARY_PATH");
+#endif /* _WIN32 */
+
+ /* Add all headers as HTTP_* variables */
+ for (i = 0; hm->header_names[i].len > 0; i++) {
+ p = mg_addenv(blk, "HTTP_%.*s=%.*s", (int) hm->header_names[i].len,
+ hm->header_names[i].p, (int) hm->header_values[i].len,
+ hm->header_values[i].p);
+
+ /* Convert variable name into uppercase, and change - to _ */
+ for (; *p != '=' && *p != '\0'; p++) {
+ if (*p == '-') *p = '_';
+ *p = (char) toupper(*(unsigned char *) p);
+ }
+ }
+
+ blk->vars[blk->nvars++] = NULL;
+ blk->buf[blk->len++] = '\0';
+}
+
+static void mg_cgi_ev_handler(struct mg_connection *cgi_nc, int ev,
+ void *ev_data) {
+ struct mg_connection *nc = (struct mg_connection *) cgi_nc->user_data;
+ (void) ev_data;
+
+ if (nc == NULL) return;
+
+ switch (ev) {
+ case MG_EV_RECV:
+ /*
+ * CGI script does not output reply line, like "HTTP/1.1 CODE XXXXX\n"
+ * It outputs headers, then body. Headers might include "Status"
+ * header, which changes CODE, and it might include "Location" header
+ * which changes CODE to 302.
+ *
+ * Therefore we do not send the output from the CGI script to the user
+ * until all CGI headers are received.
+ *
+ * Here we parse the output from the CGI script, and if all headers has
+ * been received, send appropriate reply line, and forward all
+ * received headers to the client.
+ */
+ if (nc->flags & MG_F_USER_1) {
+ struct mbuf *io = &cgi_nc->recv_mbuf;
+ int len = mg_http_get_request_len(io->buf, io->len);
+
+ if (len == 0) break;
+ if (len < 0 || io->len > MG_MAX_HTTP_REQUEST_SIZE) {
+ cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ mg_http_send_error(nc, 500, "Bad headers");
+ } else {
+ struct http_message hm;
+ struct mg_str *h;
+ mg_http_parse_headers(io->buf, io->buf + io->len, io->len, &hm);
+ if (mg_get_http_header(&hm, "Location") != NULL) {
+ mg_printf(nc, "%s", "HTTP/1.1 302 Moved\r\n");
+ } else if ((h = mg_get_http_header(&hm, "Status")) != NULL) {
+ mg_printf(nc, "HTTP/1.1 %.*s\r\n", (int) h->len, h->p);
+ } else {
+ mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\n");
+ }
+ }
+ nc->flags &= ~MG_F_USER_1;
+ }
+ if (!(nc->flags & MG_F_USER_1)) {
+ mg_forward(cgi_nc, nc);
+ }
+ break;
+ case MG_EV_CLOSE:
+ mg_http_free_proto_data_cgi(&mg_http_get_proto_data(cgi_nc)->cgi);
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ break;
+ }
+}
+
+static void mg_handle_cgi(struct mg_connection *nc, const char *prog,
+ const struct mg_str *path_info,
+ const struct http_message *hm,
+ const struct mg_serve_http_opts *opts) {
+ struct mg_cgi_env_block blk;
+ char dir[MAX_PATH_SIZE];
+ const char *p;
+ sock_t fds[2];
+
+ DBG(("%p [%s]", nc, prog));
+ mg_prepare_cgi_environment(nc, prog, path_info, hm, opts, &blk);
+ /*
+ * CGI must be executed in its own directory. 'dir' must point to the
+ * directory containing executable program, 'p' must point to the
+ * executable program name relative to 'dir'.
+ */
+ if ((p = strrchr(prog, DIRSEP)) == NULL) {
+ snprintf(dir, sizeof(dir), "%s", ".");
+ } else {
+ snprintf(dir, sizeof(dir), "%.*s", (int) (p - prog), prog);
+ prog = p + 1;
+ }
+
+ /*
+ * Try to create socketpair in a loop until success. mg_socketpair()
+ * can be interrupted by a signal and fail.
+ * TODO(lsm): use sigaction to restart interrupted syscall
+ */
+ do {
+ mg_socketpair(fds, SOCK_STREAM);
+ } while (fds[0] == INVALID_SOCKET);
+
+ if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir,
+ fds[1]) != 0) {
+ size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len);
+ struct mg_connection *cgi_nc =
+ mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler);
+ struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(cgi_nc);
+ cgi_pd->cgi.cgi_nc = cgi_nc;
+ cgi_pd->cgi.cgi_nc->user_data = nc;
+ nc->flags |= MG_F_USER_1;
+ /* Push POST data to the CGI */
+ if (n > 0 && n < nc->recv_mbuf.len) {
+ mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n);
+ }
+ mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len);
+ } else {
+ closesocket(fds[0]);
+ mg_http_send_error(nc, 500, "CGI failure");
+ }
+
+#ifndef _WIN32
+ closesocket(fds[1]); /* On Windows, CGI stdio thread closes that socket */
+#endif
+}
+#endif
+
+static int mg_get_month_index(const char *s) {
+ static const char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(month_names); i++)
+ if (!strcmp(s, month_names[i])) return (int) i;
+
+ return -1;
+}
+
+static int mg_num_leap_years(int year) {
+ return year / 4 - year / 100 + year / 400;
+}
+
+/* Parse UTC date-time string, and return the corresponding time_t value. */
+MG_INTERNAL time_t mg_parse_date_string(const char *datetime) {
+ static const unsigned short days_before_month[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+ char month_str[32];
+ int second, minute, hour, day, month, year, leap_days, days;
+ time_t result = (time_t) 0;
+
+ if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d", &day, month_str, &year, &hour,
+ &minute, &second) == 6) ||
+ (sscanf(datetime, "%d %3s %d %d:%d:%d", &day, month_str, &year, &hour,
+ &minute, &second) == 6) ||
+ (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d", &day, month_str, &year,
+ &hour, &minute, &second) == 6) ||
+ (sscanf(datetime, "%d-%3s-%d %d:%d:%d", &day, month_str, &year, &hour,
+ &minute, &second) == 6)) &&
+ year > 1970 && (month = mg_get_month_index(month_str)) != -1) {
+ leap_days = mg_num_leap_years(year) - mg_num_leap_years(1970);
+ year -= 1970;
+ days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
+ result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
+ }
+
+ return result;
+}
+
+MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st) {
+ struct mg_str *hdr;
+ if ((hdr = mg_get_http_header(hm, "If-None-Match")) != NULL) {
+ char etag[64];
+ mg_http_construct_etag(etag, sizeof(etag), st);
+ return mg_vcasecmp(hdr, etag) == 0;
+ } else if ((hdr = mg_get_http_header(hm, "If-Modified-Since")) != NULL) {
+ return st->st_mtime <= mg_parse_date_string(hdr->p);
+ } else {
+ return 0;
+ }
+}
+
+static void mg_http_send_digest_auth_request(struct mg_connection *c,
+ const char *domain) {
+ mg_printf(c,
+ "HTTP/1.1 401 Unauthorized\r\n"
+ "WWW-Authenticate: Digest qop=\"auth\", "
+ "realm=\"%s\", nonce=\"%lu\"\r\n"
+ "Content-Length: 0\r\n\r\n",
+ domain, (unsigned long) time(NULL));
+}
+
+static void mg_http_send_options(struct mg_connection *nc) {
+ mg_printf(nc, "%s",
+ "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS"
+#ifndef MG_DISABLE_DAV
+ ", MKCOL, PUT, DELETE, PROPFIND, MOVE\r\nDAV: 1,2"
+#endif
+ "\r\n\r\n");
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+}
+
+static int mg_is_creation_request(const struct http_message *hm) {
+ return mg_vcmp(&hm->method, "MKCOL") == 0 || mg_vcmp(&hm->method, "PUT") == 0;
+}
+
+MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,
+ const struct mg_str *path_info,
+ struct http_message *hm,
+ struct mg_serve_http_opts *opts) {
+ int exists, is_directory, is_dav = mg_is_dav_request(&hm->method);
+ int is_cgi;
+ char *index_file = NULL;
+ cs_stat_t st;
+
+ exists = (mg_stat(path, &st) == 0);
+ is_directory = exists && S_ISDIR(st.st_mode);
+
+ if (is_directory)
+ mg_find_index_file(path, opts->index_files, &index_file, &st);
+
+ is_cgi =
+ (mg_match_prefix(opts->cgi_file_pattern, strlen(opts->cgi_file_pattern),
+ index_file ? index_file : path) > 0);
+
+ DBG(("%p %.*s [%s] exists=%d is_dir=%d is_dav=%d is_cgi=%d index=%s", nc,
+ (int) hm->method.len, hm->method.p, path, exists, is_directory, is_dav,
+ is_cgi, index_file ? index_file : ""));
+
+ if (is_directory && hm->uri.p[hm->uri.len - 1] != '/' && !is_dav) {
+ mg_printf(nc,
+ "HTTP/1.1 301 Moved\r\nLocation: %.*s/\r\n"
+ "Content-Length: 0\r\n\r\n",
+ (int) hm->uri.len, hm->uri.p);
+ MG_FREE(index_file);
+ return;
+ }
+
+ /* If we have path_info, the only way to handle it is CGI. */
+ if (path_info->len > 0 && !is_cgi) {
+ mg_http_send_error(nc, 501, NULL);
+ MG_FREE(index_file);
+ return;
+ }
+
+ if (is_dav && opts->dav_document_root == NULL) {
+ mg_http_send_error(nc, 501, NULL);
+ } else if (!mg_is_authorized(hm, path, is_directory, opts->auth_domain,
+ opts->global_auth_file, 1) ||
+ !mg_is_authorized(hm, path, is_directory, opts->auth_domain,
+ opts->per_directory_auth_file, 0)) {
+ mg_http_send_digest_auth_request(nc, opts->auth_domain);
+ } else if (is_cgi) {
+#if !defined(MG_DISABLE_CGI)
+ mg_handle_cgi(nc, index_file ? index_file : path, path_info, hm, opts);
+#else
+ mg_http_send_error(nc, 501, NULL);
+#endif /* MG_DISABLE_CGI */
+ } else if ((!exists ||
+ mg_is_file_hidden(path, opts, 0 /* specials are ok */)) &&
+ !mg_is_creation_request(hm)) {
+ mg_http_send_error(nc, 404, NULL);
+#ifndef MG_DISABLE_DAV
+ } else if (!mg_vcmp(&hm->method, "PROPFIND")) {
+ mg_handle_propfind(nc, path, &st, hm, opts);
+#ifndef MG_DISABLE_DAV_AUTH
+ } else if (is_dav &&
+ (opts->dav_auth_file == NULL ||
+ (strcmp(opts->dav_auth_file, "-") != 0 &&
+ !mg_is_authorized(hm, path, is_directory, opts->auth_domain,
+ opts->dav_auth_file, 1)))) {
+ mg_http_send_digest_auth_request(nc, opts->auth_domain);
+#endif
+ } else if (!mg_vcmp(&hm->method, "MKCOL")) {
+ mg_handle_mkcol(nc, path, hm);
+ } else if (!mg_vcmp(&hm->method, "DELETE")) {
+ mg_handle_delete(nc, opts, path);
+ } else if (!mg_vcmp(&hm->method, "PUT")) {
+ mg_handle_put(nc, path, hm);
+ } else if (!mg_vcmp(&hm->method, "MOVE")) {
+ mg_handle_move(nc, opts, path, hm);
+#ifdef MG_ENABLE_FAKE_DAVLOCK
+ } else if (!mg_vcmp(&hm->method, "LOCK")) {
+ mg_handle_lock(nc, path);
+#endif
+#endif
+ } else if (!mg_vcmp(&hm->method, "OPTIONS")) {
+ mg_http_send_options(nc);
+ } else if (is_directory && index_file == NULL) {
+#ifndef MG_DISABLE_DIRECTORY_LISTING
+ if (strcmp(opts->enable_directory_listing, "yes") == 0) {
+ mg_send_directory_listing(nc, path, hm, opts);
+ } else {
+ mg_http_send_error(nc, 403, NULL);
+ }
+#else
+ mg_http_send_error(nc, 501, NULL);
+#endif
+ } else if (mg_is_not_modified(hm, &st)) {
+ mg_http_send_error(nc, 304, "Not Modified");
+ } else {
+ mg_http_send_file2(nc, index_file ? index_file : path, &st, hm, opts);
+ }
+ MG_FREE(index_file);
+}
+
+void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
+ struct mg_serve_http_opts opts) {
+ char *path = NULL;
+ struct mg_str *hdr, path_info;
+ uint32_t remote_ip = ntohl(*(uint32_t *) &nc->sa.sin.sin_addr);
+
+ if (mg_check_ip_acl(opts.ip_acl, remote_ip) != 1) {
+ /* Not allowed to connect */
+ mg_http_send_error(nc, 403, NULL);
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+ return;
+ }
+
+ if (mg_http_send_port_based_redirect(nc, hm, &opts)) {
+ return;
+ }
+
+ if (opts.document_root == NULL) {
+ opts.document_root = ".";
+ }
+ if (opts.per_directory_auth_file == NULL) {
+ opts.per_directory_auth_file = ".htpasswd";
+ }
+ if (opts.enable_directory_listing == NULL) {
+ opts.enable_directory_listing = "yes";
+ }
+ if (opts.cgi_file_pattern == NULL) {
+ opts.cgi_file_pattern = "**.cgi$|**.php$";
+ }
+ if (opts.ssi_pattern == NULL) {
+ opts.ssi_pattern = "**.shtml$|**.shtm$";
+ }
+ if (opts.index_files == NULL) {
+ opts.index_files = "index.html,index.htm,index.shtml,index.cgi,index.php";
+ }
+ /* Normalize path - resolve "." and ".." (in-place). */
+ if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) {
+ mg_http_send_error(nc, 400, NULL);
+ return;
+ }
+ if (mg_uri_to_local_path(hm, &opts, &path, &path_info) == 0) {
+ mg_http_send_error(nc, 404, NULL);
+ return;
+ }
+ mg_send_http_file(nc, path, &path_info, hm, &opts);
+
+ MG_FREE(path);
+ path = NULL;
+
+ /* Close connection for non-keep-alive requests */
+ if (mg_vcmp(&hm->proto, "HTTP/1.1") != 0 ||
+ ((hdr = mg_get_http_header(hm, "Connection")) != NULL &&
+ mg_vcmp(hdr, "keep-alive") != 0)) {
+#if 0
+ nc->flags |= MG_F_SEND_AND_CLOSE;
+#endif
+ }
+}
+
+#endif /* MG_DISABLE_FILESYSTEM */
+
+/* returns 0 on success, -1 on error */
+static int mg_http_common_url_parse(const char *url, const char *schema,
+ const char *schema_tls, int *use_ssl,
+ char **addr, int *port_i,
+ const char **path) {
+ int addr_len = 0;
+
+ if (memcmp(url, schema, strlen(schema)) == 0) {
+ url += strlen(schema);
+ } else if (memcmp(url, schema_tls, strlen(schema_tls)) == 0) {
+ url += strlen(schema_tls);
+ *use_ssl = 1;
+#ifndef MG_ENABLE_SSL
+ return -1; /* SSL is not enabled, cannot do HTTPS URLs */
+#endif
+ }
+
+ while (*url != '\0') {
+ *addr = (char *) MG_REALLOC(*addr, addr_len + 5 /* space for port too. */);
+ if (*addr == NULL) {
+ DBG(("OOM"));
+ return -1;
+ }
+ if (*url == '/') {
+ break;
+ }
+ if (*url == ':') *port_i = addr_len;
+ (*addr)[addr_len++] = *url;
+ (*addr)[addr_len] = '\0';
+ url++;
+ }
+ if (addr_len == 0) goto cleanup;
+ if (*port_i < 0) {
+ *port_i = addr_len;
+ strcpy(*addr + *port_i, *use_ssl ? ":443" : ":80");
+ } else {
+ *port_i = -1;
+ }
+
+ if (*path == NULL) *path = url;
+
+ if (**path == '\0') *path = "/";
+
+ DBG(("%s %s", *addr, *path));
+
+ return 0;
+
+cleanup:
+ MG_FREE(*addr);
+ return -1;
+}
+
+struct mg_connection *mg_connect_http_base(
+ struct mg_mgr *mgr, mg_event_handler_t ev_handler,
+ struct mg_connect_opts opts, const char *schema, const char *schema_ssl,
+ const char *url, const char **path, char **addr) {
+ struct mg_connection *nc = NULL;
+ int port_i = -1;
+ int use_ssl = 0;
+
+ if (mg_http_common_url_parse(url, schema, schema_ssl, &use_ssl, addr, &port_i,
+ path) < 0) {
+ return NULL;
+ }
+
+#ifndef MG_ENABLE_SSL
+ if (use_ssl) {
+ MG_SET_PTRPTR(opts.error_string, "ssl is disabled");
+ MG_FREE(addr);
+ return NULL;
+ }
+#endif
+
+ if ((nc = mg_connect_opt(mgr, *addr, ev_handler, opts)) != NULL) {
+#ifdef MG_ENABLE_SSL
+ if (use_ssl && nc->ssl_ctx == NULL) {
+ /*
+ * Schema requires SSL, but no SSL parameters were provided in
+ * opts. In order to maintain backward compatibility, use
+ * NULL, NULL
+ */
+ mg_set_ssl(nc, NULL, NULL);
+ }
+#endif
+ mg_set_protocol_http_websocket(nc);
+
+ /* If the port was addred by us, restore the original host. */
+ if (port_i >= 0) (*addr)[port_i] = '\0';
+ }
+
+ return nc;
+}
+
+struct mg_connection *mg_connect_http_opt(struct mg_mgr *mgr,
+ mg_event_handler_t ev_handler,
+ struct mg_connect_opts opts,
+ const char *url,
+ const char *extra_headers,
+ const char *post_data) {
+ char *addr = NULL;
+ const char *path = NULL;
+ struct mg_connection *nc = mg_connect_http_base(
+ mgr, ev_handler, opts, "http://", "https://", url, &path, &addr);
+
+ if (nc == NULL) {
+ return NULL;
+ }
+
+ mg_printf(nc, "%s %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %" SIZE_T_FMT
+ "\r\n%s\r\n%s",
+ post_data == NULL ? "GET" : "POST", path, addr,
+ post_data == NULL ? 0 : strlen(post_data),
+ extra_headers == NULL ? "" : extra_headers,
+ post_data == NULL ? "" : post_data);
+
+ MG_FREE(addr);
+ return nc;
+}
+
+struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
+ mg_event_handler_t ev_handler,
+ const char *url,
+ const char *extra_headers,
+ const char *post_data) {
+ struct mg_connect_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ return mg_connect_http_opt(mgr, ev_handler, opts, url, extra_headers,
+ post_data);
+}
+
+struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr,
+ mg_event_handler_t ev_handler,
+ struct mg_connect_opts opts,
+ const char *url, const char *protocol,
+ const char *extra_headers) {
+ char *addr = NULL;
+ const char *path = NULL;
+ struct mg_connection *nc = mg_connect_http_base(
+ mgr, ev_handler, opts, "ws://", "wss://", url, &path, &addr);
+
+ if (nc == NULL) {
+ return NULL;
+ }
+
+ mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers);
+
+ MG_FREE(addr);
+ return nc;
+}
+
+struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
+ mg_event_handler_t ev_handler,
+ const char *url, const char *protocol,
+ const char *extra_headers) {
+ struct mg_connect_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers);
+}
+
+size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
+ size_t var_name_len, char *file_name,
+ size_t file_name_len, const char **data,
+ size_t *data_len) {
+ static const char cd[] = "Content-Disposition: ";
+ size_t hl, bl, n, ll, pos, cdl = sizeof(cd) - 1;
+
+ if (buf == NULL || buf_len <= 0) return 0;
+ if ((hl = mg_http_get_request_len(buf, buf_len)) <= 0) return 0;
+ if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\n') return 0;
+
+ /* Get boundary length */
+ bl = mg_get_line_len(buf, buf_len);
+
+ /* Loop through headers, fetch variable name and file name */
+ var_name[0] = file_name[0] = '\0';
+ for (n = bl; (ll = mg_get_line_len(buf + n, hl - n)) > 0; n += ll) {
+ if (mg_ncasecmp(cd, buf + n, cdl) == 0) {
+ struct mg_str header;
+ header.p = buf + n + cdl;
+ header.len = ll - (cdl + 2);
+ mg_http_parse_header(&header, "name", var_name, var_name_len);
+ mg_http_parse_header(&header, "filename", file_name, file_name_len);
+ }
+ }
+
+ /* Scan through the body, search for terminating boundary */
+ for (pos = hl; pos + (bl - 2) < buf_len; pos++) {
+ if (buf[pos] == '-' && !memcmp(buf, &buf[pos], bl - 2)) {
+ if (data_len != NULL) *data_len = (pos - 2) - hl;
+ if (data != NULL) *data = buf + hl;
+ return pos;
+ }
+ }
+
+ return 0;
+}
+
+void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
+ mg_event_handler_t handler) {
+ struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+ struct mg_http_endpoint *new_ep =
+ (struct mg_http_endpoint *) calloc(1, sizeof(*new_ep));
+ new_ep->name = strdup(uri_path);
+ new_ep->name_len = strlen(new_ep->name);
+ new_ep->handler = handler;
+ new_ep->next = pd->endpoints;
+ pd->endpoints = new_ep;
+}
+
+#endif /* MG_DISABLE_HTTP */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/util.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Amalgamated: #include "common/base64.h" */
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/util.h" */
+
+const char *mg_skip(const char *s, const char *end, const char *delims,
+ struct mg_str *v) {
+ v->p = s;
+ while (s < end && strchr(delims, *(unsigned char *) s) == NULL) s++;
+ v->len = s - v->p;
+ while (s < end && strchr(delims, *(unsigned char *) s) != NULL) s++;
+ return s;
+}
+
+static int lowercase(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 = lowercase(s1++) - lowercase(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_vcasecmp(const struct mg_str *str1, const char *str2) {
+ size_t n2 = strlen(str2), n1 = str1->len;
+ int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2);
+ if (r == 0) {
+ return n1 - n2;
+ }
+ return r;
+}
+
+int mg_vcmp(const struct mg_str *str1, const char *str2) {
+ size_t n2 = strlen(str2), n1 = str1->len;
+ int r = memcmp(str1->p, str2, (n1 < n2) ? n1 : n2);
+ if (r == 0) {
+ return n1 - n2;
+ }
+ return r;
+}
+
+#ifndef MG_DISABLE_FILESYSTEM
+int mg_stat(const char *path, cs_stat_t *st) {
+#ifdef _WIN32
+ wchar_t wpath[MAX_PATH_SIZE];
+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
+ DBG(("[%ls] -> %d", wpath, _wstati64(wpath, st)));
+ return _wstati64(wpath, (struct _stati64 *) st);
+#else
+ return stat(path, st);
+#endif
+}
+
+FILE *mg_fopen(const char *path, const char *mode) {
+#ifdef _WIN32
+ wchar_t wpath[MAX_PATH_SIZE], wmode[10];
+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
+ to_wchar(mode, wmode, ARRAY_SIZE(wmode));
+ return _wfopen(wpath, wmode);
+#else
+ return fopen(path, mode);
+#endif
+}
+
+int mg_open(const char *path, int flag, int mode) { /* LCOV_EXCL_LINE */
+#ifdef _WIN32
+ wchar_t wpath[MAX_PATH_SIZE];
+ to_wchar(path, wpath, ARRAY_SIZE(wpath));
+ return _wopen(wpath, flag, mode);
+#else
+ return open(path, flag, mode); /* LCOV_EXCL_LINE */
+#endif
+}
+#endif
+
+void mg_base64_encode(const unsigned char *src, int src_len, char *dst) {
+ cs_base64_encode(src, src_len, dst);
+}
+
+int mg_base64_decode(const unsigned char *s, int len, char *dst) {
+ return cs_base64_decode(s, len, dst);
+}
+
+#ifdef MG_ENABLE_THREADS
+void *mg_start_thread(void *(*f)(void *), void *p) {
+#ifdef _WIN32
+ return (void *) _beginthread((void(__cdecl *) (void *) ) f, 0, p);
+#else
+ pthread_t thread_id = (pthread_t) 0;
+ pthread_attr_t attr;
+
+ (void) pthread_attr_init(&attr);
+ (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+#if defined(MG_STACK_SIZE) && MG_STACK_SIZE > 1
+ (void) pthread_attr_setstacksize(&attr, MG_STACK_SIZE);
+#endif
+
+ pthread_create(&thread_id, &attr, f, p);
+ pthread_attr_destroy(&attr);
+
+ return (void *) thread_id;
+#endif
+}
+#endif /* MG_ENABLE_THREADS */
+
+/* Set close-on-exec bit for a given socket. */
+void mg_set_close_on_exec(sock_t sock) {
+#ifdef _WIN32
+ (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
+#elif defined(__unix__)
+ fcntl(sock, F_SETFD, FD_CLOEXEC);
+#else
+ (void) sock;
+#endif
+}
+
+void mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
+ int flags) {
+ int is_v6;
+ if (buf == NULL || len <= 0) return;
+ buf[0] = '\0';
+#if defined(MG_ENABLE_IPV6)
+ is_v6 = sa->sa.sa_family == AF_INET6;
+#else
+ is_v6 = 0;
+#endif
+ if (flags & MG_SOCK_STRINGIFY_IP) {
+#if defined(MG_ENABLE_IPV6)
+ const void *addr = NULL;
+ char *start = buf;
+ socklen_t capacity = len;
+ if (!is_v6) {
+ addr = &sa->sin.sin_addr;
+ } else {
+ addr = (void *) &sa->sin6.sin6_addr;
+ if (flags & MG_SOCK_STRINGIFY_PORT) {
+ *buf = '[';
+ start++;
+ capacity--;
+ }
+ }
+ if (inet_ntop(sa->sa.sa_family, addr, start, capacity) == NULL) {
+ *buf = '\0';
+ }
+#elif defined(_WIN32) || defined(MG_LWIP)
+ /* Only Windoze Vista (and newer) have inet_ntop() */
+ strncpy(buf, inet_ntoa(sa->sin.sin_addr), len);
+#else
+ inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len);
+#endif
+ }
+ if (flags & MG_SOCK_STRINGIFY_PORT) {
+ int port = ntohs(sa->sin.sin_port);
+ if (flags & MG_SOCK_STRINGIFY_IP) {
+ snprintf(buf + strlen(buf), len - (strlen(buf) + 1), "%s:%d",
+ (is_v6 ? "]" : ""), port);
+ } else {
+ snprintf(buf, len, "%d", port);
+ }
+ }
+}
+
+void mg_conn_addr_to_str(struct mg_connection *nc, char *buf, size_t len,
+ int flags) {
+ union socket_address sa;
+ memset(&sa, 0, sizeof(sa));
+ mg_if_get_conn_addr(nc, flags & MG_SOCK_STRINGIFY_REMOTE, &sa);
+ mg_sock_addr_to_str(&sa, buf, len, flags);
+}
+
+#ifndef MG_DISABLE_HEXDUMP
+int mg_hexdump(const void *buf, int len, char *dst, int dst_len) {
+ const unsigned char *p = (const unsigned char *) buf;
+ char ascii[17] = "";
+ int i, idx, n = 0;
+
+ for (i = 0; i < len; i++) {
+ idx = i % 16;
+ if (idx == 0) {
+ if (i > 0) n += snprintf(dst + n, dst_len - n, " %s\n", ascii);
+ n += snprintf(dst + n, dst_len - n, "%04x ", i);
+ }
+ n += snprintf(dst + n, dst_len - n, " %02x", p[i]);
+ ascii[idx] = p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i];
+ ascii[idx + 1] = '\0';
+ }
+
+ while (i++ % 16) n += snprintf(dst + n, dst_len - n, "%s", " ");
+ n += snprintf(dst + n, dst_len - n, " %s\n\n", ascii);
+
+ return n;
+}
+#endif
+
+int mg_avprintf(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. */
+ *buf = NULL; /* LCOV_EXCL_START */
+ while (len < 0) {
+ MG_FREE(*buf);
+ size *= 2;
+ if ((*buf = (char *) MG_MALLOC(size)) == NULL) break;
+ va_copy(ap_copy, ap);
+ len = vsnprintf(*buf, size, fmt, ap_copy);
+ va_end(ap_copy);
+ }
+ /* LCOV_EXCL_STOP */
+ } else if (len >= (int) size) {
+ /* Standard-compliant code path. Allocate a buffer that is large enough. */
+ if ((*buf = (char *) MG_MALLOC(len + 1)) == NULL) {
+ len = -1; /* LCOV_EXCL_LINE */
+ } else { /* LCOV_EXCL_LINE */
+ va_copy(ap_copy, ap);
+ len = vsnprintf(*buf, len + 1, fmt, ap_copy);
+ va_end(ap_copy);
+ }
+ }
+
+ return len;
+}
+
+#if !defined(MG_DISABLE_HEXDUMP)
+void mg_hexdump_connection(struct mg_connection *nc, const char *path,
+ const void *buf, int num_bytes, int ev) {
+#if !defined(NO_LIBC) && !defined(MG_DISABLE_STDIO)
+ FILE *fp = NULL;
+ char *hexbuf, src[60], dst[60];
+ int buf_size = num_bytes * 5 + 100;
+
+ if (strcmp(path, "-") == 0) {
+ fp = stdout;
+ } else if (strcmp(path, "--") == 0) {
+ fp = stderr;
+#ifndef MG_DISABLE_FILESYSTEM
+ } else {
+ fp = fopen(path, "a");
+#endif
+ }
+ if (fp == NULL) return;
+
+ mg_conn_addr_to_str(nc, src, sizeof(src),
+ MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
+ mg_conn_addr_to_str(nc, dst, sizeof(dst), MG_SOCK_STRINGIFY_IP |
+ MG_SOCK_STRINGIFY_PORT |
+ MG_SOCK_STRINGIFY_REMOTE);
+ fprintf(
+ fp, "%lu %p %s %s %s %d\n", (unsigned long) time(NULL), (void *) nc, src,
+ ev == MG_EV_RECV ? "<-" : ev == MG_EV_SEND
+ ? "->"
+ : ev == MG_EV_ACCEPT
+ ? "" : "XX",
+ dst, num_bytes);
+ if (num_bytes > 0 && (hexbuf = (char *) MG_MALLOC(buf_size)) != NULL) {
+ mg_hexdump(buf, num_bytes, hexbuf, buf_size);
+ fprintf(fp, "%s", hexbuf);
+ MG_FREE(hexbuf);
+ }
+ if (fp != stdin && fp != stdout) fclose(fp);
+#endif
+}
+#endif
+
+int mg_is_big_endian(void) {
+ static const int n = 1;
+ /* TODO(mkm) use compiletime check with 4-byte char literal */
+ return ((char *) &n)[0] == 0;
+}
+
+const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
+ struct mg_str *eq_val) {
+ if (list == NULL || *list == '\0') {
+ /* End of the list */
+ list = NULL;
+ } else {
+ val->p = list;
+ if ((list = strchr(val->p, ',')) != NULL) {
+ /* Comma found. Store length and shift the list ptr */
+ val->len = list - val->p;
+ list++;
+ } else {
+ /* This value is the last one */
+ list = val->p + strlen(val->p);
+ val->len = list - val->p;
+ }
+
+ if (eq_val != NULL) {
+ /* Value has form "x=y", adjust pointers and lengths */
+ /* so that val points to "x", and eq_val points to "y". */
+ eq_val->len = 0;
+ eq_val->p = (const char *) memchr(val->p, '=', val->len);
+ if (eq_val->p != NULL) {
+ eq_val->p++; /* Skip over '=' character */
+ eq_val->len = val->p + val->len - eq_val->p;
+ val->len = (eq_val->p - val->p) - 1;
+ }
+ }
+ }
+
+ return list;
+}
+
+int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) {
+ const char *or_str;
+ size_t len, i = 0, j = 0;
+ int res;
+
+ if ((or_str = (const char *) memchr(pattern.p, '|', pattern.len)) != NULL) {
+ struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)};
+ res = mg_match_prefix_n(pstr, str);
+ if (res > 0) return res;
+ pstr.p = or_str + 1;
+ pstr.len = (pattern.p + pattern.len) - (or_str + 1);
+ return mg_match_prefix_n(pstr, str);
+ }
+
+ for (; i < pattern.len; i++, j++) {
+ if (pattern.p[i] == '?' && j != str.len) {
+ continue;
+ } else if (pattern.p[i] == '$') {
+ return j == str.len ? (int) j : -1;
+ } else if (pattern.p[i] == '*') {
+ i++;
+ if (pattern.p[i] == '*') {
+ i++;
+ len = str.len - j;
+ } else {
+ len = 0;
+ while (j + len != str.len && str.p[len] != '/') {
+ len++;
+ }
+ }
+ if (i == pattern.len) {
+ return j + len;
+ }
+ do {
+ const struct mg_str pstr = {pattern.p + i, pattern.len - i};
+ const struct mg_str sstr = {str.p + j + len, str.len - j - len};
+ res = mg_match_prefix_n(pstr, sstr);
+ } while (res == -1 && len-- > 0);
+ return res == -1 ? -1 : (int) (j + res + len);
+ } else if (lowercase(&pattern.p[i]) != lowercase(&str.p[j])) {
+ return -1;
+ }
+ }
+ return j;
+}
+
+int mg_match_prefix(const char *pattern, int pattern_len, const char *str) {
+ const struct mg_str pstr = {pattern, (size_t) pattern_len};
+ return mg_match_prefix_n(pstr, mg_mk_str(str));
+}
+
+struct mg_str mg_mk_str(const char *s) {
+ struct mg_str ret = {s, 0};
+ if (s != NULL) ret.len = strlen(s);
+ return ret;
+}
+#ifdef MG_MODULE_LINES
+#line 1 "./src/json-rpc.c"
+#endif
+/* Copyright (c) 2014 Cesanta Software Limited */
+/* All rights reserved */
+
+#ifndef MG_DISABLE_JSON_RPC
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/json-rpc.h" */
+/* Amalgamated: #include "mongoose/deps/frozen/frozen.h" */
+
+int mg_rpc_create_reply(char *buf, int len, const struct mg_rpc_request *req,
+ const char *result_fmt, ...) {
+ static const struct json_token null_tok = {"null", 4, 0, JSON_TYPE_NULL};
+ const struct json_token *id = req->id == NULL ? &null_tok : req->id;
+ va_list ap;
+ int n = 0;
+
+ n += json_emit(buf + n, len - n, "{s:s,s:", "jsonrpc", "2.0", "id");
+ if (id->type == JSON_TYPE_STRING) {
+ n += json_emit_quoted_str(buf + n, len - n, id->ptr, id->len);
+ } else {
+ n += json_emit_unquoted_str(buf + n, len - n, id->ptr, id->len);
+ }
+ n += json_emit(buf + n, len - n, ",s:", "result");
+
+ va_start(ap, result_fmt);
+ n += json_emit_va(buf + n, len - n, result_fmt, ap);
+ va_end(ap);
+
+ n += json_emit(buf + n, len - n, "}");
+
+ return n;
+}
+
+int mg_rpc_create_request(char *buf, int len, const char *method,
+ const char *id, const char *params_fmt, ...) {
+ va_list ap;
+ int n = 0;
+
+ n += json_emit(buf + n, len - n, "{s:s,s:s,s:s,s:", "jsonrpc", "2.0", "id",
+ id, "method", method, "params");
+ va_start(ap, params_fmt);
+ n += json_emit_va(buf + n, len - n, params_fmt, ap);
+ va_end(ap);
+
+ n += json_emit(buf + n, len - n, "}");
+
+ return n;
+}
+
+int mg_rpc_create_error(char *buf, int len, struct mg_rpc_request *req,
+ int code, const char *message, const char *fmt, ...) {
+ va_list ap;
+ int n = 0;
+
+ n += json_emit(buf + n, len - n, "{s:s,s:V,s:{s:i,s:s,s:", "jsonrpc", "2.0",
+ "id", req->id == NULL ? "null" : req->id->ptr,
+ req->id == NULL ? 4 : req->id->len, "error", "code",
+ (long) code, "message", message, "data");
+ va_start(ap, fmt);
+ n += json_emit_va(buf + n, len - n, fmt, ap);
+ va_end(ap);
+
+ n += json_emit(buf + n, len - n, "}}");
+
+ return n;
+}
+
+int mg_rpc_create_std_error(char *buf, int len, struct mg_rpc_request *req,
+ int code) {
+ const char *message = NULL;
+
+ switch (code) {
+ case JSON_RPC_PARSE_ERROR:
+ message = "parse error";
+ break;
+ case JSON_RPC_INVALID_REQUEST_ERROR:
+ message = "invalid request";
+ break;
+ case JSON_RPC_METHOD_NOT_FOUND_ERROR:
+ message = "method not found";
+ break;
+ case JSON_RPC_INVALID_PARAMS_ERROR:
+ message = "invalid parameters";
+ break;
+ case JSON_RPC_SERVER_ERROR:
+ message = "server error";
+ break;
+ default:
+ message = "unspecified error";
+ break;
+ }
+
+ return mg_rpc_create_error(buf, len, req, code, message, "N");
+}
+
+int mg_rpc_dispatch(const char *buf, int len, char *dst, int dst_len,
+ const char **methods, mg_rpc_handler_t *handlers) {
+ struct json_token tokens[200];
+ struct mg_rpc_request req;
+ int i, n;
+
+ memset(&req, 0, sizeof(req));
+ n = parse_json(buf, len, tokens, sizeof(tokens) / sizeof(tokens[0]));
+ if (n <= 0) {
+ int err_code = (n == JSON_STRING_INVALID) ? JSON_RPC_PARSE_ERROR
+ : JSON_RPC_SERVER_ERROR;
+ return mg_rpc_create_std_error(dst, dst_len, &req, err_code);
+ }
+
+ req.message = tokens;
+ req.id = find_json_token(tokens, "id");
+ req.method = find_json_token(tokens, "method");
+ req.params = find_json_token(tokens, "params");
+
+ if (req.id == NULL || req.method == NULL) {
+ return mg_rpc_create_std_error(dst, dst_len, &req,
+ JSON_RPC_INVALID_REQUEST_ERROR);
+ }
+
+ for (i = 0; methods[i] != NULL; i++) {
+ int mlen = strlen(methods[i]);
+ if (mlen == req.method->len &&
+ memcmp(methods[i], req.method->ptr, mlen) == 0)
+ break;
+ }
+
+ if (methods[i] == NULL) {
+ return mg_rpc_create_std_error(dst, dst_len, &req,
+ JSON_RPC_METHOD_NOT_FOUND_ERROR);
+ }
+
+ return handlers[i](dst, dst_len, &req);
+}
+
+int mg_rpc_parse_reply(const char *buf, int len, struct json_token *toks,
+ int max_toks, struct mg_rpc_reply *rep,
+ struct mg_rpc_error *er) {
+ int n = parse_json(buf, len, toks, max_toks);
+
+ memset(rep, 0, sizeof(*rep));
+ memset(er, 0, sizeof(*er));
+
+ if (n > 0) {
+ if ((rep->result = find_json_token(toks, "result")) != NULL) {
+ rep->message = toks;
+ rep->id = find_json_token(toks, "id");
+ } else {
+ er->message = toks;
+ er->id = find_json_token(toks, "id");
+ er->error_code = find_json_token(toks, "error.code");
+ er->error_message = find_json_token(toks, "error.message");
+ er->error_data = find_json_token(toks, "error.data");
+ }
+ }
+ return n;
+}
+
+#endif /* MG_DISABLE_JSON_RPC */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/mqtt.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef MG_DISABLE_MQTT
+
+#include
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/mqtt.h" */
+
+MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {
+ uint8_t header;
+ int cmd;
+ size_t len = 0;
+ int var_len = 0;
+ char *vlen = &io->buf[1];
+
+ if (io->len < 2) return -1;
+
+ header = io->buf[0];
+ cmd = header >> 4;
+
+ /* decode mqtt variable length */
+ do {
+ len += (*vlen & 127) << 7 * (vlen - &io->buf[1]);
+ } while ((*vlen++ & 128) != 0 && ((size_t)(vlen - io->buf) <= io->len));
+
+ if (len != 0 && io->len < (size_t)(len - 1)) return -1;
+
+ mbuf_remove(io, 1 + (vlen - &io->buf[1]));
+ mm->cmd = cmd;
+ mm->qos = MG_MQTT_GET_QOS(header);
+
+ switch (cmd) {
+ case MG_MQTT_CMD_CONNECT:
+ /* TODO(mkm): parse keepalive and will */
+ break;
+ case MG_MQTT_CMD_CONNACK:
+ mm->connack_ret_code = io->buf[1];
+ var_len = 2;
+ break;
+ case MG_MQTT_CMD_PUBACK:
+ case MG_MQTT_CMD_PUBREC:
+ case MG_MQTT_CMD_PUBREL:
+ case MG_MQTT_CMD_PUBCOMP:
+ case MG_MQTT_CMD_SUBACK:
+ mm->message_id = ntohs(*(uint16_t *) io->buf);
+ var_len = 2;
+ break;
+ case MG_MQTT_CMD_PUBLISH: {
+ uint16_t topic_len = ntohs(*(uint16_t *) io->buf);
+ mm->topic = (char *) MG_MALLOC(topic_len + 1);
+ mm->topic[topic_len] = 0;
+ strncpy(mm->topic, io->buf + 2, topic_len);
+ var_len = topic_len + 2;
+
+ if (MG_MQTT_GET_QOS(header) > 0) {
+ mm->message_id = ntohs(*(uint16_t *) io->buf);
+ var_len += 2;
+ }
+ } break;
+ case MG_MQTT_CMD_SUBSCRIBE:
+ /*
+ * topic expressions are left in the payload and can be parsed with
+ * `mg_mqtt_next_subscribe_topic`
+ */
+ mm->message_id = ntohs(*(uint16_t *) io->buf);
+ var_len = 2;
+ break;
+ default:
+ /* Unhandled command */
+ break;
+ }
+
+ mbuf_remove(io, var_len);
+ return len - var_len;
+}
+
+static void mqtt_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ int len;
+ struct mbuf *io = &nc->recv_mbuf;
+ struct mg_mqtt_message mm;
+ memset(&mm, 0, sizeof(mm));
+
+ nc->handler(nc, ev, ev_data);
+
+ switch (ev) {
+ case MG_EV_RECV:
+ len = parse_mqtt(io, &mm);
+ if (len == -1) break; /* not fully buffered */
+ mm.payload.p = io->buf;
+ mm.payload.len = len;
+
+ nc->handler(nc, MG_MQTT_EVENT_BASE + mm.cmd, &mm);
+
+ if (mm.topic) {
+ MG_FREE(mm.topic);
+ }
+ mbuf_remove(io, mm.payload.len);
+ break;
+ }
+}
+
+void mg_set_protocol_mqtt(struct mg_connection *nc) {
+ nc->proto_handler = mqtt_handler;
+}
+
+void mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id) {
+ static struct mg_send_mqtt_handshake_opts opts;
+ mg_send_mqtt_handshake_opt(nc, client_id, opts);
+}
+
+void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,
+ struct mg_send_mqtt_handshake_opts opts) {
+ uint8_t header = MG_MQTT_CMD_CONNECT << 4;
+ uint8_t rem_len;
+ uint16_t keep_alive;
+ uint16_t len;
+
+ /*
+ * 9: version_header(len, magic_string, version_number), 1: flags, 2:
+ * keep-alive timer,
+ * 2: client_identifier_len, n: client_id
+ */
+ rem_len = 9 + 1 + 2 + 2 + strlen(client_id);
+
+ if (opts.user_name != NULL) {
+ opts.flags |= MG_MQTT_HAS_USER_NAME;
+ rem_len += strlen(opts.user_name) + 2;
+ }
+ if (opts.password != NULL) {
+ opts.flags |= MG_MQTT_HAS_PASSWORD;
+ rem_len += strlen(opts.password) + 2;
+ }
+
+ mg_send(nc, &header, 1);
+ mg_send(nc, &rem_len, 1);
+ mg_send(nc, "\00\06MQIsdp\03", 9);
+ mg_send(nc, &opts.flags, 1);
+
+ if (opts.keep_alive == 0) {
+ opts.keep_alive = 60;
+ }
+ keep_alive = htons(opts.keep_alive);
+ mg_send(nc, &keep_alive, 2);
+
+ len = htons(strlen(client_id));
+ mg_send(nc, &len, 2);
+ mg_send(nc, client_id, strlen(client_id));
+
+ if (opts.flags & MG_MQTT_HAS_USER_NAME) {
+ len = htons(strlen(opts.user_name));
+ mg_send(nc, &len, 2);
+ mg_send(nc, opts.user_name, strlen(opts.user_name));
+ }
+ if (opts.flags & MG_MQTT_HAS_PASSWORD) {
+ len = htons(strlen(opts.password));
+ mg_send(nc, &len, 2);
+ mg_send(nc, opts.password, strlen(opts.password));
+ }
+}
+
+static void mg_mqtt_prepend_header(struct mg_connection *nc, uint8_t cmd,
+ uint8_t flags, size_t len) {
+ size_t off = nc->send_mbuf.len - len;
+ uint8_t header = cmd << 4 | (uint8_t) flags;
+
+ uint8_t buf[1 + sizeof(size_t)];
+ uint8_t *vlen = &buf[1];
+
+ assert(nc->send_mbuf.len >= len);
+
+ buf[0] = header;
+
+ /* mqtt variable length encoding */
+ do {
+ *vlen = len % 0x80;
+ len /= 0x80;
+ if (len > 0) *vlen |= 0x80;
+ vlen++;
+ } while (len > 0);
+
+ mbuf_insert(&nc->send_mbuf, off, buf, vlen - buf);
+}
+
+void mg_mqtt_publish(struct mg_connection *nc, const char *topic,
+ uint16_t message_id, int flags, const void *data,
+ size_t len) {
+ size_t old_len = nc->send_mbuf.len;
+
+ uint16_t topic_len = htons(strlen(topic));
+ uint16_t message_id_net = htons(message_id);
+
+ mg_send(nc, &topic_len, 2);
+ mg_send(nc, topic, strlen(topic));
+ if (MG_MQTT_GET_QOS(flags) > 0) {
+ mg_send(nc, &message_id_net, 2);
+ }
+ mg_send(nc, data, len);
+
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PUBLISH, flags,
+ nc->send_mbuf.len - old_len);
+}
+
+void mg_mqtt_subscribe(struct mg_connection *nc,
+ const struct mg_mqtt_topic_expression *topics,
+ size_t topics_len, uint16_t message_id) {
+ size_t old_len = nc->send_mbuf.len;
+
+ uint16_t message_id_n = htons(message_id);
+ size_t i;
+
+ mg_send(nc, (char *) &message_id_n, 2);
+ for (i = 0; i < topics_len; i++) {
+ uint16_t topic_len_n = htons(strlen(topics[i].topic));
+ mg_send(nc, &topic_len_n, 2);
+ mg_send(nc, topics[i].topic, strlen(topics[i].topic));
+ mg_send(nc, &topics[i].qos, 1);
+ }
+
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_SUBSCRIBE, MG_MQTT_QOS(1),
+ nc->send_mbuf.len - old_len);
+}
+
+int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,
+ struct mg_str *topic, uint8_t *qos, int pos) {
+ unsigned char *buf = (unsigned char *) msg->payload.p + pos;
+ if ((size_t) pos >= msg->payload.len) {
+ return -1;
+ }
+
+ topic->len = buf[0] << 8 | buf[1];
+ topic->p = (char *) buf + 2;
+ *qos = buf[2 + topic->len];
+ return pos + 2 + topic->len + 1;
+}
+
+void mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics,
+ size_t topics_len, uint16_t message_id) {
+ size_t old_len = nc->send_mbuf.len;
+
+ uint16_t message_id_n = htons(message_id);
+ size_t i;
+
+ mg_send(nc, (char *) &message_id_n, 2);
+ for (i = 0; i < topics_len; i++) {
+ uint16_t topic_len_n = htons(strlen(topics[i]));
+ mg_send(nc, &topic_len_n, 2);
+ mg_send(nc, topics[i], strlen(topics[i]));
+ }
+
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_UNSUBSCRIBE, MG_MQTT_QOS(1),
+ nc->send_mbuf.len - old_len);
+}
+
+void mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code) {
+ uint8_t unused = 0;
+ mg_send(nc, &unused, 1);
+ mg_send(nc, &return_code, 1);
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_CONNACK, 0, 2);
+}
+
+/*
+ * Sends a command which contains only a `message_id` and a QoS level of 1.
+ *
+ * Helper function.
+ */
+static void mg_send_mqtt_short_command(struct mg_connection *nc, uint8_t cmd,
+ uint16_t message_id) {
+ uint16_t message_id_net = htons(message_id);
+ mg_send(nc, &message_id_net, 2);
+ mg_mqtt_prepend_header(nc, cmd, MG_MQTT_QOS(1), 2);
+}
+
+void mg_mqtt_puback(struct mg_connection *nc, uint16_t message_id) {
+ mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBACK, message_id);
+}
+
+void mg_mqtt_pubrec(struct mg_connection *nc, uint16_t message_id) {
+ mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBREC, message_id);
+}
+
+void mg_mqtt_pubrel(struct mg_connection *nc, uint16_t message_id) {
+ mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBREL, message_id);
+}
+
+void mg_mqtt_pubcomp(struct mg_connection *nc, uint16_t message_id) {
+ mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBCOMP, message_id);
+}
+
+void mg_mqtt_suback(struct mg_connection *nc, uint8_t *qoss, size_t qoss_len,
+ uint16_t message_id) {
+ size_t i;
+ uint16_t message_id_net = htons(message_id);
+ mg_send(nc, &message_id_net, 2);
+ for (i = 0; i < qoss_len; i++) {
+ mg_send(nc, &qoss[i], 1);
+ }
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_SUBACK, MG_MQTT_QOS(1), 2 + qoss_len);
+}
+
+void mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id) {
+ mg_send_mqtt_short_command(nc, MG_MQTT_CMD_UNSUBACK, message_id);
+}
+
+void mg_mqtt_ping(struct mg_connection *nc) {
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PINGREQ, 0, 0);
+}
+
+void mg_mqtt_pong(struct mg_connection *nc) {
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PINGRESP, 0, 0);
+}
+
+void mg_mqtt_disconnect(struct mg_connection *nc) {
+ mg_mqtt_prepend_header(nc, MG_MQTT_CMD_DISCONNECT, 0, 0);
+}
+
+#endif /* MG_DISABLE_MQTT */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/mqtt-broker.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/mqtt-broker.h" */
+
+#ifdef MG_ENABLE_MQTT_BROKER
+
+static void mg_mqtt_session_init(struct mg_mqtt_broker *brk,
+ struct mg_mqtt_session *s,
+ struct mg_connection *nc) {
+ s->brk = brk;
+ s->subscriptions = NULL;
+ s->num_subscriptions = 0;
+ s->nc = nc;
+}
+
+static void mg_mqtt_add_session(struct mg_mqtt_session *s) {
+ s->next = s->brk->sessions;
+ s->brk->sessions = s;
+ s->prev = NULL;
+ if (s->next != NULL) s->next->prev = s;
+}
+
+static void mg_mqtt_remove_session(struct mg_mqtt_session *s) {
+ if (s->prev == NULL) s->brk->sessions = s->next;
+ if (s->prev) s->prev->next = s->next;
+ if (s->next) s->next->prev = s->prev;
+}
+
+static void mg_mqtt_destroy_session(struct mg_mqtt_session *s) {
+ size_t i;
+ for (i = 0; i < s->num_subscriptions; i++) {
+ MG_FREE((void *) s->subscriptions[i].topic);
+ }
+ MG_FREE(s->subscriptions);
+ MG_FREE(s);
+}
+
+static void mg_mqtt_close_session(struct mg_mqtt_session *s) {
+ mg_mqtt_remove_session(s);
+ mg_mqtt_destroy_session(s);
+}
+
+void mg_mqtt_broker_init(struct mg_mqtt_broker *brk, void *user_data) {
+ brk->sessions = NULL;
+ brk->user_data = user_data;
+}
+
+static void mg_mqtt_broker_handle_connect(struct mg_mqtt_broker *brk,
+ struct mg_connection *nc) {
+ struct mg_mqtt_session *s = (struct mg_mqtt_session *) malloc(sizeof *s);
+ if (s == NULL) {
+ /* LCOV_EXCL_START */
+ mg_mqtt_connack(nc, MG_EV_MQTT_CONNACK_SERVER_UNAVAILABLE);
+ return;
+ /* LCOV_EXCL_STOP */
+ }
+
+ /* TODO(mkm): check header (magic and version) */
+
+ mg_mqtt_session_init(brk, s, nc);
+ s->user_data = nc->user_data;
+ nc->user_data = s;
+ mg_mqtt_add_session(s);
+
+ mg_mqtt_connack(nc, MG_EV_MQTT_CONNACK_ACCEPTED);
+}
+
+static void mg_mqtt_broker_handle_subscribe(struct mg_connection *nc,
+ struct mg_mqtt_message *msg) {
+ struct mg_mqtt_session *ss = (struct mg_mqtt_session *) nc->user_data;
+ uint8_t qoss[512];
+ size_t qoss_len = 0;
+ struct mg_str topic;
+ uint8_t qos;
+ int pos;
+ struct mg_mqtt_topic_expression *te;
+
+ for (pos = 0;
+ (pos = mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;) {
+ qoss[qoss_len++] = qos;
+ }
+
+ ss->subscriptions = (struct mg_mqtt_topic_expression *) realloc(
+ ss->subscriptions, sizeof(*ss->subscriptions) * qoss_len);
+ for (pos = 0;
+ (pos = mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;
+ ss->num_subscriptions++) {
+ te = &ss->subscriptions[ss->num_subscriptions];
+ te->topic = (char *) malloc(topic.len + 1);
+ te->qos = qos;
+ strncpy((char *) te->topic, topic.p, topic.len + 1);
+ }
+
+ mg_mqtt_suback(nc, qoss, qoss_len, msg->message_id);
+}
+
+/*
+ * Matches a topic against a topic expression
+ *
+ * See http://goo.gl/iWk21X
+ *
+ * Returns 1 if it matches; 0 otherwise.
+ */
+static int mg_mqtt_match_topic_expression(const char *exp, const char *topic) {
+ /* TODO(mkm): implement real matching */
+ int len = strlen(exp);
+ if (strchr(exp, '#')) {
+ len -= 2;
+ }
+ return strncmp(exp, topic, len) == 0;
+}
+
+static void mg_mqtt_broker_handle_publish(struct mg_mqtt_broker *brk,
+ struct mg_mqtt_message *msg) {
+ struct mg_mqtt_session *s;
+ size_t i;
+
+ for (s = mg_mqtt_next(brk, NULL); s != NULL; s = mg_mqtt_next(brk, s)) {
+ for (i = 0; i < s->num_subscriptions; i++) {
+ if (mg_mqtt_match_topic_expression(s->subscriptions[i].topic,
+ msg->topic)) {
+ mg_mqtt_publish(s->nc, msg->topic, 0, 0, msg->payload.p,
+ msg->payload.len);
+ break;
+ }
+ }
+ }
+}
+
+void mg_mqtt_broker(struct mg_connection *nc, int ev, void *data) {
+ struct mg_mqtt_message *msg = (struct mg_mqtt_message *) data;
+ struct mg_mqtt_broker *brk;
+
+ if (nc->listener) {
+ brk = (struct mg_mqtt_broker *) nc->listener->user_data;
+ } else {
+ brk = (struct mg_mqtt_broker *) nc->user_data;
+ }
+
+ switch (ev) {
+ case MG_EV_ACCEPT:
+ mg_set_protocol_mqtt(nc);
+ break;
+ case MG_EV_MQTT_CONNECT:
+ mg_mqtt_broker_handle_connect(brk, nc);
+ break;
+ case MG_EV_MQTT_SUBSCRIBE:
+ mg_mqtt_broker_handle_subscribe(nc, msg);
+ break;
+ case MG_EV_MQTT_PUBLISH:
+ mg_mqtt_broker_handle_publish(brk, msg);
+ break;
+ case MG_EV_CLOSE:
+ if (nc->listener) {
+ mg_mqtt_close_session((struct mg_mqtt_session *) nc->user_data);
+ }
+ break;
+ }
+}
+
+struct mg_mqtt_session *mg_mqtt_next(struct mg_mqtt_broker *brk,
+ struct mg_mqtt_session *s) {
+ return s == NULL ? brk->sessions : s->next;
+}
+
+#endif /* MG_ENABLE_MQTT_BROKER */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/dns.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef MG_DISABLE_DNS
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/dns.h" */
+
+static int mg_dns_tid = 0xa0;
+
+struct mg_dns_header {
+ uint16_t transaction_id;
+ uint16_t flags;
+ uint16_t num_questions;
+ uint16_t num_answers;
+ uint16_t num_authority_prs;
+ uint16_t num_other_prs;
+};
+
+struct mg_dns_resource_record *mg_dns_next_record(
+ struct mg_dns_message *msg, int query,
+ struct mg_dns_resource_record *prev) {
+ struct mg_dns_resource_record *rr;
+
+ for (rr = (prev == NULL ? msg->answers : prev + 1);
+ rr - msg->answers < msg->num_answers; rr++) {
+ if (rr->rtype == query) {
+ return rr;
+ }
+ }
+ return NULL;
+}
+
+int mg_dns_parse_record_data(struct mg_dns_message *msg,
+ struct mg_dns_resource_record *rr, void *data,
+ size_t data_len) {
+ switch (rr->rtype) {
+ case MG_DNS_A_RECORD:
+ if (data_len < sizeof(struct in_addr)) {
+ return -1;
+ }
+ if (rr->rdata.p + data_len > msg->pkt.p + msg->pkt.len) {
+ return -1;
+ }
+ memcpy(data, rr->rdata.p, data_len);
+ return 0;
+#ifdef MG_ENABLE_IPV6
+ case MG_DNS_AAAA_RECORD:
+ if (data_len < sizeof(struct in6_addr)) {
+ return -1; /* LCOV_EXCL_LINE */
+ }
+ memcpy(data, rr->rdata.p, data_len);
+ return 0;
+#endif
+ case MG_DNS_CNAME_RECORD:
+ mg_dns_uncompress_name(msg, &rr->rdata, (char *) data, data_len);
+ return 0;
+ }
+
+ return -1;
+}
+
+int mg_dns_insert_header(struct mbuf *io, size_t pos,
+ struct mg_dns_message *msg) {
+ struct mg_dns_header header;
+
+ memset(&header, 0, sizeof(header));
+ header.transaction_id = msg->transaction_id;
+ header.flags = htons(msg->flags);
+ header.num_questions = htons(msg->num_questions);
+ header.num_answers = htons(msg->num_answers);
+
+ return mbuf_insert(io, pos, &header, sizeof(header));
+}
+
+int mg_dns_copy_questions(struct mbuf *io, struct mg_dns_message *msg) {
+ unsigned char *begin, *end;
+ struct mg_dns_resource_record *last_q;
+ if (msg->num_questions <= 0) return 0;
+ begin = (unsigned char *) msg->pkt.p + sizeof(struct mg_dns_header);
+ last_q = &msg->questions[msg->num_questions - 1];
+ end = (unsigned char *) last_q->name.p + last_q->name.len + 4;
+ return mbuf_append(io, begin, end - begin);
+}
+
+static int mg_dns_encode_name(struct mbuf *io, const char *name, size_t len) {
+ const char *s;
+ unsigned char n;
+ size_t pos = io->len;
+
+ do {
+ if ((s = strchr(name, '.')) == NULL) {
+ s = name + len;
+ }
+
+ if (s - name > 127) {
+ return -1; /* TODO(mkm) cover */
+ }
+ n = s - name; /* chunk length */
+ mbuf_append(io, &n, 1); /* send length */
+ mbuf_append(io, name, n);
+
+ if (*s == '.') {
+ n++;
+ }
+
+ name += n;
+ len -= n;
+ } while (*s != '\0');
+ mbuf_append(io, "\0", 1); /* Mark end of host name */
+
+ return io->len - pos;
+}
+
+int mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr,
+ const char *name, size_t nlen, const void *rdata,
+ size_t rlen) {
+ size_t pos = io->len;
+ uint16_t u16;
+ uint32_t u32;
+
+ if (rr->kind == MG_DNS_INVALID_RECORD) {
+ return -1; /* LCOV_EXCL_LINE */
+ }
+
+ if (mg_dns_encode_name(io, name, nlen) == -1) {
+ return -1;
+ }
+
+ u16 = htons(rr->rtype);
+ mbuf_append(io, &u16, 2);
+ u16 = htons(rr->rclass);
+ mbuf_append(io, &u16, 2);
+
+ if (rr->kind == MG_DNS_ANSWER) {
+ u32 = htonl(rr->ttl);
+ mbuf_append(io, &u32, 4);
+
+ if (rr->rtype == MG_DNS_CNAME_RECORD) {
+ int clen;
+ /* fill size after encoding */
+ size_t off = io->len;
+ mbuf_append(io, &u16, 2);
+ if ((clen = mg_dns_encode_name(io, (const char *) rdata, rlen)) == -1) {
+ return -1;
+ }
+ u16 = clen;
+ io->buf[off] = u16 >> 8;
+ io->buf[off + 1] = u16 & 0xff;
+ } else {
+ u16 = htons(rlen);
+ mbuf_append(io, &u16, 2);
+ mbuf_append(io, rdata, rlen);
+ }
+ }
+
+ return io->len - pos;
+}
+
+void mg_send_dns_query(struct mg_connection *nc, const char *name,
+ int query_type) {
+ struct mg_dns_message *msg =
+ (struct mg_dns_message *) MG_CALLOC(1, sizeof(*msg));
+ struct mbuf pkt;
+ struct mg_dns_resource_record *rr = &msg->questions[0];
+
+ DBG(("%s %d", name, query_type));
+
+ mbuf_init(&pkt, 64 /* Start small, it'll grow as needed. */);
+
+ msg->transaction_id = ++mg_dns_tid;
+ msg->flags = 0x100;
+ msg->num_questions = 1;
+
+ mg_dns_insert_header(&pkt, 0, msg);
+
+ rr->rtype = query_type;
+ rr->rclass = 1; /* Class: inet */
+ rr->kind = MG_DNS_QUESTION;
+
+ if (mg_dns_encode_record(&pkt, rr, name, strlen(name), NULL, 0) == -1) {
+ /* TODO(mkm): return an error code */
+ goto cleanup; /* LCOV_EXCL_LINE */
+ }
+
+ /* TCP DNS requires messages to be prefixed with len */
+ if (!(nc->flags & MG_F_UDP)) {
+ uint16_t len = htons(pkt.len);
+ mbuf_insert(&pkt, 0, &len, 2);
+ }
+
+ mg_send(nc, pkt.buf, pkt.len);
+ mbuf_free(&pkt);
+
+cleanup:
+ MG_FREE(msg);
+}
+
+static unsigned char *mg_parse_dns_resource_record(
+ unsigned char *data, unsigned char *end, struct mg_dns_resource_record *rr,
+ int reply) {
+ unsigned char *name = data;
+ int chunk_len, data_len;
+
+ while (data < end && (chunk_len = *data)) {
+ if (((unsigned char *) data)[0] & 0xc0) {
+ data += 1;
+ break;
+ }
+ data += chunk_len + 1;
+ }
+
+ if (data > end - 5) {
+ return NULL;
+ }
+
+ rr->name.p = (char *) name;
+ rr->name.len = data - name + 1;
+ data++;
+
+ rr->rtype = data[0] << 8 | data[1];
+ data += 2;
+
+ rr->rclass = data[0] << 8 | data[1];
+ data += 2;
+
+ rr->kind = reply ? MG_DNS_ANSWER : MG_DNS_QUESTION;
+ if (reply) {
+ if (data >= end - 6) {
+ return NULL;
+ }
+
+ rr->ttl = (uint32_t) data[0] << 24 | (uint32_t) data[1] << 16 |
+ data[2] << 8 | data[3];
+ data += 4;
+
+ data_len = *data << 8 | *(data + 1);
+ data += 2;
+
+ rr->rdata.p = (char *) data;
+ rr->rdata.len = data_len;
+ data += data_len;
+ }
+ return data;
+}
+
+int mg_parse_dns(const char *buf, int len, struct mg_dns_message *msg) {
+ struct mg_dns_header *header = (struct mg_dns_header *) buf;
+ unsigned char *data = (unsigned char *) buf + sizeof(*header);
+ unsigned char *end = (unsigned char *) buf + len;
+ int i;
+
+ memset(msg, 0, sizeof(*msg));
+ msg->pkt.p = buf;
+ msg->pkt.len = len;
+
+ if (len < (int) sizeof(*header)) return -1;
+
+ msg->transaction_id = header->transaction_id;
+ msg->flags = ntohs(header->flags);
+ msg->num_questions = ntohs(header->num_questions);
+ if (msg->num_questions > (int) ARRAY_SIZE(msg->questions)) {
+ msg->num_questions = (int) ARRAY_SIZE(msg->questions);
+ }
+ msg->num_answers = ntohs(header->num_answers);
+ if (msg->num_answers > (int) ARRAY_SIZE(msg->answers)) {
+ msg->num_answers = (int) ARRAY_SIZE(msg->answers);
+ }
+
+ for (i = 0; i < msg->num_questions; i++) {
+ data = mg_parse_dns_resource_record(data, end, &msg->questions[i], 0);
+ if (data == NULL) return -1;
+ }
+
+ for (i = 0; i < msg->num_answers; i++) {
+ data = mg_parse_dns_resource_record(data, end, &msg->answers[i], 1);
+ if (data == NULL) return -1;
+ }
+
+ return 0;
+}
+
+size_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,
+ char *dst, int dst_len) {
+ int chunk_len;
+ char *old_dst = dst;
+ const unsigned char *data = (unsigned char *) name->p;
+ const unsigned char *end = (unsigned char *) msg->pkt.p + msg->pkt.len;
+
+ if (data >= end) {
+ return 0;
+ }
+
+ while ((chunk_len = *data++)) {
+ int leeway = dst_len - (dst - old_dst);
+ if (data >= end) {
+ return 0;
+ }
+
+ if (chunk_len & 0xc0) {
+ uint16_t off = (data[-1] & (~0xc0)) << 8 | data[0];
+ if (off >= msg->pkt.len) {
+ return 0;
+ }
+ data = (unsigned char *) msg->pkt.p + off;
+ continue;
+ }
+ if (chunk_len > leeway) {
+ chunk_len = leeway;
+ }
+
+ if (data + chunk_len >= end) {
+ return 0;
+ }
+
+ memcpy(dst, data, chunk_len);
+ data += chunk_len;
+ dst += chunk_len;
+ leeway -= chunk_len;
+ if (leeway == 0) {
+ return dst - old_dst;
+ }
+ *dst++ = '.';
+ }
+
+ if (dst != old_dst) {
+ *--dst = 0;
+ }
+ return dst - old_dst;
+}
+
+static void dns_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ struct mbuf *io = &nc->recv_mbuf;
+ struct mg_dns_message msg;
+
+ /* Pass low-level events to the user handler */
+ nc->handler(nc, ev, ev_data);
+
+ switch (ev) {
+ case MG_EV_RECV:
+ if (!(nc->flags & MG_F_UDP)) {
+ mbuf_remove(&nc->recv_mbuf, 2);
+ }
+ if (mg_parse_dns(nc->recv_mbuf.buf, nc->recv_mbuf.len, &msg) == -1) {
+ /* reply + recursion allowed + format error */
+ memset(&msg, 0, sizeof(msg));
+ msg.flags = 0x8081;
+ mg_dns_insert_header(io, 0, &msg);
+ if (!(nc->flags & MG_F_UDP)) {
+ uint16_t len = htons(io->len);
+ mbuf_insert(io, 0, &len, 2);
+ }
+ mg_send(nc, io->buf, io->len);
+ } else {
+ /* Call user handler with parsed message */
+ nc->handler(nc, MG_DNS_MESSAGE, &msg);
+ }
+ mbuf_remove(io, io->len);
+ break;
+ }
+}
+
+void mg_set_protocol_dns(struct mg_connection *nc) {
+ nc->proto_handler = dns_handler;
+}
+
+#endif /* MG_DISABLE_DNS */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/dns-server.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifdef MG_ENABLE_DNS_SERVER
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/dns-server.h" */
+
+struct mg_dns_reply mg_dns_create_reply(struct mbuf *io,
+ struct mg_dns_message *msg) {
+ struct mg_dns_reply rep;
+ rep.msg = msg;
+ rep.io = io;
+ rep.start = io->len;
+
+ /* reply + recursion allowed */
+ msg->flags |= 0x8080;
+ mg_dns_copy_questions(io, msg);
+
+ msg->num_answers = 0;
+ return rep;
+}
+
+void mg_dns_send_reply(struct mg_connection *nc, struct mg_dns_reply *r) {
+ size_t sent = r->io->len - r->start;
+ mg_dns_insert_header(r->io, r->start, r->msg);
+ if (!(nc->flags & MG_F_UDP)) {
+ uint16_t len = htons(sent);
+ mbuf_insert(r->io, r->start, &len, 2);
+ }
+
+ if (&nc->send_mbuf != r->io) {
+ mg_send(nc, r->io->buf + r->start, r->io->len - r->start);
+ r->io->len = r->start;
+ }
+}
+
+int mg_dns_reply_record(struct mg_dns_reply *reply,
+ struct mg_dns_resource_record *question,
+ const char *name, int rtype, int ttl, const void *rdata,
+ size_t rdata_len) {
+ struct mg_dns_message *msg = (struct mg_dns_message *) reply->msg;
+ char rname[512];
+ struct mg_dns_resource_record *ans = &msg->answers[msg->num_answers];
+ if (msg->num_answers >= MG_MAX_DNS_ANSWERS) {
+ return -1; /* LCOV_EXCL_LINE */
+ }
+
+ if (name == NULL) {
+ name = rname;
+ rname[511] = 0;
+ mg_dns_uncompress_name(msg, &question->name, rname, sizeof(rname) - 1);
+ }
+
+ *ans = *question;
+ ans->kind = MG_DNS_ANSWER;
+ ans->rtype = rtype;
+ ans->ttl = ttl;
+
+ if (mg_dns_encode_record(reply->io, ans, name, strlen(name), rdata,
+ rdata_len) == -1) {
+ return -1; /* LCOV_EXCL_LINE */
+ };
+
+ msg->num_answers++;
+ return 0;
+}
+
+#endif /* MG_ENABLE_DNS_SERVER */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/resolv.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef MG_DISABLE_RESOLVER
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/resolv.h" */
+
+#ifndef MG_DEFAULT_NAMESERVER
+#define MG_DEFAULT_NAMESERVER "8.8.8.8"
+#endif
+
+static const char *mg_default_dns_server = "udp://" MG_DEFAULT_NAMESERVER ":53";
+
+MG_INTERNAL char mg_dns_server[256];
+
+struct mg_resolve_async_request {
+ char name[1024];
+ int query;
+ mg_resolve_callback_t callback;
+ void *data;
+ time_t timeout;
+ int max_retries;
+ enum mg_resolve_err err;
+
+ /* state */
+ time_t last_time;
+ int retries;
+};
+
+/*
+ * Find what nameserver to use.
+ *
+ * Return 0 if OK, -1 if error
+ */
+static int mg_get_ip_address_of_nameserver(char *name, size_t name_len) {
+ int ret = -1;
+
+#ifdef _WIN32
+ int i;
+ LONG err;
+ HKEY hKey, hSub;
+ char subkey[512], value[128],
+ *key = "SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces";
+
+ if ((err = RegOpenKeyA(HKEY_LOCAL_MACHINE, key, &hKey)) != ERROR_SUCCESS) {
+ fprintf(stderr, "cannot open reg key %s: %d\n", key, err);
+ ret = -1;
+ } else {
+ for (ret = -1, i = 0;
+ RegEnumKeyA(hKey, i, subkey, sizeof(subkey)) == ERROR_SUCCESS; i++) {
+ DWORD type, len = sizeof(value);
+ if (RegOpenKeyA(hKey, subkey, &hSub) == ERROR_SUCCESS &&
+ (RegQueryValueExA(hSub, "NameServer", 0, &type, (void *) value,
+ &len) == ERROR_SUCCESS ||
+ RegQueryValueExA(hSub, "DhcpNameServer", 0, &type, (void *) value,
+ &len) == ERROR_SUCCESS)) {
+ /*
+ * See https://github.com/cesanta/mongoose/issues/176
+ * The value taken from the registry can be empty, a single
+ * IP address, or multiple IP addresses separated by comma.
+ * If it's empty, check the next interface.
+ * If it's multiple IP addresses, take the first one.
+ */
+ char *comma = strchr(value, ',');
+ if (value[0] == '\0') {
+ continue;
+ }
+ if (comma != NULL) {
+ *comma = '\0';
+ }
+ snprintf(name, name_len, "udp://%s:53", value);
+ ret = 0;
+ RegCloseKey(hSub);
+ break;
+ }
+ }
+ RegCloseKey(hKey);
+ }
+#elif !defined(MG_DISABLE_FILESYSTEM)
+ FILE *fp;
+ char line[512];
+
+ if ((fp = fopen("/etc/resolv.conf", "r")) == NULL) {
+ ret = -1;
+ } else {
+ /* Try to figure out what nameserver to use */
+ for (ret = -1; fgets(line, sizeof(line), fp) != NULL;) {
+ char buf[256];
+ if (sscanf(line, "nameserver %255[^\n\t #]s", buf) == 1) {
+ snprintf(name, name_len, "udp://%s:53", buf);
+ ret = 0;
+ break;
+ }
+ }
+ (void) fclose(fp);
+ }
+#else
+ snprintf(name, name_len, "%s", mg_default_dns_server);
+#endif /* _WIN32 */
+
+ return ret;
+}
+
+int mg_resolve_from_hosts_file(const char *name, union socket_address *usa) {
+#ifndef MG_DISABLE_FILESYSTEM
+ /* TODO(mkm) cache /etc/hosts */
+ FILE *fp;
+ char line[1024];
+ char *p;
+ char alias[256];
+ unsigned int a, b, c, d;
+ int len = 0;
+
+ if ((fp = fopen("/etc/hosts", "r")) == NULL) {
+ return -1;
+ }
+
+ for (; fgets(line, sizeof(line), fp) != NULL;) {
+ if (line[0] == '#') continue;
+
+ if (sscanf(line, "%u.%u.%u.%u%n", &a, &b, &c, &d, &len) == 0) {
+ /* TODO(mkm): handle ipv6 */
+ continue;
+ }
+ for (p = line + len; sscanf(p, "%s%n", alias, &len) == 1; p += len) {
+ if (strcmp(alias, name) == 0) {
+ usa->sin.sin_addr.s_addr = htonl(a << 24 | b << 16 | c << 8 | d);
+ fclose(fp);
+ return 0;
+ }
+ }
+ }
+
+ fclose(fp);
+#endif
+
+ return -1;
+}
+
+static void mg_resolve_async_eh(struct mg_connection *nc, int ev, void *data) {
+ time_t now = time(NULL);
+ struct mg_resolve_async_request *req;
+ struct mg_dns_message *msg;
+
+ DBG(("ev=%d user_data=%p", ev, nc->user_data));
+
+ req = (struct mg_resolve_async_request *) nc->user_data;
+
+ if (req == NULL) {
+ return;
+ }
+
+ switch (ev) {
+ case MG_EV_CONNECT:
+ case MG_EV_POLL:
+ if (req->retries > req->max_retries) {
+ req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT;
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ break;
+ }
+ if (now - req->last_time >= req->timeout) {
+ mg_send_dns_query(nc, req->name, req->query);
+ req->last_time = now;
+ req->retries++;
+ }
+ break;
+ case MG_EV_RECV:
+ msg = (struct mg_dns_message *) MG_MALLOC(sizeof(*msg));
+ if (mg_parse_dns(nc->recv_mbuf.buf, *(int *) data, msg) == 0 &&
+ msg->num_answers > 0) {
+ req->callback(msg, req->data, MG_RESOLVE_OK);
+ nc->user_data = NULL;
+ MG_FREE(req);
+ } else {
+ req->err = MG_RESOLVE_NO_ANSWERS;
+ }
+ MG_FREE(msg);
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ break;
+ case MG_EV_SEND:
+ /*
+ * If a send error occurs, prevent closing of the connection by the core.
+ * We will retry after timeout.
+ */
+ nc->flags &= ~MG_F_CLOSE_IMMEDIATELY;
+ mbuf_remove(&nc->send_mbuf, nc->send_mbuf.len);
+ break;
+ case MG_EV_TIMER:
+ req->err = MG_RESOLVE_TIMEOUT;
+ nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+ break;
+ case MG_EV_CLOSE:
+ /* If we got here with request still not done, fire an error callback. */
+ if (req != NULL) {
+ req->callback(NULL, req->data, req->err);
+ nc->user_data = NULL;
+ MG_FREE(req);
+ }
+ break;
+ }
+}
+
+int mg_resolve_async(struct mg_mgr *mgr, const char *name, int query,
+ mg_resolve_callback_t cb, void *data) {
+ struct mg_resolve_async_opts opts;
+ memset(&opts, 0, sizeof(opts));
+ return mg_resolve_async_opt(mgr, name, query, cb, data, opts);
+}
+
+int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,
+ mg_resolve_callback_t cb, void *data,
+ struct mg_resolve_async_opts opts) {
+ struct mg_resolve_async_request *req;
+ struct mg_connection *dns_nc;
+ const char *nameserver = opts.nameserver_url;
+
+ DBG(("%s %d %p", name, query, opts.dns_conn));
+
+ /* resolve with DNS */
+ req = (struct mg_resolve_async_request *) MG_CALLOC(1, sizeof(*req));
+ if (req == NULL) {
+ return -1;
+ }
+
+ strncpy(req->name, name, sizeof(req->name));
+ req->query = query;
+ req->callback = cb;
+ req->data = data;
+ /* TODO(mkm): parse defaults out of resolve.conf */
+ req->max_retries = opts.max_retries ? opts.max_retries : 2;
+ req->timeout = opts.timeout ? opts.timeout : 5;
+
+ /* Lazily initialize dns server */
+ if (nameserver == NULL && mg_dns_server[0] == '\0' &&
+ mg_get_ip_address_of_nameserver(mg_dns_server, sizeof(mg_dns_server)) ==
+ -1) {
+ strncpy(mg_dns_server, mg_default_dns_server, sizeof(mg_dns_server));
+ }
+
+ if (nameserver == NULL) {
+ nameserver = mg_dns_server;
+ }
+
+ dns_nc = mg_connect(mgr, nameserver, mg_resolve_async_eh);
+ if (dns_nc == NULL) {
+ free(req);
+ return -1;
+ }
+ dns_nc->user_data = req;
+ if (opts.dns_conn != NULL) {
+ *opts.dns_conn = dns_nc;
+ }
+
+ return 0;
+}
+
+#endif /* MG_DISABLE_RESOLVE */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/coap.c"
+#endif
+/*
+ * Copyright (c) 2015 Cesanta Software Limited
+ * All rights reserved
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in .
+ */
+
+/* Amalgamated: #include "mongoose/src/internal.h" */
+/* Amalgamated: #include "mongoose/src/coap.h" */
+
+#ifdef MG_ENABLE_COAP
+
+void mg_coap_free_options(struct mg_coap_message *cm) {
+ while (cm->options != NULL) {
+ struct mg_coap_option *next = cm->options->next;
+ MG_FREE(cm->options);
+ cm->options = next;
+ }
+}
+
+struct mg_coap_option *mg_coap_add_option(struct mg_coap_message *cm,
+ uint32_t number, char *value,
+ size_t len) {
+ struct mg_coap_option *new_option =
+ (struct mg_coap_option *) MG_CALLOC(1, sizeof(*new_option));
+
+ new_option->number = number;
+ new_option->value.p = value;
+ new_option->value.len = len;
+
+ if (cm->options == NULL) {
+ cm->options = cm->optiomg_tail = new_option;
+ } else {
+ /*
+ * A very simple attention to help clients to compose options:
+ * CoAP wants to see options ASC ordered.
+ * Could be change by using sort in coap_compose
+ */
+ if (cm->optiomg_tail->number <= new_option->number) {
+ /* if option is already ordered just add it */
+ cm->optiomg_tail = cm->optiomg_tail->next = new_option;
+ } else {
+ /* looking for appropriate position */
+ struct mg_coap_option *current_opt = cm->options;
+ struct mg_coap_option *prev_opt = 0;
+
+ while (current_opt != NULL) {
+ if (current_opt->number > new_option->number) {
+ break;
+ }
+ prev_opt = current_opt;
+ current_opt = current_opt->next;
+ }
+
+ if (prev_opt != NULL) {
+ prev_opt->next = new_option;
+ new_option->next = current_opt;
+ } else {
+ /* insert new_option to the beginning */
+ new_option->next = cm->options;
+ cm->options = new_option;
+ }
+ }
+ }
+
+ return new_option;
+}
+
+/*
+ * Fills CoAP header in mg_coap_message.
+ *
+ * Helper function.
+ */
+static char *coap_parse_header(char *ptr, struct mbuf *io,
+ struct mg_coap_message *cm) {
+ if (io->len < sizeof(uint32_t)) {
+ cm->flags |= MG_COAP_NOT_ENOUGH_DATA;
+ return NULL;
+ }
+
+ /*
+ * Version (Ver): 2-bit unsigned integer. Indicates the CoAP version
+ * number. Implementations of this specification MUST set this field
+ * to 1 (01 binary). Other values are reserved for future versions.
+ * Messages with unknown version numbers MUST be silently ignored.
+ */
+ if (((uint8_t) *ptr >> 6) != 1) {
+ cm->flags |= MG_COAP_IGNORE;
+ return NULL;
+ }
+
+ /*
+ * Type (T): 2-bit unsigned integer. Indicates if this message is of
+ * type Confirmable (0), Non-confirmable (1), Acknowledgement (2), or
+ * Reset (3).
+ */
+ cm->msg_type = ((uint8_t) *ptr & 0x30) >> 4;
+ cm->flags |= MG_COAP_MSG_TYPE_FIELD;
+
+ /*
+ * Token Length (TKL): 4-bit unsigned integer. Indicates the length of
+ * the variable-length Token field (0-8 bytes). Lengths 9-15 are
+ * reserved, MUST NOT be sent, and MUST be processed as a message
+ * format error.
+ */
+ cm->token.len = *ptr & 0x0F;
+ if (cm->token.len > 8) {
+ cm->flags |= MG_COAP_FORMAT_ERROR;
+ return NULL;
+ }
+
+ ptr++;
+
+ /*
+ * Code: 8-bit unsigned integer, split into a 3-bit class (most
+ * significant bits) and a 5-bit detail (least significant bits)
+ */
+ cm->code_class = (uint8_t) *ptr >> 5;
+ cm->code_detail = *ptr & 0x1F;
+ cm->flags |= (MG_COAP_CODE_CLASS_FIELD | MG_COAP_CODE_DETAIL_FIELD);
+
+ ptr++;
+
+ /* Message ID: 16-bit unsigned integer in network byte order. */
+ cm->msg_id = (uint8_t) *ptr << 8 | (uint8_t) * (ptr + 1);
+ cm->flags |= MG_COAP_MSG_ID_FIELD;
+
+ ptr += 2;
+
+ return ptr;
+}
+
+/*
+ * Fills token information in mg_coap_message.
+ *
+ * Helper function.
+ */
+static char *coap_get_token(char *ptr, struct mbuf *io,
+ struct mg_coap_message *cm) {
+ if (cm->token.len != 0) {
+ if (ptr + cm->token.len > io->buf + io->len) {
+ cm->flags |= MG_COAP_NOT_ENOUGH_DATA;
+ return NULL;
+ } else {
+ cm->token.p = ptr;
+ ptr += cm->token.len;
+ cm->flags |= MG_COAP_TOKEN_FIELD;
+ }
+ }
+
+ return ptr;
+}
+
+/*
+ * Returns Option Delta or Length.
+ *
+ * Helper function.
+ */
+static int coap_get_ext_opt(char *ptr, struct mbuf *io, uint16_t *opt_info) {
+ int ret = 0;
+
+ if (*opt_info == 13) {
+ /*
+ * 13: An 8-bit unsigned integer follows the initial byte and
+ * indicates the Option Delta/Length minus 13.
+ */
+ if (ptr < io->buf + io->len) {
+ *opt_info = (uint8_t) *ptr + 13;
+ ret = sizeof(uint8_t);
+ } else {
+ ret = -1; /* LCOV_EXCL_LINE */
+ }
+ } else if (*opt_info == 14) {
+ /*
+ * 14: A 16-bit unsigned integer in network byte order follows the
+ * initial byte and indicates the Option Delta/Length minus 269.
+ */
+ if (ptr + sizeof(uint8_t) < io->buf + io->len) {
+ *opt_info = ((uint8_t) *ptr << 8 | (uint8_t) * (ptr + 1)) + 269;
+ ret = sizeof(uint16_t);
+ } else {
+ ret = -1; /* LCOV_EXCL_LINE */
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Fills options in mg_coap_message.
+ *
+ * Helper function.
+ *
+ * General options format:
+ * +---------------+---------------+
+ * | Option Delta | Option Length | 1 byte
+ * +---------------+---------------+
+ * \ Option Delta (extended) \ 0-2 bytes
+ * +-------------------------------+
+ * / Option Length (extended) \ 0-2 bytes
+ * +-------------------------------+
+ * \ Option Value \ 0 or more bytes
+ * +-------------------------------+
+ */
+static char *coap_get_options(char *ptr, struct mbuf *io,
+ struct mg_coap_message *cm) {
+ uint16_t prev_opt = 0;
+
+ if (ptr == io->buf + io->len) {
+ /* end of packet, ok */
+ return NULL;
+ }
+
+ /* 0xFF is payload marker */
+ while (ptr < io->buf + io->len && (uint8_t) *ptr != 0xFF) {
+ uint16_t option_delta, option_lenght;
+ int optinfo_len;
+
+ /* Option Delta: 4-bit unsigned integer */
+ option_delta = ((uint8_t) *ptr & 0xF0) >> 4;
+ /* Option Length: 4-bit unsigned integer */
+ option_lenght = *ptr & 0x0F;
+
+ if (option_delta == 15 || option_lenght == 15) {
+ /*
+ * 15: Reserved for future use. If the field is set to this value,
+ * it MUST be processed as a message format error
+ */
+ cm->flags |= MG_COAP_FORMAT_ERROR;
+ break;
+ }
+
+ ptr++;
+
+ /* check for extended option delta */
+ optinfo_len = coap_get_ext_opt(ptr, io, &option_delta);
+ if (optinfo_len == -1) {
+ cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */
+ break; /* LCOV_EXCL_LINE */
+ }
+
+ ptr += optinfo_len;
+
+ /* check or extended option lenght */
+ optinfo_len = coap_get_ext_opt(ptr, io, &option_lenght);
+ if (optinfo_len == -1) {
+ cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */
+ break; /* LCOV_EXCL_LINE */
+ }
+
+ ptr += optinfo_len;
+
+ /*
+ * Instead of specifying the Option Number directly, the instances MUST
+ * appear in order of their Option Numbers and a delta encoding is used
+ * between them.
+ */
+ option_delta += prev_opt;
+
+ mg_coap_add_option(cm, option_delta, ptr, option_lenght);
+
+ prev_opt = option_delta;
+
+ if (ptr + option_lenght > io->buf + io->len) {
+ cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */
+ break; /* LCOV_EXCL_LINE */
+ }
+
+ ptr += option_lenght;
+ }
+
+ if ((cm->flags & MG_COAP_ERROR) != 0) {
+ mg_coap_free_options(cm);
+ return NULL;
+ }
+
+ cm->flags |= MG_COAP_OPTIOMG_FIELD;
+
+ if (ptr == io->buf + io->len) {
+ /* end of packet, ok */
+ return NULL;
+ }
+
+ ptr++;
+
+ return ptr;
+}
+
+uint32_t mg_coap_parse(struct mbuf *io, struct mg_coap_message *cm) {
+ char *ptr;
+
+ memset(cm, 0, sizeof(*cm));
+
+ if ((ptr = coap_parse_header(io->buf, io, cm)) == NULL) {
+ return cm->flags;
+ }
+
+ if ((ptr = coap_get_token(ptr, io, cm)) == NULL) {
+ return cm->flags;
+ }
+
+ if ((ptr = coap_get_options(ptr, io, cm)) == NULL) {
+ return cm->flags;
+ }
+
+ /* the rest is payload */
+ cm->payload.len = io->len - (ptr - io->buf);
+ if (cm->payload.len != 0) {
+ cm->payload.p = ptr;
+ cm->flags |= MG_COAP_PAYLOAD_FIELD;
+ }
+
+ return cm->flags;
+}
+
+/*
+ * Calculates extended size of given Opt Number/Length in coap message.
+ *
+ * Helper function.
+ */
+static size_t coap_get_ext_opt_size(uint32_t value) {
+ int ret = 0;
+
+ if (value >= 13 && value <= 0xFF + 13) {
+ ret = sizeof(uint8_t);
+ } else if (value > 0xFF + 13 && value <= 0xFFFF + 269) {
+ ret = sizeof(uint16_t);
+ }
+
+ return ret;
+}
+
+/*
+ * Splits given Opt Number/Length into base and ext values.
+ *
+ * Helper function.
+ */
+static int coap_split_opt(uint32_t value, uint8_t *base, uint16_t *ext) {
+ int ret = 0;
+
+ if (value < 13) {
+ *base = value;
+ } else if (value >= 13 && value <= 0xFF + 13) {
+ *base = 13;
+ *ext = value - 13;
+ ret = sizeof(uint8_t);
+ } else if (value > 0xFF + 13 && value <= 0xFFFF + 269) {
+ *base = 14;
+ *ext = value - 269;
+ ret = sizeof(uint16_t);
+ }
+
+ return ret;
+}
+
+/*
+ * Puts uint16_t (in network order) into given char stream.
+ *
+ * Helper function.
+ */
+static char *coap_add_uint16(char *ptr, uint16_t val) {
+ *ptr = val >> 8;
+ ptr++;
+ *ptr = val & 0x00FF;
+ ptr++;
+ return ptr;
+}
+
+/*
+ * Puts extended value of Opt Number/Length into given char stream.
+ *
+ * Helper function.
+ */
+static char *coap_add_opt_info(char *ptr, uint16_t val, size_t len) {
+ if (len == sizeof(uint8_t)) {
+ *ptr = val;
+ ptr++;
+ } else if (len == sizeof(uint16_t)) {
+ ptr = coap_add_uint16(ptr, val);
+ }
+
+ return ptr;
+}
+
+/*
+ * Verifies given mg_coap_message and calculates message size for it.
+ *
+ * Helper function.
+ */
+static uint32_t coap_calculate_packet_size(struct mg_coap_message *cm,
+ size_t *len) {
+ struct mg_coap_option *opt;
+ uint32_t prev_opt_number;
+
+ *len = 4; /* header */
+ if (cm->msg_type > MG_COAP_MSG_MAX) {
+ return MG_COAP_ERROR | MG_COAP_MSG_TYPE_FIELD;
+ }
+ if (cm->token.len > 8) {
+ return MG_COAP_ERROR | MG_COAP_TOKEN_FIELD;
+ }
+ if (cm->code_class > 7) {
+ return MG_COAP_ERROR | MG_COAP_CODE_CLASS_FIELD;
+ }
+ if (cm->code_detail > 31) {
+ return MG_COAP_ERROR | MG_COAP_CODE_DETAIL_FIELD;
+ }
+
+ *len += cm->token.len;
+ if (cm->payload.len != 0) {
+ *len += cm->payload.len + 1; /* ... + 1; add payload marker */
+ }
+
+ opt = cm->options;
+ prev_opt_number = 0;
+ while (opt != NULL) {
+ *len += 1; /* basic delta/length */
+ *len += coap_get_ext_opt_size(opt->number);
+ *len += coap_get_ext_opt_size((uint32_t) opt->value.len);
+ /*
+ * Current implementation performs check if
+ * option_number > previous option_number and produces an error
+ * TODO(alashkin): write design doc with limitations
+ * May be resorting is more suitable solution.
+ */
+ if ((opt->next != NULL && opt->number > opt->next->number) ||
+ opt->value.len > 0xFFFF + 269 ||
+ opt->number - prev_opt_number > 0xFFFF + 269) {
+ return MG_COAP_ERROR | MG_COAP_OPTIOMG_FIELD;
+ }
+ *len += opt->value.len;
+ opt = opt->next;
+ }
+
+ return 0;
+}
+
+uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io) {
+ struct mg_coap_option *opt;
+ uint32_t res, prev_opt_number;
+ size_t prev_io_len, packet_size;
+ char *ptr;
+
+ res = coap_calculate_packet_size(cm, &packet_size);
+ if (res != 0) {
+ return res;
+ }
+
+ /* saving previous lenght to handle non-empty mbuf */
+ prev_io_len = io->len;
+ mbuf_append(io, NULL, packet_size);
+ ptr = io->buf + prev_io_len;
+
+ /*
+ * since cm is verified, it is possible to use bits shift operator
+ * without additional zeroing of unused bits
+ */
+
+ /* ver: 2 bits, msg_type: 2 bits, toklen: 4 bits */
+ *ptr = (1 << 6) | (cm->msg_type << 4) | (cm->token.len);
+ ptr++;
+
+ /* code class: 3 bits, code detail: 5 bits */
+ *ptr = (cm->code_class << 5) | (cm->code_detail);
+ ptr++;
+
+ ptr = coap_add_uint16(ptr, cm->msg_id);
+
+ if (cm->token.len != 0) {
+ memcpy(ptr, cm->token.p, cm->token.len);
+ ptr += cm->token.len;
+ }
+
+ opt = cm->options;
+ prev_opt_number = 0;
+ while (opt != NULL) {
+ uint8_t delta_base = 0, length_base = 0;
+ uint16_t delta_ext, length_ext;
+
+ size_t opt_delta_len =
+ coap_split_opt(opt->number - prev_opt_number, &delta_base, &delta_ext);
+ size_t opt_lenght_len =
+ coap_split_opt((uint32_t) opt->value.len, &length_base, &length_ext);
+
+ *ptr = (delta_base << 4) | length_base;
+ ptr++;
+
+ ptr = coap_add_opt_info(ptr, delta_ext, opt_delta_len);
+ ptr = coap_add_opt_info(ptr, length_ext, opt_lenght_len);
+
+ if (opt->value.len != 0) {
+ memcpy(ptr, opt->value.p, opt->value.len);
+ ptr += opt->value.len;
+ }
+
+ prev_opt_number = opt->number;
+ opt = opt->next;
+ }
+
+ if (cm->payload.len != 0) {
+ *ptr = -1;
+ ptr++;
+ memcpy(ptr, cm->payload.p, cm->payload.len);
+ }
+
+ return 0;
+}
+
+uint32_t mg_coap_send_message(struct mg_connection *nc,
+ struct mg_coap_message *cm) {
+ struct mbuf packet_out;
+ uint32_t compose_res;
+
+ mbuf_init(&packet_out, 0);
+ compose_res = mg_coap_compose(cm, &packet_out);
+ if (compose_res != 0) {
+ return compose_res; /* LCOV_EXCL_LINE */
+ }
+
+ mg_send(nc, packet_out.buf, (int) packet_out.len);
+ mbuf_free(&packet_out);
+
+ return 0;
+}
+
+uint32_t mg_coap_send_ack(struct mg_connection *nc, uint16_t msg_id) {
+ struct mg_coap_message cm;
+ memset(&cm, 0, sizeof(cm));
+ cm.msg_type = MG_COAP_MSG_ACK;
+ cm.msg_id = msg_id;
+
+ return mg_coap_send_message(nc, &cm);
+}
+
+static void coap_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ struct mbuf *io = &nc->recv_mbuf;
+ struct mg_coap_message cm;
+ uint32_t parse_res;
+
+ memset(&cm, 0, sizeof(cm));
+
+ nc->handler(nc, ev, ev_data);
+
+ switch (ev) {
+ case MG_EV_RECV:
+ parse_res = mg_coap_parse(io, &cm);
+ if ((parse_res & MG_COAP_IGNORE) == 0) {
+ if ((cm.flags & MG_COAP_NOT_ENOUGH_DATA) != 0) {
+ /*
+ * Since we support UDP only
+ * MG_COAP_NOT_ENOUGH_DATA == MG_COAP_FORMAT_ERROR
+ */
+ cm.flags |= MG_COAP_FORMAT_ERROR; /* LCOV_EXCL_LINE */
+ } /* LCOV_EXCL_LINE */
+ nc->handler(nc, MG_COAP_EVENT_BASE + cm.msg_type, &cm);
+ }
+
+ mg_coap_free_options(&cm);
+ mbuf_remove(io, io->len);
+ break;
+ }
+}
+/*
+ * Attach built-in CoAP event handler to the given connection.
+ *
+ * The user-defined event handler will receive following extra events:
+ *
+ * - MG_EV_COAP_CON
+ * - MG_EV_COAP_NOC
+ * - MG_EV_COAP_ACK
+ * - MG_EV_COAP_RST
+ */
+int mg_set_protocol_coap(struct mg_connection *nc) {
+ /* supports UDP only */
+ if ((nc->flags & MG_F_UDP) == 0) {
+ return -1;
+ }
+
+ nc->proto_handler = coap_handler;
+
+ return 0;
+}
+
+#endif /* MG_DISABLE_COAP */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/platforms/cc3200/cc3200_libc.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#if CS_PLATFORM == CS_P_CC3200
+
+#include
+#include
+
+#ifndef __TI_COMPILER_VERSION__
+#include
+#include
+#include
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define CONSOLE_UART UARTA0_BASE
+
+#ifdef __TI_COMPILER_VERSION__
+int asprintf(char **strp, const char *fmt, ...) {
+ va_list ap;
+ int len;
+
+ *strp = malloc(BUFSIZ);
+ if (*strp == NULL) return -1;
+
+ va_start(ap, fmt);
+ len = vsnprintf(*strp, BUFSIZ, fmt, ap);
+ va_end(ap);
+
+ if (len > 0) {
+ *strp = realloc(*strp, len);
+ if (*strp == NULL) return -1;
+ }
+
+ if (len >= BUFSIZ) {
+ va_start(ap, fmt);
+ len = vsnprintf(*strp, len, fmt, ap);
+ va_end(ap);
+ }
+
+ return len;
+}
+#endif /* __TI_COMPILER_VERSION__ */
+
+#ifndef __TI_COMPILER_VERSION__
+int _gettimeofday_r(struct _reent *r, struct timeval *tp, void *tzp) {
+#else
+int gettimeofday(struct timeval *tp, void *tzp) {
+#endif
+ unsigned long long r1 = 0, r2;
+ /* Achieve two consecutive reads of the same value. */
+ do {
+ r2 = r1;
+ r1 = PRCMSlowClkCtrFastGet();
+ } while (r1 != r2);
+ /* This is a 32768 Hz counter. */
+ tp->tv_sec = (r1 >> 15);
+ /* 1/32768-th of a second is 30.517578125 microseconds, approx. 31,
+ * but we round down so it doesn't overflow at 32767 */
+ tp->tv_usec = (r1 & 0x7FFF) * 30;
+ return 0;
+}
+
+long int random(void) {
+ return 42; /* FIXME */
+}
+
+void fprint_str(FILE *fp, const char *str) {
+ while (*str != '\0') {
+ if (*str == '\n') MAP_UARTCharPut(CONSOLE_UART, '\r');
+ MAP_UARTCharPut(CONSOLE_UART, *str++);
+ }
+}
+
+void _exit(int status) {
+ fprint_str(stderr, "_exit\n");
+ /* cause an unaligned access exception, that will drop you into gdb */
+ *(int *) 1 = status;
+ while (1)
+ ; /* avoid gcc warning because stdlib abort() has noreturn attribute */
+}
+
+void _not_implemented(const char *what) {
+ fprint_str(stderr, what);
+ fprint_str(stderr, " is not implemented\n");
+ _exit(42);
+}
+
+int _kill(int pid, int sig) {
+ (void) pid;
+ (void) sig;
+ _not_implemented("_kill");
+ return -1;
+}
+
+int _getpid() {
+ fprint_str(stderr, "_getpid is not implemented\n");
+ return 42;
+}
+
+int _isatty(int fd) {
+ /* 0, 1 and 2 are TTYs. */
+ return fd < 2;
+}
+
+#endif /* CS_PLATFORM == CS_P_CC3200 */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/platforms/msp432/msp432_libc.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#if CS_PLATFORM == CS_P_MSP432
+
+#include
+#include
+
+int gettimeofday(struct timeval *tp, void *tzp) {
+ uint32_t ticks = Clock_getTicks();
+ tp->tv_sec = ticks / 1000;
+ tp->tv_usec = (ticks % 1000) * 1000;
+ return 0;
+}
+
+long int random(void) {
+ return 42; /* FIXME */
+}
+
+#endif /* CS_PLATFORM == CS_P_MSP432 */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/platforms/simplelink/sl_fs_slfs.h"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_
+#define CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_
+
+#if defined(MG_FS_SLFS)
+
+#include
+#ifndef __TI_COMPILER_VERSION__
+#include
+#include
+#endif
+
+#define MAX_OPEN_SLFS_FILES 8
+
+/* Indirect libc interface - same functions, different names. */
+int fs_slfs_open(const char *pathname, int flags, mode_t mode);
+int fs_slfs_close(int fd);
+ssize_t fs_slfs_read(int fd, void *buf, size_t count);
+ssize_t fs_slfs_write(int fd, const void *buf, size_t count);
+int fs_slfs_stat(const char *pathname, struct stat *s);
+int fs_slfs_fstat(int fd, struct stat *s);
+off_t fs_slfs_lseek(int fd, off_t offset, int whence);
+int fs_slfs_unlink(const char *filename);
+int fs_slfs_rename(const char *from, const char *to);
+
+void fs_slfs_set_new_file_size(const char *name, size_t size);
+
+#endif /* defined(MG_FS_SLFS) */
+
+#endif /* CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_ */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/platforms/simplelink/sl_fs_slfs.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/* Standard libc interface to TI SimpleLink FS. */
+
+#if defined(MG_FS_SLFS) || defined(CC3200_FS_SLFS)
+
+/* Amalgamated: #include "common/platforms/simplelink/sl_fs_slfs.h" */
+
+#include
+
+#if CS_PLATFORM == CS_P_CC3200
+#include
+#endif
+#include
+#include
+
+/* Amalgamated: #include "common/cs_dbg.h" */
+
+extern int set_errno(int e); /* From sl_fs.c */
+
+/*
+ * With SLFS, you have to pre-declare max file size. Yes. Really.
+ * 64K should be enough for everyone. Right?
+ */
+#ifndef FS_SLFS_MAX_FILE_SIZE
+#define FS_SLFS_MAX_FILE_SIZE (64 * 1024)
+#endif
+
+struct sl_file_size_hint {
+ char *name;
+ size_t size;
+};
+
+struct sl_fd_info {
+ _i32 fh;
+ _off_t pos;
+ size_t size;
+};
+
+static struct sl_fd_info s_sl_fds[MAX_OPEN_SLFS_FILES];
+static struct sl_file_size_hint s_sl_file_size_hints[MAX_OPEN_SLFS_FILES];
+
+static int sl_fs_to_errno(_i32 r) {
+ DBG(("SL error: %d", (int) r));
+ switch (r) {
+ case SL_FS_OK:
+ return 0;
+ case SL_FS_FILE_NAME_EXIST:
+ return EEXIST;
+ case SL_FS_WRONG_FILE_NAME:
+ return EINVAL;
+ case SL_FS_ERR_NO_AVAILABLE_NV_INDEX:
+ case SL_FS_ERR_NO_AVAILABLE_BLOCKS:
+ return ENOSPC;
+ case SL_FS_ERR_FAILED_TO_ALLOCATE_MEM:
+ return ENOMEM;
+ case SL_FS_ERR_FILE_NOT_EXISTS:
+ return ENOENT;
+ case SL_FS_ERR_NOT_SUPPORTED:
+ return ENOTSUP;
+ }
+ return ENXIO;
+}
+
+int fs_slfs_open(const char *pathname, int flags, mode_t mode) {
+ int fd;
+ for (fd = 0; fd < MAX_OPEN_SLFS_FILES; fd++) {
+ if (s_sl_fds[fd].fh <= 0) break;
+ }
+ if (fd >= MAX_OPEN_SLFS_FILES) return set_errno(ENOMEM);
+ struct sl_fd_info *fi = &s_sl_fds[fd];
+
+ _u32 am = 0;
+ fi->size = (size_t) -1;
+ if (pathname[0] == '/') pathname++;
+ int rw = (flags & 3);
+ if (rw == O_RDONLY) {
+ SlFsFileInfo_t sl_fi;
+ _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi);
+ if (r == SL_FS_OK) {
+ fi->size = sl_fi.FileLen;
+ }
+ am = FS_MODE_OPEN_READ;
+ } else {
+ if (!(flags & O_TRUNC) || (flags & O_APPEND)) {
+ // FailFS files cannot be opened for append and will be truncated
+ // when opened for write.
+ return set_errno(ENOTSUP);
+ }
+ if (flags & O_CREAT) {
+ size_t i, size = FS_SLFS_MAX_FILE_SIZE;
+ for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {
+ if (s_sl_file_size_hints[i].name != NULL &&
+ strcmp(s_sl_file_size_hints[i].name, pathname) == 0) {
+ size = s_sl_file_size_hints[i].size;
+ free(s_sl_file_size_hints[i].name);
+ s_sl_file_size_hints[i].name = NULL;
+ break;
+ }
+ }
+ DBG(("creating %s with max size %d", pathname, (int) size));
+ am = FS_MODE_OPEN_CREATE(size, 0);
+ } else {
+ am = FS_MODE_OPEN_WRITE;
+ }
+ }
+ _i32 r = sl_FsOpen((_u8 *) pathname, am, NULL, &fi->fh);
+ DBG(("sl_FsOpen(%s, 0x%x) = %d, %d", pathname, (int) am, (int) r,
+ (int) fi->fh));
+ if (r == SL_FS_OK) {
+ fi->pos = 0;
+ r = fd;
+ } else {
+ fi->fh = -1;
+ r = set_errno(sl_fs_to_errno(r));
+ }
+ return r;
+}
+
+int fs_slfs_close(int fd) {
+ struct sl_fd_info *fi = &s_sl_fds[fd];
+ if (fi->fh <= 0) return set_errno(EBADF);
+ _i32 r = sl_FsClose(fi->fh, NULL, NULL, 0);
+ DBG(("sl_FsClose(%d) = %d", (int) fi->fh, (int) r));
+ s_sl_fds[fd].fh = -1;
+ return set_errno(sl_fs_to_errno(r));
+}
+
+ssize_t fs_slfs_read(int fd, void *buf, size_t count) {
+ struct sl_fd_info *fi = &s_sl_fds[fd];
+ if (fi->fh <= 0) return set_errno(EBADF);
+ /* Simulate EOF. sl_FsRead @ file_size return SL_FS_ERR_OFFSET_OUT_OF_RANGE.
+ */
+ if (fi->pos == fi->size) return 0;
+ _i32 r = sl_FsRead(fi->fh, fi->pos, buf, count);
+ DBG(("sl_FsRead(%d, %d, %d) = %d", (int) fi->fh, (int) fi->pos, (int) count,
+ (int) r));
+ if (r >= 0) {
+ fi->pos += r;
+ return r;
+ }
+ return set_errno(sl_fs_to_errno(r));
+}
+
+ssize_t fs_slfs_write(int fd, const void *buf, size_t count) {
+ struct sl_fd_info *fi = &s_sl_fds[fd];
+ if (fi->fh <= 0) return set_errno(EBADF);
+ _i32 r = sl_FsWrite(fi->fh, fi->pos, (_u8 *) buf, count);
+ DBG(("sl_FsWrite(%d, %d, %d) = %d", (int) fi->fh, (int) fi->pos, (int) count,
+ (int) r));
+ if (r >= 0) {
+ fi->pos += r;
+ return r;
+ }
+ return set_errno(sl_fs_to_errno(r));
+}
+
+int fs_slfs_stat(const char *pathname, struct stat *s) {
+ SlFsFileInfo_t sl_fi;
+ _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi);
+ if (r == SL_FS_OK) {
+ s->st_mode = S_IFREG | 0666;
+ s->st_nlink = 1;
+ s->st_size = sl_fi.FileLen;
+ return 0;
+ }
+ return set_errno(sl_fs_to_errno(r));
+}
+
+int fs_slfs_fstat(int fd, struct stat *s) {
+ struct sl_fd_info *fi = &s_sl_fds[fd];
+ if (fi->fh <= 0) return set_errno(EBADF);
+ s->st_mode = 0666;
+ s->st_mode = S_IFREG | 0666;
+ s->st_nlink = 1;
+ s->st_size = fi->size;
+ return 0;
+}
+
+off_t fs_slfs_lseek(int fd, off_t offset, int whence) {
+ if (s_sl_fds[fd].fh <= 0) return set_errno(EBADF);
+ switch (whence) {
+ case SEEK_SET:
+ s_sl_fds[fd].pos = offset;
+ break;
+ case SEEK_CUR:
+ s_sl_fds[fd].pos += offset;
+ break;
+ case SEEK_END:
+ return set_errno(ENOTSUP);
+ }
+ return 0;
+}
+
+int fs_slfs_unlink(const char *filename) {
+ return set_errno(sl_fs_to_errno(sl_FsDel((const _u8 *) filename, 0)));
+}
+
+int fs_slfs_rename(const char *from, const char *to) {
+ return set_errno(ENOTSUP);
+}
+
+void fs_slfs_set_new_file_size(const char *name, size_t size) {
+ int i;
+ for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {
+ if (s_sl_file_size_hints[i].name == NULL) {
+ DBG(("File size hint: %s %d", name, (int) size));
+ s_sl_file_size_hints[i].name = strdup(name);
+ s_sl_file_size_hints[i].size = size;
+ break;
+ }
+ }
+}
+
+#endif /* defined(MG_FS_SLFS) || defined(CC3200_FS_SLFS) */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/platforms/simplelink/sl_fs.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#if defined(MG_SOCKET_SIMPLELINK) && \
+ (defined(MG_FS_SLFS) || defined(MG_FS_SPIFFS))
+
+#include
+#include
+#include
+#include
+#ifdef __TI_COMPILER_VERSION__
+#include
+#endif
+
+#if CS_PLATFORM == CS_P_CC3200
+#include
+#include
+#include
+#include
+#include
+#endif
+
+/* Amalgamated: #include "common/cs_dbg.h" */
+/* Amalgamated: #include "common/platform.h" */
+
+#ifdef CC3200_FS_SPIFFS
+/* Amalgamated: #include "cc3200_fs_spiffs.h" */
+#endif
+
+#ifdef MG_FS_SLFS
+/* Amalgamated: #include "sl_fs_slfs.h" */
+#endif
+
+#define NUM_SYS_FDS 3
+#define SPIFFS_FD_BASE 10
+#define SLFS_FD_BASE 100
+
+#define CONSOLE_UART UARTA0_BASE
+
+int set_errno(int e) {
+ errno = e;
+ return -e;
+}
+
+static int is_sl_fname(const char *fname) {
+ return strncmp(fname, "SL:", 3) == 0;
+}
+
+static const char *sl_fname(const char *fname) {
+ return fname + 3;
+}
+
+static const char *drop_dir(const char *fname) {
+ if (*fname == '.') fname++;
+ if (*fname == '/') fname++;
+ return fname;
+}
+
+enum fd_type {
+ FD_INVALID,
+ FD_SYS,
+#ifdef CC3200_FS_SPIFFS
+ FD_SPIFFS,
+#endif
+#ifdef MG_FS_SLFS
+ FD_SLFS
+#endif
+};
+static int fd_type(int fd) {
+ if (fd >= 0 && fd < NUM_SYS_FDS) return FD_SYS;
+#ifdef CC3200_FS_SPIFFS
+ if (fd >= SPIFFS_FD_BASE && fd < SPIFFS_FD_BASE + MAX_OPEN_SPIFFS_FILES) {
+ return FD_SPIFFS;
+ }
+#endif
+#ifdef MG_FS_SLFS
+ if (fd >= SLFS_FD_BASE && fd < SLFS_FD_BASE + MAX_OPEN_SLFS_FILES) {
+ return FD_SLFS;
+ }
+#endif
+ return FD_INVALID;
+}
+
+int _open(const char *pathname, int flags, mode_t mode) {
+ int fd = -1;
+ pathname = drop_dir(pathname);
+ if (is_sl_fname(pathname)) {
+#ifdef MG_FS_SLFS
+ fd = fs_slfs_open(sl_fname(pathname), flags, mode);
+ if (fd >= 0) fd += SLFS_FD_BASE;
+#endif
+ } else {
+#ifdef CC3200_FS_SPIFFS
+ fd = fs_spiffs_open(pathname, flags, mode);
+ if (fd >= 0) fd += SPIFFS_FD_BASE;
+#endif
+ }
+ DBG(("open(%s, 0x%x) = %d", pathname, flags, fd));
+ return fd;
+}
+
+int _stat(const char *pathname, struct stat *st) {
+ int res = -1;
+ const char *fname = pathname;
+ int is_sl = is_sl_fname(pathname);
+ if (is_sl) fname = sl_fname(pathname);
+ fname = drop_dir(fname);
+ memset(st, 0, sizeof(*st));
+ /* Simulate statting the root directory. */
+ if (strcmp(fname, "") == 0) {
+ st->st_ino = 0;
+ st->st_mode = S_IFDIR | 0777;
+ st->st_nlink = 1;
+ st->st_size = 0;
+ return 0;
+ }
+ if (is_sl) {
+#ifdef MG_FS_SLFS
+ res = fs_slfs_stat(fname, st);
+#endif
+ } else {
+#ifdef CC3200_FS_SPIFFS
+ res = fs_spiffs_stat(fname, st);
+#endif
+ }
+ DBG(("stat(%s) = %d; fname = %s", pathname, res, fname));
+ return res;
+}
+
+int _close(int fd) {
+ int r = -1;
+ switch (fd_type(fd)) {
+ case FD_INVALID:
+ r = set_errno(EBADF);
+ break;
+ case FD_SYS:
+ r = set_errno(EACCES);
+ break;
+#ifdef CC3200_FS_SPIFFS
+ case FD_SPIFFS:
+ r = fs_spiffs_close(fd - SPIFFS_FD_BASE);
+ break;
+#endif
+#ifdef MG_FS_SLFS
+ case FD_SLFS:
+ r = fs_slfs_close(fd - SLFS_FD_BASE);
+ break;
+#endif
+ }
+ DBG(("close(%d) = %d", fd, r));
+ return r;
+}
+
+off_t _lseek(int fd, off_t offset, int whence) {
+ int r = -1;
+ switch (fd_type(fd)) {
+ case FD_INVALID:
+ r = set_errno(EBADF);
+ break;
+ case FD_SYS:
+ r = set_errno(ESPIPE);
+ break;
+#ifdef CC3200_FS_SPIFFS
+ case FD_SPIFFS:
+ r = fs_spiffs_lseek(fd - SPIFFS_FD_BASE, offset, whence);
+ break;
+#endif
+#ifdef MG_FS_SLFS
+ case FD_SLFS:
+ r = fs_slfs_lseek(fd - SLFS_FD_BASE, offset, whence);
+ break;
+#endif
+ }
+ DBG(("lseek(%d, %d, %d) = %d", fd, (int) offset, whence, r));
+ return r;
+}
+
+int _fstat(int fd, struct stat *s) {
+ int r = -1;
+ memset(s, 0, sizeof(*s));
+ switch (fd_type(fd)) {
+ case FD_INVALID:
+ r = set_errno(EBADF);
+ break;
+ case FD_SYS: {
+ /* Create barely passable stats for STD{IN,OUT,ERR}. */
+ memset(s, 0, sizeof(*s));
+ s->st_ino = fd;
+ s->st_mode = S_IFCHR | 0666;
+ r = 0;
+ break;
+ }
+#ifdef CC3200_FS_SPIFFS
+ case FD_SPIFFS:
+ r = fs_spiffs_fstat(fd - SPIFFS_FD_BASE, s);
+ break;
+#endif
+#ifdef MG_FS_SLFS
+ case FD_SLFS:
+ r = fs_slfs_fstat(fd - SLFS_FD_BASE, s);
+ break;
+#endif
+ }
+ DBG(("fstat(%d) = %d", fd, r));
+ return r;
+}
+
+ssize_t _read(int fd, void *buf, size_t count) {
+ int r = -1;
+ switch (fd_type(fd)) {
+ case FD_INVALID:
+ r = set_errno(EBADF);
+ break;
+ case FD_SYS: {
+ if (fd != 0) {
+ r = set_errno(EACCES);
+ break;
+ }
+ /* Should we allow reading from stdin = uart? */
+ r = set_errno(ENOTSUP);
+ break;
+ }
+#ifdef CC3200_FS_SPIFFS
+ case FD_SPIFFS:
+ r = fs_spiffs_read(fd - SPIFFS_FD_BASE, buf, count);
+ break;
+#endif
+#ifdef MG_FS_SLFS
+ case FD_SLFS:
+ r = fs_slfs_read(fd - SLFS_FD_BASE, buf, count);
+ break;
+#endif
+ }
+ DBG(("read(%d, %u) = %d", fd, count, r));
+ return r;
+}
+
+ssize_t _write(int fd, const void *buf, size_t count) {
+ int r = -1;
+ size_t i = 0;
+ switch (fd_type(fd)) {
+ case FD_INVALID:
+ r = set_errno(EBADF);
+ break;
+ case FD_SYS: {
+ if (fd == 0) {
+ r = set_errno(EACCES);
+ break;
+ }
+#if CS_PLATFORM == CS_P_CC3200
+ for (i = 0; i < count; i++) {
+ const char c = ((const char *) buf)[i];
+ if (c == '\n') MAP_UARTCharPut(CONSOLE_UART, '\r');
+ MAP_UARTCharPut(CONSOLE_UART, c);
+ }
+#else
+ (void) i;
+#endif
+ r = count;
+ break;
+ }
+#ifdef CC3200_FS_SPIFFS
+ case FD_SPIFFS:
+ r = fs_spiffs_write(fd - SPIFFS_FD_BASE, buf, count);
+ break;
+#endif
+#ifdef MG_FS_SLFS
+ case FD_SLFS:
+ r = fs_slfs_write(fd - SLFS_FD_BASE, buf, count);
+ break;
+#endif
+ }
+ return r;
+}
+
+int _rename(const char *from, const char *to) {
+ int r = -1;
+ from = drop_dir(from);
+ to = drop_dir(to);
+ if (is_sl_fname(from) || is_sl_fname(to)) {
+#ifdef MG_FS_SLFS
+ r = fs_slfs_rename(sl_fname(from), sl_fname(to));
+#endif
+ } else {
+#ifdef CC3200_FS_SPIFFS
+ r = fs_spiffs_rename(from, to);
+#endif
+ }
+ DBG(("rename(%s, %s) = %d", from, to, r));
+ return r;
+}
+
+int _link(const char *from, const char *to) {
+ DBG(("link(%s, %s)", from, to));
+ return set_errno(ENOTSUP);
+}
+
+int _unlink(const char *filename) {
+ int r = -1;
+ filename = drop_dir(filename);
+ if (is_sl_fname(filename)) {
+#ifdef MG_FS_SLFS
+ r = fs_slfs_unlink(sl_fname(filename));
+#endif
+ } else {
+#ifdef CC3200_FS_SPIFFS
+ r = fs_spiffs_unlink(filename);
+#endif
+ }
+ DBG(("unlink(%s) = %d", filename, r));
+ return r;
+}
+
+#ifdef CC3200_FS_SPIFFS /* FailFS does not support listing files. */
+DIR *opendir(const char *dir_name) {
+ DIR *r = NULL;
+ if (is_sl_fname(dir_name)) {
+ r = NULL;
+ set_errno(ENOTSUP);
+ } else {
+ r = fs_spiffs_opendir(dir_name);
+ }
+ DBG(("opendir(%s) = %p", dir_name, r));
+ return r;
+}
+
+struct dirent *readdir(DIR *dir) {
+ struct dirent *res = fs_spiffs_readdir(dir);
+ DBG(("readdir(%p) = %p", dir, res));
+ return res;
+}
+
+int closedir(DIR *dir) {
+ int res = fs_spiffs_closedir(dir);
+ DBG(("closedir(%p) = %d", dir, res));
+ return res;
+}
+
+int rmdir(const char *path) {
+ return fs_spiffs_rmdir(path);
+}
+
+int mkdir(const char *path, mode_t mode) {
+ (void) path;
+ (void) mode;
+ /* for spiffs supports only root dir, which comes from mongoose as '.' */
+ return (strlen(path) == 1 && *path == '.') ? 0 : ENOTDIR;
+}
+#endif
+
+int sl_fs_init() {
+ int ret = 1;
+#ifdef __TI_COMPILER_VERSION__
+#ifdef MG_FS_SLFS
+#pragma diag_push
+#pragma diag_suppress 169 /* Nothing we can do about the prototype mismatch. */
+ ret = (add_device("SL", _MSA, fs_slfs_open, fs_slfs_close, fs_slfs_read,
+ fs_slfs_write, fs_slfs_lseek, fs_slfs_unlink,
+ fs_slfs_rename) == 0);
+#pragma diag_pop
+#endif
+#endif
+ return ret;
+}
+
+#endif /* defined(MG_SOCKET_SIMPLELINK) && (defined(MG_FS_SLFS) || \
+ defined(MG_FS_SPIFFS)) */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/platforms/simplelink/sl_socket.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifdef MG_SOCKET_SIMPLELINK
+
+#include
+#include
+
+/* Amalgamated: #include "common/platform.h" */
+
+#include
+
+const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) {
+ int res;
+ struct in_addr *in = (struct in_addr *) src;
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return NULL;
+ }
+ res = snprintf(dst, size, "%lu.%lu.%lu.%lu", SL_IPV4_BYTE(in->s_addr, 0),
+ SL_IPV4_BYTE(in->s_addr, 1), SL_IPV4_BYTE(in->s_addr, 2),
+ SL_IPV4_BYTE(in->s_addr, 3));
+ return res > 0 ? dst : NULL;
+}
+
+char *inet_ntoa(struct in_addr n) {
+ static char a[16];
+ return (char *) inet_ntop(AF_INET, &n, a, sizeof(n));
+}
+
+int inet_pton(int af, const char *src, void *dst) {
+ uint32_t a0, a1, a2, a3;
+ uint8_t *db = (uint8_t *) dst;
+ if (af != AF_INET) {
+ errno = EAFNOSUPPORT;
+ return 0;
+ }
+ if (sscanf(src, "%lu.%lu.%lu.%lu", &a0, &a1, &a2, &a3) != 4) {
+ return 0;
+ }
+ *db = a3;
+ *(db + 1) = a2;
+ *(db + 2) = a1;
+ *(db + 3) = a0;
+ return 1;
+}
+
+#endif /* CS_COMMON_PLATFORMS_SIMPLELINK_SL_SOCKET_C_ */
+#ifdef MG_MODULE_LINES
+#line 1 "./src/../../common/platforms/simplelink/sl_mg_task.c"
+#endif
+#if defined(MG_SOCKET_SIMPLELINK)
+
+/* Amalgamated: #include "mg_task.h" */
+
+#include
+
+enum mg_q_msg_type {
+ MG_Q_MSG_CB,
+};
+struct mg_q_msg {
+ enum mg_q_msg_type type;
+ void (*cb)(struct mg_mgr *mgr, void *arg);
+ void *arg;
+};
+static OsiMsgQ_t s_mg_q;
+static void mg_task(void *arg);
+
+bool mg_start_task(int priority, int stack_size, mg_init_cb mg_init) {
+ if (osi_MsgQCreate(&s_mg_q, "MG", sizeof(struct mg_q_msg), 16) != OSI_OK) {
+ return false;
+ }
+ if (osi_TaskCreate(mg_task, (const signed char *) "MG", stack_size,
+ (void *) mg_init, priority, NULL) != OSI_OK) {
+ return false;
+ }
+ return true;
+}
+
+static void mg_task(void *arg) {
+ struct mg_mgr mgr;
+ mg_init_cb mg_init = (mg_init_cb) arg;
+ mg_mgr_init(&mgr, NULL);
+ mg_init(&mgr);
+ while (1) {
+ struct mg_q_msg msg;
+ mg_mgr_poll(&mgr, 1);
+ if (osi_MsgQRead(&s_mg_q, &msg, 1) != OSI_OK) continue;
+ switch (msg.type) {
+ case MG_Q_MSG_CB: {
+ msg.cb(&mgr, msg.arg);
+ }
+ }
+ }
+}
+
+void mg_run_in_task(void (*cb)(struct mg_mgr *mgr, void *arg), void *cb_arg) {
+ struct mg_q_msg msg = {MG_Q_MSG_CB, cb, cb_arg};
+ osi_MsgQWrite(&s_mg_q, &msg, OSI_NO_WAIT);
+}
+
+#endif /* defined(MG_SOCKET_SIMPLELINK) */
diff --git a/include/mongoose.h b/include/mongoose.h
new file mode 100644
index 00000000..a1a35f3a
--- /dev/null
+++ b/include/mongoose.h
@@ -0,0 +1,3650 @@
+/*
+ * Copyright (c) 2004-2013 Sergey Lyubka
+ * Copyright (c) 2013-2015 Cesanta Software Limited
+ * All rights reserved
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in .
+ */
+
+#ifndef CS_MONGOOSE_SRC_COMMON_H_
+#define CS_MONGOOSE_SRC_COMMON_H_
+
+#define MG_VERSION "6.4"
+
+/* Local tweaks, applied before any of Mongoose's own headers. */
+#ifdef MG_LOCALS
+#include
+#endif
+
+#if defined(MG_ENABLE_DEBUG) && !defined(CS_ENABLE_DEBUG)
+#define CS_ENABLE_DEBUG
+#endif
+#if defined(MG_DISABLE_STDIO) && !defined(CS_DISABLE_STDIO)
+#define CS_DISABLE_STDIO
+#elif defined(CS_DISABLE_STDIO) && !defined(MG_DISABLE_STDIO)
+#define MG_DISABLE_STDIO
+#endif
+
+/* All of the below features depend on filesystem access, disable them. */
+#ifdef MG_DISABLE_FILESYSTEM
+#ifndef MG_DISABLE_DAV
+#define MG_DISABLE_DAV
+#endif
+#ifndef MG_DISABLE_CGI
+#define MG_DISABLE_CGI
+#endif
+#ifndef MG_DISABLE_DIRECTORY_LISTING
+#define MG_DISABLE_DIRECTORY_LISTING
+#endif
+#ifndef MG_DISABLE_DAV
+#define MG_DISABLE_DAV
+#endif
+#endif /* MG_DISABLE_FILESYSTEM */
+
+#ifdef MG_NO_BSD_SOCKETS
+#ifndef MG_DISABLE_SYNC_RESOLVER
+#define MG_DISABLE_SYNC_RESOLVER
+#endif
+#ifndef MG_DISABLE_SOCKETPAIR
+#define MG_DISABLE_SOCKETPAIR
+#endif
+#endif /* MG_NO_BSD_SOCKETS */
+
+
+#endif /* CS_MONGOOSE_SRC_COMMON_H_ */
+#ifndef CS_COMMON_PLATFORM_H_
+#define CS_COMMON_PLATFORM_H_
+
+/*
+ * For the "custom" platform, includes and dependencies can be
+ * provided through mg_locals.h.
+ */
+#define CS_P_CUSTOM 0
+#define CS_P_UNIX 1
+#define CS_P_WINDOWS 2
+#define CS_P_ESP_LWIP 3
+#define CS_P_CC3200 4
+#define CS_P_MSP432 5
+
+/* If not specified explicitly, we guess platform by defines. */
+#ifndef CS_PLATFORM
+
+#if defined(TARGET_IS_MSP432P4XX) || defined(__MSP432P401R__)
+
+#define CS_PLATFORM CS_P_MSP432
+#elif defined(cc3200)
+#define CS_PLATFORM CS_P_CC3200
+#elif defined(__unix__) || defined(__APPLE__)
+#define CS_PLATFORM CS_P_UNIX
+#elif defined(_WIN32)
+#define CS_PLATFORM CS_P_WINDOWS
+#endif
+
+#ifndef CS_PLATFORM
+#error "CS_PLATFORM is not specified and we couldn't guess it."
+#endif
+
+#endif /* !defined(CS_PLATFORM) */
+
+
+/* Common stuff */
+
+#ifdef __GNUC__
+#define NORETURN __attribute__((noreturn))
+#define NOINLINE __attribute__((noinline))
+#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+#define NORETURN
+#define NOINLINE
+#define WARN_UNUSED_RESULT
+#endif /* __GNUC__ */
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
+#endif
+
+#endif /* CS_COMMON_PLATFORM_H_ */
+#ifndef CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_
+#define CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_
+#if CS_PLATFORM == CS_P_WINDOWS
+
+/*
+ * MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
+ * MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
+ * MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
+ * MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
+ * MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008)
+ * MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005)
+ * MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio 2003)
+ * MSVC++ 7.0 _MSC_VER == 1300
+ * MSVC++ 6.0 _MSC_VER == 1200
+ * MSVC++ 5.0 _MSC_VER == 1100
+ */
+#ifdef _MSC_VER
+#pragma warning(disable : 4127) /* FD_SET() emits warning, disable it */
+#pragma warning(disable : 4204) /* missing c99 support */
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define random() rand()
+#ifdef _MSC_VER
+#pragma comment(lib, "ws2_32.lib") /* Linking with winsock library */
+#endif
+
+#include
+#include
+#include
+#include
+
+#ifndef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#endif
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#endif
+#ifndef __func__
+#define STRX(x) #x
+#define STR(x) STRX(x)
+#define __func__ __FILE__ ":" STR(__LINE__)
+#endif
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+#define snprintf _snprintf
+#define vsnprintf _vsnprintf
+#endif
+#define fileno _fileno
+#define sleep(x) Sleep((x) *1000)
+#define to64(x) _atoi64(x)
+#if !defined(__MINGW32__) && !defined(__MINGW64__)
+#define popen(x, y) _popen((x), (y))
+#define pclose(x) _pclose(x)
+#endif
+#define rmdir _rmdir
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+#define fseeko(x, y, z) _fseeki64((x), (y), (z))
+#else
+#define fseeko(x, y, z) fseek((x), (y), (z))
+#endif
+#define random() rand()
+typedef int socklen_t;
+#if _MSC_VER >= 1700
+#include
+#else
+typedef signed char int8_t;
+typedef unsigned char uint8_t;
+typedef int int32_t;
+typedef unsigned int uint32_t;
+typedef short int16_t;
+typedef unsigned short uint16_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#endif
+typedef SOCKET sock_t;
+typedef uint32_t in_addr_t;
+#ifndef UINT16_MAX
+#define UINT16_MAX 65535
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX 4294967295
+#endif
+#ifndef pid_t
+#define pid_t HANDLE
+#endif
+#define INT64_FMT "I64d"
+#define INT64_X_FMT "I64x"
+#define SIZE_T_FMT "Iu"
+#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
+typedef struct stat cs_stat_t;
+#else
+typedef struct _stati64 cs_stat_t;
+#endif
+#ifndef S_ISDIR
+#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)
+#endif
+#ifndef S_ISREG
+#define S_ISREG(x) (((x) &_S_IFMT) == _S_IFREG)
+#endif
+#define DIRSEP '\\'
+
+#ifndef va_copy
+#ifdef __va_copy
+#define va_copy __va_copy
+#else
+#define va_copy(x, y) (x) = (y)
+#endif
+#endif
+
+#ifndef MG_MAX_HTTP_REQUEST_SIZE
+#define MG_MAX_HTTP_REQUEST_SIZE 8192
+#endif
+
+#ifndef MG_MAX_HTTP_SEND_MBUF
+#define MG_MAX_HTTP_SEND_MBUF 4096
+#endif
+
+#ifndef MG_MAX_HTTP_HEADERS
+#define MG_MAX_HTTP_HEADERS 40
+#endif
+
+#endif /* CS_PLATFORM == CS_P_WINDOWS */
+#endif /* CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_ */
+#ifndef CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_
+#define CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_
+#if CS_PLATFORM == CS_P_UNIX
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 600
+#endif
+
+/* wants this for C++ */
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
+/* C++ wants that for INT64_MAX */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS
+#endif
+
+/* Enable fseeko() and ftello() functions */
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+
+/* Enable 64-bit file offsets */
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 64
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * osx correctly avoids defining strtoll when compiling in strict ansi mode.
+ * We require strtoll, and if your embedded pre-c99 compiler lacks one, please
+ * implement a shim.
+ */
+#if !(defined(__DARWIN_C_LEVEL) && __DARWIN_C_LEVEL >= 200809L)
+long long strtoll(const char *, char **, int);
+#endif
+
+typedef int sock_t;
+#define INVALID_SOCKET (-1)
+#define SIZE_T_FMT "zu"
+typedef struct stat cs_stat_t;
+#define DIRSEP '/'
+#define to64(x) strtoll(x, NULL, 10)
+#define INT64_FMT PRId64
+#define INT64_X_FMT PRIx64
+
+#ifndef __cdecl
+#define __cdecl
+#endif
+
+#ifndef va_copy
+#ifdef __va_copy
+#define va_copy __va_copy
+#else
+#define va_copy(x, y) (x) = (y)
+#endif
+#endif
+
+#define closesocket(x) close(x)
+
+#ifndef MG_MAX_HTTP_REQUEST_SIZE
+#define MG_MAX_HTTP_REQUEST_SIZE 8192
+#endif
+
+#ifndef MG_MAX_HTTP_SEND_MBUF
+#define MG_MAX_HTTP_SEND_MBUF 4096
+#endif
+
+#ifndef MG_MAX_HTTP_HEADERS
+#define MG_MAX_HTTP_HEADERS 40
+#endif
+
+#endif /* CS_PLATFORM == CS_P_UNIX */
+#endif /* CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_ */
+#ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP_LWIP_H_
+#define CS_COMMON_PLATFORMS_PLATFORM_ESP_LWIP_H_
+#if CS_PLATFORM == CS_P_ESP_LWIP
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#ifndef LWIP_PROVIDE_ERRNO
+#include
+#endif
+
+#define LWIP_TIMEVAL_PRIVATE 0
+
+#if LWIP_SOCKET
+#include
+#define SOMAXCONN 10
+#else
+/* We really need the definitions from sockets.h. */
+#undef LWIP_SOCKET
+#define LWIP_SOCKET 1
+#include
+#undef LWIP_SOCKET
+#define LWIP_SOCKET 0
+#endif
+
+typedef int sock_t;
+#define INVALID_SOCKET (-1)
+#define SIZE_T_FMT "u"
+typedef struct stat cs_stat_t;
+#define DIRSEP '/'
+#define to64(x) strtoll(x, NULL, 10)
+#define INT64_FMT PRId64
+#define INT64_X_FMT PRIx64
+#define __cdecl
+
+unsigned long os_random(void);
+#define random os_random
+
+#endif /* CS_PLATFORM == CS_P_ESP_LWIP */
+#endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP_LWIP_H_ */
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_
+#define CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_
+#if CS_PLATFORM == CS_P_CC3200
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef __TI_COMPILER_VERSION__
+#include
+#include
+#endif
+
+#define MG_SOCKET_SIMPLELINK 1
+#define MG_DISABLE_SOCKETPAIR 1
+#define MG_DISABLE_SYNC_RESOLVER 1
+#define MG_DISABLE_POPEN 1
+#define MG_DISABLE_CGI 1
+/* Only SPIFFS supports directories, SLFS does not. */
+#ifndef CC3200_FS_SPIFFS
+#define MG_DISABLE_DAV 1
+#define MG_DISABLE_DIRECTORY_LISTING 1
+#endif
+
+
+typedef int sock_t;
+#define INVALID_SOCKET (-1)
+#define SIZE_T_FMT "u"
+typedef struct stat cs_stat_t;
+#define DIRSEP '/'
+#define to64(x) strtoll(x, NULL, 10)
+#define INT64_FMT PRId64
+#define INT64_X_FMT PRIx64
+#define __cdecl
+
+#define fileno(x) -1
+
+/* Some functions we implement for Mongoose. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __TI_COMPILER_VERSION__
+struct SlTimeval_t;
+#define timeval SlTimeval_t
+int gettimeofday(struct timeval *t, void *tz);
+
+int asprintf(char **strp, const char *fmt, ...);
+
+#endif
+
+long int random(void);
+
+/* TI's libc does not have stat & friends, add them. */
+#ifdef __TI_COMPILER_VERSION__
+
+#include
+
+typedef unsigned int mode_t;
+typedef size_t _off_t;
+typedef long ssize_t;
+
+struct stat {
+ int st_ino;
+ mode_t st_mode;
+ int st_nlink;
+ time_t st_mtime;
+ off_t st_size;
+};
+
+int _stat(const char *pathname, struct stat *st);
+#define stat(a, b) _stat(a, b)
+
+#define __S_IFMT 0170000
+
+#define __S_IFDIR 0040000
+#define __S_IFCHR 0020000
+#define __S_IFREG 0100000
+
+#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask))
+
+#define S_IFDIR __S_IFDIR
+#define S_IFCHR __S_IFCHR
+#define S_IFREG __S_IFREG
+#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
+#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
+
+/* As of 5.2.7, TI compiler does not support va_copy() yet. */
+#define va_copy(apc, ap) ((apc) = (ap))
+
+#endif /* __TI_COMPILER_VERSION__ */
+
+#ifdef CC3200_FS_SPIFFS
+#include
+
+typedef struct {
+ spiffs_DIR dh;
+ struct spiffs_dirent de;
+} DIR;
+
+#define d_name name
+#define dirent spiffs_dirent
+
+DIR *opendir(const char *dir_name);
+int closedir(DIR *dir);
+struct dirent *readdir(DIR *dir);
+#endif /* CC3200_FS_SPIFFS */
+
+#ifdef CC3200_FS_SLFS
+#define MG_FS_SLFS
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CS_PLATFORM == CS_P_CC3200 */
+#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_
+#define CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_
+#if CS_PLATFORM == CS_P_MSP432
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifndef __TI_COMPILER_VERSION__
+#include
+#include
+#endif
+
+#define MG_SOCKET_SIMPLELINK 1
+#define MG_DISABLE_SOCKETPAIR 1
+#define MG_DISABLE_SYNC_RESOLVER 1
+#define MG_DISABLE_POPEN 1
+#define MG_DISABLE_CGI 1
+#define MG_DISABLE_DAV 1
+#define MG_DISABLE_DIRECTORY_LISTING 1
+
+
+typedef int sock_t;
+#define INVALID_SOCKET (-1)
+#define SIZE_T_FMT "u"
+typedef struct stat cs_stat_t;
+#define DIRSEP '/'
+#define to64(x) strtoll(x, NULL, 10)
+#define INT64_FMT PRId64
+#define INT64_X_FMT PRIx64
+#define __cdecl
+
+#define fileno(x) -1
+
+/* Some functions we implement for Mongoose. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef __TI_COMPILER_VERSION__
+struct SlTimeval_t;
+#define timeval SlTimeval_t
+int gettimeofday(struct timeval *t, void *tz);
+#endif
+
+long int random(void);
+
+/* TI's libc does not have stat & friends, add them. */
+#ifdef __TI_COMPILER_VERSION__
+
+#include
+
+typedef unsigned int mode_t;
+typedef size_t _off_t;
+typedef long ssize_t;
+
+struct stat {
+ int st_ino;
+ mode_t st_mode;
+ int st_nlink;
+ time_t st_mtime;
+ off_t st_size;
+};
+
+int _stat(const char *pathname, struct stat *st);
+#define stat(a, b) _stat(a, b)
+
+#define __S_IFMT 0170000
+
+#define __S_IFDIR 0040000
+#define __S_IFCHR 0020000
+#define __S_IFREG 0100000
+
+#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask))
+
+#define S_IFDIR __S_IFDIR
+#define S_IFCHR __S_IFCHR
+#define S_IFREG __S_IFREG
+#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
+#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
+
+/* As of 5.2.7, TI compiler does not support va_copy() yet. */
+#define va_copy(apc, ap) ((apc) = (ap))
+
+#endif /* __TI_COMPILER_VERSION__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CS_PLATFORM == CS_P_MSP432 */
+#endif /* CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_ */
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_
+#define CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_
+
+/* If simplelink.h is already included, all bets are off. */
+#if defined(MG_SOCKET_SIMPLELINK) && !defined(__SIMPLELINK_H__)
+
+#include
+
+#ifndef __TI_COMPILER_VERSION__
+#undef __CONCAT
+#undef FD_CLR
+#undef FD_ISSET
+#undef FD_SET
+#undef FD_SETSIZE
+#undef FD_ZERO
+#undef fd_set
+#endif
+
+/* We want to disable SL_INC_STD_BSD_API_NAMING, so we include user.h ourselves
+ * and undef it. */
+#define PROVISIONING_API_H_
+#include
+#undef PROVISIONING_API_H_
+#undef SL_INC_STD_BSD_API_NAMING
+
+#include
+
+/* Now define only the subset of the BSD API that we use.
+ * Notably, close(), read() and write() are not defined. */
+#define AF_INET SL_AF_INET
+
+#define socklen_t SlSocklen_t
+#define sockaddr SlSockAddr_t
+#define sockaddr_in SlSockAddrIn_t
+#define in_addr SlInAddr_t
+
+#define SOCK_STREAM SL_SOCK_STREAM
+#define SOCK_DGRAM SL_SOCK_DGRAM
+
+#define FD_SET SL_FD_SET
+#define FD_CLR SL_FD_CLR
+#define FD_ISSET SL_FD_ISSET
+#define FD_ZERO SL_FD_ZERO
+#define fd_set SlFdSet_t
+
+#define htonl sl_Htonl
+#define ntohl sl_Ntohl
+#define htons sl_Htons
+#define ntohs sl_Ntohs
+
+#define accept sl_Accept
+#define closesocket sl_Close
+#define bind sl_Bind
+#define connect sl_Connect
+#define listen sl_Listen
+#define recv sl_Recv
+#define recvfrom sl_RecvFrom
+#define send sl_Send
+#define sendto sl_SendTo
+#define socket sl_Socket
+
+#define select(nfds, rfds, wfds, efds, tout) \
+ sl_Select((nfds), (rfds), (wfds), (efds), (struct SlTimeval_t *)(tout))
+
+#ifndef EACCES
+#define EACCES SL_EACCES
+#endif
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT SL_EAFNOSUPPORT
+#endif
+#ifndef EAGAIN
+#define EAGAIN SL_EAGAIN
+#endif
+#ifndef EBADF
+#define EBADF SL_EBADF
+#endif
+#ifndef EINVAL
+#define EINVAL SL_EINVAL
+#endif
+#ifndef ENOMEM
+#define ENOMEM SL_ENOMEM
+#endif
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK SL_EWOULDBLOCK
+#endif
+
+#define SOMAXCONN 8
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
+char *inet_ntoa(struct in_addr in);
+int inet_pton(int af, const char *src, void *dst);
+
+struct mg_mgr;
+
+typedef void (*mg_init_cb)(struct mg_mgr *mgr);
+bool mg_start_task(int priority, int stack_size, mg_init_cb mg_init);
+
+void mg_run_in_task(void (*cb)(struct mg_mgr *mgr, void *arg), void *cb_arg);
+
+int sl_fs_init();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* defined(MG_SOCKET_SIMPLELINK) && !defined(__SIMPLELINK_H__) */
+
+#endif /* CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_ */
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_CS_DBG_H_
+#define CS_COMMON_CS_DBG_H_
+
+#ifndef CS_DISABLE_STDIO
+#include
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+enum cs_log_level {
+ LL_NONE = -1,
+ LL_ERROR = 0,
+ LL_WARN = 1,
+ LL_INFO = 2,
+ LL_DEBUG = 3,
+ LL_VERBOSE_DEBUG = 4,
+
+ _LL_MIN = -2,
+ _LL_MAX = 5,
+};
+
+void cs_log_set_level(enum cs_log_level level);
+
+#ifndef CS_DISABLE_STDIO
+
+void cs_log_set_file(FILE *file);
+
+extern enum cs_log_level cs_log_level;
+void cs_log_print_prefix(const char *func);
+void cs_log_printf(const char *fmt, ...);
+
+#define LOG(l, x) \
+ if (cs_log_level >= l) { \
+ cs_log_print_prefix(__func__); \
+ cs_log_printf x; \
+ }
+
+#ifndef CS_NDEBUG
+
+#define DBG(x) \
+ if (cs_log_level >= LL_VERBOSE_DEBUG) { \
+ cs_log_print_prefix(__func__); \
+ cs_log_printf x; \
+ }
+
+#else /* NDEBUG */
+
+#define DBG(x)
+
+#endif
+
+#else /* CS_DISABLE_STDIO */
+
+#define LOG(l, x)
+#define DBG(x)
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_COMMON_CS_DBG_H_ */
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_CS_TIME_H_
+#define CS_COMMON_CS_TIME_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Sub-second granularity time(). */
+double cs_time();
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_COMMON_CS_TIME_H_ */
+/*
+ * Copyright (c) 2015 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === Memory Buffers
+ *
+ * Mbufs are mutable/growing memory buffers, like C++ strings.
+ * Mbuf can append data to the end of a buffer, or insert data into arbitrary
+ * position in the middle of a buffer. The buffer grows automatically when
+ * needed.
+ */
+
+#ifndef CS_COMMON_MBUF_H_
+#define CS_COMMON_MBUF_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#include
+
+#ifndef MBUF_SIZE_MULTIPLIER
+#define MBUF_SIZE_MULTIPLIER 1.5
+#endif
+
+/* Memory buffer descriptor */
+struct mbuf {
+ char *buf; /* Buffer pointer */
+ size_t len; /* Data length. Data is located between offset 0 and len. */
+ size_t size; /* Buffer size allocated by realloc(1). Must be >= len */
+};
+
+/*
+ * Initialize an Mbuf.
+ * `initial_capacity` specifies the initial capacity of the mbuf.
+ */
+void mbuf_init(struct mbuf *, size_t initial_capacity);
+
+/* Free the space allocated for the mbuffer and resets the mbuf structure. */
+void mbuf_free(struct mbuf *);
+
+/*
+ * Appends data to the Mbuf.
+ *
+ * Return the number of bytes appended, or 0 if out of memory.
+ */
+size_t mbuf_append(struct mbuf *, const void *data, size_t data_size);
+
+/*
+ * Insert data at a specified offset in the Mbuf.
+ *
+ * Existing data will be shifted forwards and the buffer will
+ * be grown if necessary.
+ * Return the number of bytes inserted.
+ */
+size_t mbuf_insert(struct mbuf *, size_t, const void *, size_t);
+
+/* Remove `data_size` bytes from the beginning of the buffer. */
+void mbuf_remove(struct mbuf *, size_t data_size);
+
+/*
+ * Resize an Mbuf.
+ *
+ * If `new_size` is smaller than buffer's `len`, the
+ * resize is not performed.
+ */
+void mbuf_resize(struct mbuf *, size_t new_size);
+
+/* Shrink an Mbuf by resizing its `size` to `len`. */
+void mbuf_trim(struct mbuf *);
+
+#if defined(__cplusplus)
+}
+#endif /* __cplusplus */
+
+#endif /* CS_COMMON_MBUF_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_SHA1_H_
+#define CS_COMMON_SHA1_H_
+
+#ifndef DISABLE_SHA1
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+ uint32_t state[5];
+ uint32_t count[2];
+ unsigned char buffer[64];
+} cs_sha1_ctx;
+
+void cs_sha1_init(cs_sha1_ctx *);
+void cs_sha1_update(cs_sha1_ctx *, const unsigned char *data, uint32_t len);
+void cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *);
+void cs_hmac_sha1(const unsigned char *key, size_t key_len,
+ const unsigned char *text, size_t text_len,
+ unsigned char out[20]);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* DISABLE_SHA1 */
+
+#endif /* CS_COMMON_SHA1_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_MD5_H_
+#define CS_COMMON_MD5_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct MD5Context {
+ uint32_t buf[4];
+ uint32_t bits[2];
+ unsigned char in[64];
+} MD5_CTX;
+
+void MD5_Init(MD5_CTX *c);
+void MD5_Update(MD5_CTX *c, const unsigned char *data, size_t len);
+void MD5_Final(unsigned char *md, MD5_CTX *c);
+
+/*
+ * Return stringified MD5 hash for NULL terminated list of pointer/length pairs.
+ * A length should be specified as size_t variable.
+ * Example:
+ *
+ * char buf[33];
+ * cs_md5(buf, "foo", (size_t) 3, "bar", (size_t) 3, NULL);
+ */
+char *cs_md5(char buf[33], ...);
+
+/*
+ * Stringify binary data. Output buffer size must be 2 * size_of_input + 1
+ * because each byte of input takes 2 bytes in string representation
+ * plus 1 byte for the terminating \0 character.
+ */
+void cs_to_hex(char *to, const unsigned char *p, size_t len);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_COMMON_MD5_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_BASE64_H_
+#define CS_COMMON_BASE64_H_
+
+#ifndef DISABLE_BASE64
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*cs_base64_putc_t)(char, void *);
+
+struct cs_base64_ctx {
+ /* cannot call it putc because it's a macro on some environments */
+ cs_base64_putc_t b64_putc;
+ unsigned char chunk[3];
+ int chunk_size;
+ void *user_data;
+};
+
+void cs_base64_init(struct cs_base64_ctx *ctx, cs_base64_putc_t putc,
+ void *user_data);
+void cs_base64_update(struct cs_base64_ctx *ctx, const char *str, size_t len);
+void cs_base64_finish(struct cs_base64_ctx *ctx);
+
+void cs_base64_encode(const unsigned char *src, int src_len, char *dst);
+void cs_fprint_base64(FILE *f, const unsigned char *src, int src_len);
+int cs_base64_decode(const unsigned char *s, int len, char *dst);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DISABLE_BASE64 */
+
+#endif /* CS_COMMON_BASE64_H_ */
+/*
+ * Copyright (c) 2015 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_COMMON_STR_UTIL_H_
+#define CS_COMMON_STR_UTIL_H_
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+size_t c_strnlen(const char *s, size_t maxlen);
+int c_snprintf(char *buf, size_t buf_size, const char *format, ...);
+int c_vsnprintf(char *buf, size_t buf_size, const char *format, va_list ap);
+/*
+ * Find the first occurrence of find in s, where the search is limited to the
+ * first slen characters of s.
+ */
+const char *c_strnstr(const char *s, const char *find, size_t slen);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CS_COMMON_STR_UTIL_H_ */
+/*
+ * Copyright (c) 2004-2013 Sergey Lyubka
+ * Copyright (c) 2013 Cesanta Software Limited
+ * All rights reserved
+ *
+ * This library is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this library 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 library under a commercial
+ * license, as set out in .
+ */
+
+#ifndef CS_MONGOOSE_DEPS_FROZEN_FROZEN_H_
+#define CS_MONGOOSE_DEPS_FROZEN_FROZEN_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include
+
+enum json_type {
+ JSON_TYPE_EOF = 0, /* End of parsed tokens marker */
+ JSON_TYPE_STRING = 1,
+ JSON_TYPE_NUMBER = 2,
+ JSON_TYPE_OBJECT = 3,
+ JSON_TYPE_TRUE = 4,
+ JSON_TYPE_FALSE = 5,
+ JSON_TYPE_NULL = 6,
+ JSON_TYPE_ARRAY = 7
+};
+
+struct json_token {
+ const char *ptr; /* Points to the beginning of the token */
+ int len; /* Token length */
+ int num_desc; /* For arrays and object, total number of descendants */
+ enum json_type type; /* Type of the token, possible values above */
+};
+
+/* Error codes */
+#define JSON_STRING_INVALID -1
+#define JSON_STRING_INCOMPLETE -2
+#define JSON_TOKEN_ARRAY_TOO_SMALL -3
+
+int parse_json(const char *json_string, int json_string_length,
+ struct json_token *tokens_array, int size_of_tokens_array);
+struct json_token *parse_json2(const char *json_string, int string_length);
+struct json_token *find_json_token(struct json_token *toks, const char *path);
+
+int json_emit_long(char *buf, int buf_len, long value);
+int json_emit_double(char *buf, int buf_len, double value);
+int json_emit_quoted_str(char *buf, int buf_len, const char *str, int len);
+int json_emit_unquoted_str(char *buf, int buf_len, const char *str, int len);
+int json_emit(char *buf, int buf_len, const char *fmt, ...);
+int json_emit_va(char *buf, int buf_len, const char *fmt, va_list);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_MONGOOSE_DEPS_FROZEN_FROZEN_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in .
+ */
+
+/*
+ * === Core: TCP/UDP/SSL
+ *
+ * NOTE: Mongoose manager is single threaded. It does not protect
+ * its data structures by mutexes, therefore all functions that are dealing
+ * with particular event manager should be called from the same thread,
+ * with exception of `mg_broadcast()` function. It is fine to have different
+ * event managers handled by different threads.
+ */
+
+#ifndef CS_MONGOOSE_SRC_NET_H_
+#define CS_MONGOOSE_SRC_NET_H_
+
+#ifdef MG_ENABLE_JAVASCRIPT
+#define EXCLUDE_COMMON
+#include
+#endif
+
+
+#ifdef MG_ENABLE_SSL
+#ifdef __APPLE__
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+#include
+#else
+typedef void *SSL;
+typedef void *SSL_CTX;
+#endif
+
+#ifndef MG_VPRINTF_BUFFER_SIZE
+#define MG_VPRINTF_BUFFER_SIZE 100
+#endif
+
+#ifdef MG_USE_READ_WRITE
+#define MG_RECV_FUNC(s, b, l, f) read(s, b, l)
+#define MG_SEND_FUNC(s, b, l, f) write(s, b, l)
+#else
+#define MG_RECV_FUNC(s, b, l, f) recv(s, b, l, f)
+#define MG_SEND_FUNC(s, b, l, f) send(s, b, l, f)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+union socket_address {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#ifdef MG_ENABLE_IPV6
+ struct sockaddr_in6 sin6;
+#else
+ struct sockaddr sin6;
+#endif
+};
+
+/* Describes chunk of memory */
+struct mg_str {
+ const char *p; /* Memory chunk pointer */
+ size_t len; /* Memory chunk length */
+};
+
+struct mg_connection;
+
+/*
+ * Callback function (event handler) prototype, must be defined by user.
+ * Mongoose calls event handler, passing events defined below.
+ */
+typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, void *);
+
+/* Events. Meaning of event parameter (evp) is given in the comment. */
+#define MG_EV_POLL 0 /* Sent to each connection on each mg_mgr_poll() call */
+#define MG_EV_ACCEPT 1 /* New connection accepted. union socket_address * */
+#define MG_EV_CONNECT 2 /* connect() succeeded or failed. int * */
+#define MG_EV_RECV 3 /* Data has benn received. int *num_bytes */
+#define MG_EV_SEND 4 /* Data has been written to a socket. int *num_bytes */
+#define MG_EV_CLOSE 5 /* Connection is closed. NULL */
+#define MG_EV_TIMER 6 /* now >= conn->ev_timer_time. double * */
+
+/*
+ * Mongoose event manager.
+ */
+struct mg_mgr {
+ struct mg_connection *active_connections;
+ const char *hexdump_file; /* Debug hexdump file path */
+#ifndef MG_DISABLE_SOCKETPAIR
+ sock_t ctl[2]; /* Socketpair for mg_wakeup() */
+#endif
+ void *user_data; /* User data */
+ void *mgr_data; /* Implementation-specific event manager's data. */
+#ifdef MG_ENABLE_JAVASCRIPT
+ struct v7 *v7;
+#endif
+};
+
+/*
+ * Mongoose connection.
+ */
+struct mg_connection {
+ struct mg_connection *next, *prev; /* mg_mgr::active_connections linkage */
+ struct mg_connection *listener; /* Set only for accept()-ed connections */
+ struct mg_mgr *mgr; /* Pointer to containing manager */
+
+ sock_t sock; /* Socket to the remote peer */
+ int err;
+ union socket_address sa; /* Remote peer address */
+ size_t recv_mbuf_limit; /* Max size of recv buffer */
+ struct mbuf recv_mbuf; /* Received data */
+ struct mbuf send_mbuf; /* Data scheduled for sending */
+ SSL *ssl;
+ SSL_CTX *ssl_ctx;
+ time_t last_io_time; /* Timestamp of the last socket IO */
+ double ev_timer_time; /* Timestamp of the future MG_EV_TIMER */
+ mg_event_handler_t proto_handler; /* Protocol-specific event handler */
+ void *proto_data; /* Protocol-specific data */
+ void (*proto_data_destructor)(void *proto_data);
+ mg_event_handler_t handler; /* Event handler function */
+ void *user_data; /* User-specific data */
+ union {
+ void *v;
+ /*
+ * the C standard is fussy about fitting function pointers into
+ * void pointers, since some archs might have fat pointers for functions.
+ */
+ mg_event_handler_t f;
+ } priv_1; /* Used by mg_enable_multithreading() */
+ void *priv_2; /* Used by mg_enable_multithreading() */
+ void *mgr_data; /* Implementation-specific event manager's data. */
+ unsigned long flags;
+/* Flags set by Mongoose */
+#define MG_F_LISTENING (1 << 0) /* This connection is listening */
+#define MG_F_UDP (1 << 1) /* This connection is UDP */
+#define MG_F_RESOLVING (1 << 2) /* Waiting for async resolver */
+#define MG_F_CONNECTING (1 << 3) /* connect() call in progress */
+#define MG_F_SSL_HANDSHAKE_DONE (1 << 4) /* SSL specific */
+#define MG_F_WANT_READ (1 << 5) /* SSL specific */
+#define MG_F_WANT_WRITE (1 << 6) /* SSL specific */
+#define MG_F_IS_WEBSOCKET (1 << 7) /* Websocket specific */
+
+/* Flags that are settable by user */
+#define MG_F_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */
+#define MG_F_CLOSE_IMMEDIATELY (1 << 11) /* Disconnect */
+#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */
+#define MG_F_DELETE_CHUNK (1 << 13) /* HTTP specific */
+
+#define MG_F_USER_1 (1 << 20) /* Flags left for application */
+#define MG_F_USER_2 (1 << 21)
+#define MG_F_USER_3 (1 << 22)
+#define MG_F_USER_4 (1 << 23)
+#define MG_F_USER_5 (1 << 24)
+#define MG_F_USER_6 (1 << 25)
+};
+
+/*
+ * Initialize Mongoose manager. Side effect: ignores SIGPIPE signal.
+ * `mgr->user_data` field will be initialized with `user_data` parameter.
+ * That is an arbitrary pointer, where user code can associate some data
+ * with the particular Mongoose manager. For example, a C++ wrapper class
+ * could be written, in which case `user_data` can hold a pointer to the
+ * class instance.
+ */
+void mg_mgr_init(struct mg_mgr *mgr, void *user_data);
+
+/*
+ * De-initializes Mongoose manager.
+ *
+ * Close and deallocate all active connections.
+ */
+void mg_mgr_free(struct mg_mgr *);
+
+/*
+ * This function performs the actual IO, and must be called in a loop
+ * (an event loop). Returns the current timestamp.
+ * `milli` is the maximum number of milliseconds to sleep.
+ * `mg_mgr_poll()` checks all connection for IO readiness. If at least one
+ * of the connections is IO-ready, `mg_mgr_poll()` triggers respective
+ * event handlers and returns.
+ */
+time_t mg_mgr_poll(struct mg_mgr *, int milli);
+
+#ifndef MG_DISABLE_SOCKETPAIR
+/*
+ * Pass a message of a given length to all connections.
+ *
+ * Must be called from a thread that does NOT call `mg_mgr_poll()`.
+ * Note that `mg_broadcast()` is the only function
+ * that can be, and must be, called from a different (non-IO) thread.
+ *
+ * `func` callback function will be called by the IO thread for each
+ * connection. When called, event would be `MG_EV_POLL`, and message will
+ * be passed as `ev_data` pointer. Maximum message size is capped
+ * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes.
+ */
+void mg_broadcast(struct mg_mgr *, mg_event_handler_t func, void *, size_t);
+#endif
+
+/*
+ * Iterate over all active connections.
+ *
+ * Returns next connection from the list
+ * of active connections, or `NULL` if there is no more connections. Below
+ * is the iteration idiom:
+ *
+ * ```c
+ * for (c = mg_next(srv, NULL); c != NULL; c = mg_next(srv, c)) {
+ * // Do something with connection `c`
+ * }
+ * ```
+ */
+struct mg_connection *mg_next(struct mg_mgr *, struct mg_connection *);
+
+/*
+ * Optional parameters to `mg_add_sock_opt()`.
+ *
+ * `flags` is an initial `struct mg_connection::flags` bitmask to set,
+ * see `MG_F_*` flags definitions.
+ */
+struct mg_add_sock_opts {
+ void *user_data; /* Initial value for connection's user_data */
+ unsigned int flags; /* Initial connection flags */
+ const char **error_string; /* Placeholder for the error string */
+};
+
+/*
+ * Create a connection, associate it with the given socket and event handler,
+ * and add it to the manager.
+ *
+ * For more options see the `mg_add_sock_opt` variant.
+ */
+struct mg_connection *mg_add_sock(struct mg_mgr *, sock_t, mg_event_handler_t);
+
+/*
+ * Create a connection, associate it with the given socket and event handler,
+ * and add to the manager.
+ *
+ * See the `mg_add_sock_opts` structure for a description of the options.
+ */
+struct mg_connection *mg_add_sock_opt(struct mg_mgr *, sock_t,
+ mg_event_handler_t,
+ struct mg_add_sock_opts);
+
+/*
+ * Optional parameters to `mg_bind_opt()`.
+ *
+ * `flags` is an initial `struct mg_connection::flags` bitmask to set,
+ * see `MG_F_*` flags definitions.
+ */
+struct mg_bind_opts {
+ void *user_data; /* Initial value for connection's user_data */
+ unsigned int flags; /* Extra connection flags */
+ const char **error_string; /* Placeholder for the error string */
+#ifdef MG_ENABLE_SSL
+ /* SSL settings. */
+ const char *ssl_cert; /* Server certificate to present to clients */
+ const char *ssl_ca_cert; /* Verify client certificates with this CA bundle */
+#endif
+};
+
+/*
+ * Create listening connection.
+ *
+ * See `mg_bind_opt` for full documentation.
+ */
+struct mg_connection *mg_bind(struct mg_mgr *, const char *,
+ mg_event_handler_t);
+/*
+ * Create listening connection.
+ *
+ * `address` parameter tells which address to bind to. It's format is the same
+ * as for the `mg_connect()` call, where `HOST` part is optional. `address`
+ * can be just a port number, e.g. `:8000`. To bind to a specific interface,
+ * an IP address can be specified, e.g. `1.2.3.4:8000`. By default, a TCP
+ * connection is created. To create UDP connection, prepend `udp://` prefix,
+ * e.g. `udp://:8000`. To summarize, `address` paramer has following format:
+ * `[PROTO://][IP_ADDRESS]:PORT`, where `PROTO` could be `tcp` or `udp`.
+ *
+ * See the `mg_bind_opts` structure for a description of the optional
+ * parameters.
+ *
+ * Return a new listening connection, or `NULL` on error.
+ * NOTE: Connection remains owned by the manager, do not free().
+ */
+struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,
+ mg_event_handler_t handler,
+ struct mg_bind_opts opts);
+
+/* Optional parameters to `mg_connect_opt()` */
+struct mg_connect_opts {
+ void *user_data; /* Initial value for connection's user_data */
+ unsigned int flags; /* Extra connection flags */
+ const char **error_string; /* Placeholder for the error string */
+#ifdef MG_ENABLE_SSL
+ /* SSL settings. */
+ const char *ssl_cert; /* Client certificate to present to the server */
+ const char *ssl_ca_cert; /* Verify server certificate using this CA bundle */
+
+ /*
+ * Server name verification. If ssl_ca_cert is set and the certificate has
+ * passed verification, its subject will be verified against this string.
+ * By default (if ssl_server_name is NULL) hostname part of the address will
+ * be used. Wildcard matching is supported. A special value of "*" disables
+ * name verification.
+ */
+ const char *ssl_server_name;
+#endif
+};
+
+/*
+ * Connect to a remote host.
+ *
+ * See `mg_connect_opt()` for full documentation.
+ */
+struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,
+ mg_event_handler_t handler);
+
+/*
+ * Connect to a remote host.
+ *
+ * `address` format is `[PROTO://]HOST:PORT`. `PROTO` could be `tcp` or `udp`.
+ * `HOST` could be an IP address,
+ * IPv6 address (if Mongoose is compiled with `-DMG_ENABLE_IPV6`), or a host
+ * name. If `HOST` is a name, Mongoose will resolve it asynchronously. Examples
+ * of valid addresses: `google.com:80`, `udp://1.2.3.4:53`, `10.0.0.1:443`,
+ * `[::1]:80`
+ *
+ * See the `mg_connect_opts` structure for a description of the optional
+ * parameters.
+ *
+ * Returns a new outbound connection, or `NULL` on error.
+ *
+ * NOTE: Connection remains owned by the manager, do not free().
+ *
+ * NOTE: To enable IPv6 addresses, `-DMG_ENABLE_IPV6` should be specified
+ * in the compilation flags.
+ *
+ * NOTE: New connection will receive `MG_EV_CONNECT` as it's first event
+ * which will report connect success status.
+ * If asynchronous resolution fail, or `connect()` syscall fail for whatever
+ * reason (e.g. with `ECONNREFUSED` or `ENETUNREACH`), then `MG_EV_CONNECT`
+ * event report failure. Code example below:
+ *
+ * ```c
+ * static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ * int connect_status;
+ *
+ * switch (ev) {
+ * case MG_EV_CONNECT:
+ * connect_status = * (int *) ev_data;
+ * if (connect_status == 0) {
+ * // Success
+ * } else {
+ * // Error
+ * printf("connect() error: %s\n", strerror(connect_status));
+ * }
+ * break;
+ * ...
+ * }
+ * }
+ *
+ * ...
+ * mg_connect(mgr, "my_site.com:80", ev_handler);
+ * ```
+ */
+struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,
+ mg_event_handler_t handler,
+ struct mg_connect_opts opts);
+
+/*
+ * Enable SSL for a given connection.
+ * `cert` is a server certificate file name for a listening connection,
+ * or a client certificate file name for an outgoing connection.
+ * Certificate files must be in PEM format. Server certificate file
+ * must contain a certificate, concatenated with a private key, optionally
+ * concatenated with parameters.
+ * `ca_cert` is a CA certificate, or NULL if peer verification is not
+ * required.
+ * Return: NULL on success, or error message on error.
+ */
+const char *mg_set_ssl(struct mg_connection *nc, const char *cert,
+ const char *ca_cert);
+
+/*
+ * Send data to the connection.
+ *
+ * Note that sending functions do not actually push data to the socket.
+ * They just append data to the output buffer. MG_EV_SEND will be delivered when
+ * the data has actually been pushed out.
+ */
+void mg_send(struct mg_connection *, const void *buf, int len);
+
+/* Enables format string warnings for mg_printf */
+#if defined(__GNUC__)
+__attribute__((format(printf, 2, 3)))
+#endif
+/* don't separate from mg_printf declaration */
+
+/*
+ * Send `printf`-style formatted data to the connection.
+ *
+ * See `mg_send` for more details on send semantics.
+ */
+int mg_printf(struct mg_connection *, const char *fmt, ...);
+
+/* Same as `mg_printf()`, but takes `va_list ap` as an argument. */
+int mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);
+
+/*
+ * Create a socket pair.
+ * `sock_type` can be either `SOCK_STREAM` or `SOCK_DGRAM`.
+ * Return 0 on failure, 1 on success.
+ */
+int mg_socketpair(sock_t[2], int sock_type);
+
+/*
+ * Convert domain name into IP address.
+ *
+ * This is a utility function. If compilation flags have
+ * `-DMG_ENABLE_GETADDRINFO`, then `getaddrinfo()` call is used for name
+ * resolution. Otherwise, `gethostbyname()` is used.
+ *
+ * CAUTION: this function can block.
+ * Return 1 on success, 0 on failure.
+ */
+#ifndef MG_DISABLE_SYNC_RESOLVER
+int mg_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len);
+#endif
+
+/*
+ * Verify given IP address against the ACL.
+ *
+ * `remote_ip` - an IPv4 address to check, in host byte order
+ * `acl` - a comma separated list of IP subnets: `x.x.x.x/x` or `x.x.x.x`.
+ * Each subnet is
+ * prepended by either a - or a + sign. A plus sign means allow, where a
+ * minus sign means deny. If a subnet mask is omitted, such as `-1.2.3.4`,
+ * this means to deny only that single IP address.
+ * Subnet masks may vary from 0 to 32, inclusive. The default setting
+ * is to allow all accesses. On each request the full list is traversed,
+ * and the last match wins. Example:
+ *
+ * `-0.0.0.0/0,+192.168/16` - deny all acccesses, only allow 192.168/16 subnet
+ *
+ * To learn more about subnet masks, see the
+ * link:https://en.wikipedia.org/wiki/Subnetwork[Wikipedia page on Subnetwork]
+ *
+ * Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
+ */
+int mg_check_ip_acl(const char *acl, uint32_t remote_ip);
+
+/*
+ * Enable multi-threaded handling for the given listening connection `nc`.
+ * For each accepted connection, Mongoose will create a separate thread
+ * and run event handler in that thread. Thus, if an event hanler is doing
+ * a blocking call or some long computation, that will not slow down
+ * other connections.
+ */
+void mg_enable_multithreading(struct mg_connection *nc);
+
+#ifdef MG_ENABLE_JAVASCRIPT
+/*
+ * Enable server-side JavaScript scripting.
+ * Requires `-DMG_ENABLE_JAVASCRIPT` compilation flag, and V7 engine sources.
+ * v7 instance must not be destroyed during manager's lifetime.
+ * Return V7 error.
+ */
+enum v7_err mg_enable_javascript(struct mg_mgr *m, struct v7 *v7,
+ const char *init_js_file_name);
+#endif
+
+/*
+ * Schedule MG_EV_TIMER event to be delivered at `timestamp` time.
+ * `timestamp` is a UNIX time (a number of seconds since Epoch). It is
+ * `double` instead of `time_t` to allow for sub-second precision.
+ * Return the old timer value.
+ *
+ * Example: set connect timeout to 1.5 seconds:
+ *
+ * ```
+ * c = mg_connect(&mgr, "cesanta.com", ev_handler);
+ * mg_set_timer(c, mg_time() + 1.5);
+ * ...
+ *
+ * void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
+ * switch (ev) {
+ * case MG_EV_CONNECT:
+ * mg_set_timer(c, 0); // Clear connect timer
+ * break;
+ * case MG_EV_TIMER:
+ * log("Connect timeout");
+ * c->flags |= MG_F_CLOSE_IMMEDIATELY;
+ * break;
+ * ```
+ */
+double mg_set_timer(struct mg_connection *c, double timestamp);
+
+/*
+ * A sub-second precision version of time().
+ */
+double mg_time();
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_MONGOOSE_SRC_NET_H_ */
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#ifndef CS_MONGOOSE_SRC_NET_IF_H_
+#define CS_MONGOOSE_SRC_NET_IF_H_
+
+/*
+ * Internal async networking core interface.
+ * Consists of calls made by the core, which should not block,
+ * and callbacks back into the core ("..._cb").
+ * Callbacks may (will) cause methods to be invoked from within,
+ * but methods are not allowed to invoke callbacks inline.
+ *
+ * Implementation must ensure that only one callback is invoked at any time.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Request that a TCP connection is made to the specified address. */
+void mg_if_connect_tcp(struct mg_connection *nc,
+ const union socket_address *sa);
+/* Open a UDP socket. Doesn't actually connect anything. */
+void mg_if_connect_udp(struct mg_connection *nc);
+/* Callback invoked by connect methods. err = 0 -> ok, != 0 -> error. */
+void mg_if_connect_cb(struct mg_connection *nc, int err);
+
+/* Set up a listening TCP socket on a given address. rv = 0 -> ok. */
+int mg_if_listen_tcp(struct mg_connection *nc, union socket_address *sa);
+
+/*
+ * Deliver a new TCP connection. Returns NULL in case on error (unable to
+ * create connection, in which case interface state should be discarded.
+ * This is phase 1 of the two-phase process - MG_EV_ACCEPT will be delivered
+ * when mg_if_accept_tcp_cb is invoked.
+ */
+struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc);
+void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,
+ size_t sa_len);
+
+/* Request that a "listening" UDP socket be created. */
+int mg_if_listen_udp(struct mg_connection *nc, union socket_address *sa);
+
+/* Send functions for TCP and UDP. Sent data is copied before return. */
+void mg_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len);
+void mg_if_udp_send(struct mg_connection *nc, const void *buf, size_t len);
+/* Callback that reports that data has been put on the wire. */
+void mg_if_sent_cb(struct mg_connection *nc, int num_sent);
+
+/*
+ * Receive callback.
+ * buf must be heap-allocated and ownership is transferred to the core.
+ * Core will acknowledge consumption by calling mg_if_recved.
+ */
+void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len);
+void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,
+ union socket_address *sa, size_t sa_len);
+void mg_if_recved(struct mg_connection *nc, size_t len);
+
+/* Deliver a POLL event to the connection. */
+void mg_if_poll(struct mg_connection *nc, time_t now);
+
+/* Deliver a TIMER event to the connection. */
+void mg_if_timer(struct mg_connection *c, double now);
+
+/* Perform interface-related connection initialization. Return 1 on success. */
+int mg_if_create_conn(struct mg_connection *nc);
+
+/* Perform interface-related cleanup on connection before destruction. */
+void mg_if_destroy_conn(struct mg_connection *nc);
+
+void mg_close_conn(struct mg_connection *nc);
+
+/* Put connection's address into *sa, local (remote = 0) or remote. */
+void mg_if_get_conn_addr(struct mg_connection *nc, int remote,
+ union socket_address *sa);
+
+/* Associate a socket to a connection. */
+void mg_sock_set(struct mg_connection *nc, sock_t sock);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_MONGOOSE_SRC_NET_IF_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === URI
+ */
+
+#ifndef CS_MONGOOSE_SRC_URI_H_
+#define CS_MONGOOSE_SRC_URI_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * Parses an URI and fills string chunks with locations of the respective
+ * uri components within the input uri string. NULL pointers will be
+ * ignored.
+ *
+ * General syntax:
+ *
+ * [scheme://[user_info@]]host[:port][/path][?query][#fragment]
+ *
+ * Example:
+ *
+ * foo.com:80
+ * tcp://foo.com:1234
+ * http://foo.com:80/bar?baz=1
+ * https://user:pw@foo.com:443/blah
+ *
+ * `path` will include the leading slash. `query` won't include the leading `?`.
+ * `host` can contain embedded colons if surrounded by square brackets in order
+ * to support IPv6 literal addresses.
+ *
+ *
+ * Returns 0 on success, -1 on error.
+ */
+int mg_parse_uri(struct mg_str uri, struct mg_str *scheme,
+ struct mg_str *user_info, struct mg_str *host,
+ unsigned int *port, struct mg_str *path, struct mg_str *query,
+ struct mg_str *fragment);
+
+int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* CS_MONGOOSE_SRC_URI_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === Utilities
+ */
+
+#ifndef CS_MONGOOSE_SRC_UTIL_H_
+#define CS_MONGOOSE_SRC_UTIL_H_
+
+#include
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifndef MAX_PATH_SIZE
+#define MAX_PATH_SIZE 500
+#endif
+
+/*
+ * Fetch substring from input string `s`, `end` into `v`.
+ * Skips initial delimiter characters. Records first non-delimiter character
+ * as the beginning of substring `v`. Then scans the rest of the string
+ * until a delimiter character or end-of-string is found.
+ * `delimiters` is a 0-terminated string containing delimiter characters.
+ * Either one of `delimiters` or `end_string` terminates the search.
+ * Return an `s` pointer, advanced forward where parsing stopped.
+ */
+const char *mg_skip(const char *s, const char *end_string,
+ const char *delimiters, struct mg_str *v);
+
+/*
+ * Cross-platform version of `strncasecmp()`.
+ */
+int mg_ncasecmp(const char *s1, const char *s2, size_t len);
+
+/*
+ * Cross-platform version of `strcasecmp()`.
+ */
+int mg_casecmp(const char *s1, const char *s2);
+
+/*
+ * Cross-platform version of `strcmp()` where where first string is
+ * specified by `struct mg_str`.
+ */
+int mg_vcmp(const struct mg_str *str2, const char *str1);
+
+/*
+ * Cross-platform version of `strncasecmp()` where first string is
+ * specified by `struct mg_str`.
+ */
+int mg_vcasecmp(const struct mg_str *str2, const char *str1);
+
+/*
+ * Decode base64-encoded string `s`, `len` into the destination `dst`.
+ * Destination has to have enough space to hold decoded buffer.
+ * Decoding stops either when all string has been decoded, or invalid
+ * character appeared.
+ * Destination is '\0'-terminated.
+ * Return number of decoded characters. On success, that should be equal to
+ * `len`. On error (invalid character) the return value is smaller then `len`.
+ */
+int mg_base64_decode(const unsigned char *s, int len, char *dst);
+
+/*
+ * Base64-encode chunk of memory `src`, `src_len` into the destination `dst`.
+ * Destination has to have enough space to hold encoded buffer.
+ * Destination is '\0'-terminated.
+ */
+void mg_base64_encode(const unsigned char *src, int src_len, char *dst);
+
+#ifndef MG_DISABLE_FILESYSTEM
+/*
+ * Perform a 64-bit `stat()` call against given file.
+ *
+ * `path` should be UTF8 encoded.
+ *
+ * Return value is the same as for `stat()` syscall.
+ */
+int mg_stat(const char *path, cs_stat_t *st);
+
+/*
+ * Open the given file and return a file stream.
+ *
+ * `path` and `mode` should be UTF8 encoded.
+ *
+ * Return value is the same as for the `fopen()` call.
+ */
+FILE *mg_fopen(const char *path, const char *mode);
+
+/*
+ * Open the given file and return a file stream.
+ *
+ * `path` should be UTF8 encoded.
+ *
+ * Return value is the same as for the `open()` syscall.
+ */
+int mg_open(const char *path, int flag, int mode);
+#endif /* MG_DISABLE_FILESYSTEM */
+
+#if defined(_WIN32) && !defined(MG_ENABLE_THREADS)
+#define MG_ENABLE_THREADS
+#endif
+
+#ifdef MG_ENABLE_THREADS
+/*
+ * Start a new detached thread.
+ * Arguments and semantic is the same as pthead's `pthread_create()`.
+ * `thread_func` is a thread function, `thread_func_param` is a parameter
+ * that is passed to the thread function.
+ */
+void *mg_start_thread(void *(*thread_func)(void *), void *thread_func_param);
+#endif
+
+void mg_set_close_on_exec(sock_t);
+
+#define MG_SOCK_STRINGIFY_IP 1
+#define MG_SOCK_STRINGIFY_PORT 2
+#define MG_SOCK_STRINGIFY_REMOTE 4
+/*
+ * Convert connection's local or remote address into string.
+ *
+ * The `flags` parameter is a bit mask that controls the behavior,
+ * see `MG_SOCK_STRINGIFY_*` definitions.
+ *
+ * - MG_SOCK_STRINGIFY_IP - print IP address
+ * - MG_SOCK_STRINGIFY_PORT - print port number
+ * - MG_SOCK_STRINGIFY_REMOTE - print remote peer's IP/port, not local address
+ *
+ * If both port number and IP address are printed, they are separated by `:`.
+ * If compiled with `-DMG_ENABLE_IPV6`, IPv6 addresses are supported.
+ */
+void mg_conn_addr_to_str(struct mg_connection *nc, char *buf, size_t len,
+ int flags);
+#ifndef MG_DISABLE_SOCKET_IF /* Legacy interface. */
+void mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags);
+#endif
+
+/*
+ * Convert socket's address into string.
+ *
+ * `flags` is MG_SOCK_STRINGIFY_IP and/or MG_SOCK_STRINGIFY_PORT.
+ */
+void mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,
+ int flags);
+
+/*
+ * Generates human-readable hexdump of memory chunk.
+ *
+ * Takes a memory buffer `buf` of length `len` and creates a hex dump of that
+ * buffer in `dst`. Generated output is a-la hexdump(1).
+ * Return length of generated string, excluding terminating `\0`. If returned
+ * length is bigger than `dst_len`, overflow bytes are discarded.
+ */
+int mg_hexdump(const void *buf, int len, char *dst, int dst_len);
+
+/*
+ * Generates human-readable hexdump of the data sent or received by connection.
+ * `path` is a file name where hexdump should be written. `num_bytes` is
+ * a number of bytes sent/received. `ev` is one of the `MG_*` events sent to
+ * an event handler. This function is supposed to be called from the
+ * event handler.
+ */
+void mg_hexdump_connection(struct mg_connection *nc, const char *path,
+ const void *buf, int num_bytes, int ev);
+/*
+ * Print message to buffer. If buffer is large enough to hold the message,
+ * return buffer. If buffer is to small, allocate large enough buffer on heap,
+ * and return allocated buffer.
+ * This is a supposed use case:
+ *
+ * char buf[5], *p = buf;
+ * p = mg_avprintf(&p, sizeof(buf), "%s", "hi there");
+ * use_p_somehow(p);
+ * if (p != buf) {
+ * free(p);
+ * }
+ *
+ * The purpose of this is to avoid malloc-ing if generated strings are small.
+ */
+int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
+
+/*
+ * Return true if target platform is big endian.
+ */
+int mg_is_big_endian(void);
+
+/*
+ * A helper function for traversing a comma separated list of values.
+ * It returns a list pointer shifted to the next value, or NULL if the end
+ * of the list found.
+ * Value is stored in val vector. If value has form "x=y", then eq_val
+ * vector is initialized to point to the "y" part, and val vector length
+ * is adjusted to point only to "x".
+ * If list is just a comma separated list of entries, like "aa,bb,cc" then
+ * `eq_val` will contain zero-length string.
+ *
+ * The purpose of this function is to parse comma separated string without
+ * any copying/memory allocation.
+ */
+const char *mg_next_comma_list_entry(const char *list, struct mg_str *val,
+ struct mg_str *eq_val);
+
+/*
+ * Match 0-terminated string (mg_match_prefix) or string with given length
+ * mg_match_prefix_n against a glob pattern.
+ * Match is case-insensitive. Return number of bytes matched, or -1 if no match.
+ */
+int mg_match_prefix(const char *pattern, int pattern_len, const char *str);
+int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str);
+
+/*
+ * A helper function for creating mg_str struct from plain C string.
+ * `NULL` is allowed and becomes `{NULL, 0}`.
+ */
+struct mg_str mg_mk_str(const char *s);
+
+/* Macro for initializing mg_str. */
+#define MG_MK_STR(str_literal) \
+ { str_literal, sizeof(str_literal) - 1 }
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* CS_MONGOOSE_SRC_UTIL_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === HTTP + Websocket
+ */
+
+#ifndef CS_MONGOOSE_SRC_HTTP_H_
+#define CS_MONGOOSE_SRC_HTTP_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifndef MG_MAX_HTTP_HEADERS
+#define MG_MAX_HTTP_HEADERS 20
+#endif
+
+#ifndef MG_MAX_HTTP_REQUEST_SIZE
+#define MG_MAX_HTTP_REQUEST_SIZE 1024
+#endif
+
+#ifndef MG_MAX_PATH
+#ifdef PATH_MAX
+#define MG_MAX_PATH PATH_MAX
+#else
+#define MG_MAX_PATH 256
+#endif
+#endif
+
+#ifndef MG_MAX_HTTP_SEND_MBUF
+#define MG_MAX_HTTP_SEND_MBUF 1024
+#endif
+
+#ifndef MG_WEBSOCKET_PING_INTERVAL_SECONDS
+#define MG_WEBSOCKET_PING_INTERVAL_SECONDS 5
+#endif
+
+#ifndef MG_CGI_ENVIRONMENT_SIZE
+#define MG_CGI_ENVIRONMENT_SIZE 8192
+#endif
+
+#ifndef MG_MAX_CGI_ENVIR_VARS
+#define MG_MAX_CGI_ENVIR_VARS 64
+#endif
+
+#ifndef MG_ENV_EXPORT_TO_CGI
+#define MG_ENV_EXPORT_TO_CGI "MONGOOSE_CGI"
+#endif
+
+/* HTTP message */
+struct http_message {
+ struct mg_str message; /* Whole message: request line + headers + body */
+
+ /* HTTP Request line (or HTTP response line) */
+ struct mg_str method; /* "GET" */
+ struct mg_str uri; /* "/my_file.html" */
+ struct mg_str proto; /* "HTTP/1.1" -- for both request and response */
+
+ /* For responses, code and response status message are set */
+ int resp_code;
+ struct mg_str resp_status_msg;
+
+ /*
+ * Query-string part of the URI. For example, for HTTP request
+ * GET /foo/bar?param1=val1¶m2=val2
+ * | uri | query_string |
+ *
+ * Note that question mark character doesn't belong neither to the uri,
+ * nor to the query_string
+ */
+ struct mg_str query_string;
+
+ /* Headers */
+ struct mg_str header_names[MG_MAX_HTTP_HEADERS];
+ struct mg_str header_values[MG_MAX_HTTP_HEADERS];
+
+ /* Message body */
+ struct mg_str body; /* Zero-length for requests with no body */
+};
+
+/* WebSocket message */
+struct websocket_message {
+ unsigned char *data;
+ size_t size;
+ unsigned char flags;
+};
+
+/* HTTP multipart part */
+struct mg_http_multipart_part {
+ const char *file_name;
+ const char *var_name;
+ struct mg_str data;
+ int status; /* <0 on error */
+ void *user_data;
+};
+
+/* HTTP and websocket events. void *ev_data is described in a comment. */
+#define MG_EV_HTTP_REQUEST 100 /* struct http_message * */
+#define MG_EV_HTTP_REPLY 101 /* struct http_message * */
+#define MG_EV_HTTP_CHUNK 102 /* struct http_message * */
+#define MG_EV_SSI_CALL 105 /* char * */
+
+#define MG_EV_WEBSOCKET_HANDSHAKE_REQUEST 111 /* NULL */
+#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112 /* NULL */
+#define MG_EV_WEBSOCKET_FRAME 113 /* struct websocket_message * */
+#define MG_EV_WEBSOCKET_CONTROL_FRAME 114 /* struct websocket_message * */
+
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+#define MG_EV_HTTP_MULTIPART_REQUEST 121 /* struct http_message */
+#define MG_EV_HTTP_PART_BEGIN 122 /* struct mg_http_multipart_part */
+#define MG_EV_HTTP_PART_DATA 123 /* struct mg_http_multipart_part */
+#define MG_EV_HTTP_PART_END 124 /* struct mg_http_multipart_part */
+#endif
+
+/*
+ * Attach built-in HTTP event handler to the given connection.
+ * User-defined event handler will receive following extra events:
+ *
+ * - MG_EV_HTTP_REQUEST: HTTP request has arrived. Parsed HTTP request
+ * is passed as
+ * `struct http_message` through the handler's `void *ev_data` pointer.
+ * - MG_EV_HTTP_MULTIPART_REQUEST: A multipart POST request has received.
+ * This event is sent before body is parsed. After this user
+ * should expect a sequence of MG_EV_HTTP_PART_BEGIN/DATA/END requests.
+ * This is also the last time when headers and other request fields are
+ * accessible.
+ * - MG_EV_HTTP_REPLY: HTTP reply has arrived. Parsed HTTP reply is passed as
+ * `struct http_message` through the handler's `void *ev_data` pointer.
+ * - MG_EV_HTTP_CHUNK: HTTP chunked-encoding chunk has arrived.
+ * Parsed HTTP reply is passed as `struct http_message` through the
+ * handler's `void *ev_data` pointer. `http_message::body` would contain
+ * incomplete, reassembled HTTP body.
+ * It will grow with every new chunk arrived, and
+ * potentially can consume a lot of memory. An event handler may process
+ * the body as chunks are coming, and signal Mongoose to delete processed
+ * body by setting `MG_F_DELETE_CHUNK` in `mg_connection::flags`. When
+ * the last zero chunk is received,
+ * Mongoose sends `MG_EV_HTTP_REPLY` event with
+ * full reassembled body (if handler did not signal to delete chunks) or
+ * with empty body (if handler did signal to delete chunks).
+ * - MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: server has received websocket handshake
+ * request. `ev_data` contains parsed HTTP request.
+ * - MG_EV_WEBSOCKET_HANDSHAKE_DONE: server has completed Websocket handshake.
+ * `ev_data` is `NULL`.
+ * - MG_EV_WEBSOCKET_FRAME: new websocket frame has arrived. `ev_data` is
+ * `struct websocket_message *`
+ * - MG_EV_HTTP_PART_BEGIN: new part of multipart message is started,
+ * extra parameters are passed in mg_http_multipart_part
+ * - MG_EV_HTTP_PART_DATA: new portion of data from multiparted message
+ * no additional headers are available, only data and data size
+ * - MG_EV_HTTP_PART_END: final boundary received, analogue to maybe used to
+ * find the end of packet
+ * Note: Mongoose should be compiled with MG_ENABLE_HTTP_STREAMING_MULTIPART
+ * to enable MG_EV_HTTP_MULTIPART_REQUEST, MG_EV_HTTP_REQUEST_END,
+ * MG_EV_HTTP_REQUEST_CANCEL, MG_EV_HTTP_PART_BEGIN, MG_EV_HTTP_PART_DATA,
+ * MG_EV_HTTP_PART_END constants
+ */
+void mg_set_protocol_http_websocket(struct mg_connection *nc);
+
+/*
+ * Send websocket handshake to the server.
+ *
+ * `nc` must be a valid connection, connected to a server. `uri` is an URI
+ * to fetch, extra_headers` is extra HTTP headers to send or `NULL`.
+ *
+ * This function is intended to be used by websocket client.
+ *
+ * Note that the Host header is mandatory in HTTP/1.1 and must be
+ * included in `extra_headers`. `mg_send_websocket_handshake2` offers
+ * a better API for that.
+ *
+ * Deprecated in favour of `mg_send_websocket_handshake2`
+ */
+void mg_send_websocket_handshake(struct mg_connection *nc, const char *uri,
+ const char *extra_headers);
+
+/*
+ * Send websocket handshake to the server.
+ *
+ * `nc` must be a valid connection, connected to a server. `uri` is an URI
+ * to fetch, `host` goes into the `Host` header, `protocol` goes into the
+ * `Sec-WebSocket-Proto` header (NULL to omit), extra_headers` is extra HTTP
+ * headers to send or `NULL`.
+ *
+ * This function is intended to be used by websocket client.
+ */
+void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,
+ const char *host, const char *protocol,
+ const char *extra_headers);
+
+/*
+ * Helper function that creates an outbound WebSocket connection.
+ *
+ * `url` is a URL to connect to. It must be properly URL-encoded, e.g. have
+ * no spaces, etc. By default, `mg_connect_ws()` sends Connection and
+ * Host headers. `extra_headers` is an extra HTTP headers to send, e.g.
+ * `"User-Agent: my-app\r\n"`.
+ * If `protocol` is not NULL, then a `Sec-WebSocket-Protocol` header is sent.
+ *
+ * Examples:
+ *
+ * ```c
+ * nc1 = mg_connect_ws(mgr, ev_handler_1, "ws://echo.websocket.org", NULL,
+ * NULL);
+ * nc2 = mg_connect_ws(mgr, ev_handler_1, "wss://echo.websocket.org", NULL,
+ * NULL);
+ * nc3 = mg_connect_ws(mgr, ev_handler_1, "ws://api.cesanta.com",
+ * "clubby.cesanta.com", NULL);
+ * ```
+ */
+struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
+ mg_event_handler_t event_handler,
+ const char *url, const char *protocol,
+ const char *extra_headers);
+
+/*
+ * Helper function that creates an outbound WebSocket connection
+ *
+ * Mostly identical to mg_connect_ws, but allows to provide extra parameters
+ * (for example, SSL parameters
+ */
+struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr,
+ mg_event_handler_t ev_handler,
+ struct mg_connect_opts opts,
+ const char *url, const char *protocol,
+ const char *extra_headers);
+
+/*
+ * Send websocket frame to the remote end.
+ *
+ * `op_and_flags` specifies frame's type, one of:
+ *
+ * - WEBSOCKET_OP_CONTINUE
+ * - WEBSOCKET_OP_TEXT
+ * - WEBSOCKET_OP_BINARY
+ * - WEBSOCKET_OP_CLOSE
+ * - WEBSOCKET_OP_PING
+ * - WEBSOCKET_OP_PONG
+ *
+ * Orred with one of the flags:
+ *
+ * - WEBSOCKET_DONT_FIN: Don't set the FIN flag on the frame to be sent.
+ *
+ * `data` and `data_len` contain frame data.
+ */
+void mg_send_websocket_frame(struct mg_connection *nc, int op_and_flags,
+ const void *data, size_t data_len);
+
+/*
+ * Send multiple websocket frames.
+ *
+ * Like `mg_send_websocket_frame()`, but composes a frame from multiple buffers.
+ */
+void mg_send_websocket_framev(struct mg_connection *nc, int op_and_flags,
+ const struct mg_str *strings, int num_strings);
+
+/*
+ * Send websocket frame to the remote end.
+ *
+ * Like `mg_send_websocket_frame()`, but allows to create formatted message
+ * with `printf()`-like semantics.
+ */
+void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags,
+ const char *fmt, ...);
+
+/*
+ * Send buffer `buf` of size `len` to the client using chunked HTTP encoding.
+ * This function first sends buffer size as hex number + newline, then
+ * buffer itself, then newline. For example,
+ * `mg_send_http_chunk(nc, "foo", 3)` whill append `3\r\nfoo\r\n` string to
+ * the `nc->send_mbuf` output IO buffer.
+ *
+ * NOTE: HTTP header "Transfer-Encoding: chunked" should be sent prior to
+ * using this function.
+ *
+ * NOTE: do not forget to send empty chunk at the end of the response,
+ * to tell the client that everything was sent. Example:
+ *
+ * ```
+ * mg_printf_http_chunk(nc, "%s", "my response!");
+ * mg_send_http_chunk(nc, "", 0); // Tell the client we're finished
+ * ```
+ */
+void mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len);
+
+/*
+ * Send printf-formatted HTTP chunk.
+ * Functionality is similar to `mg_send_http_chunk()`.
+ */
+void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...);
+
+/*
+ * Send response status line.
+ * If `extra_headers` is not NULL, then `extra_headers` are also sent
+ * after the reponse line. `extra_headers` must NOT end end with new line.
+ * Example:
+ *
+ * mg_send_response_line(nc, 200, "Access-Control-Allow-Origin: *");
+ *
+ * Will result in:
+ *
+ * HTTP/1.1 200 OK\r\n
+ * Access-Control-Allow-Origin: *\r\n
+ */
+void mg_send_response_line(struct mg_connection *c, int status_code,
+ const char *extra_headers);
+
+/*
+ * Send response line and headers.
+ * This function sends response line with the `status_code`, and automatically
+ * sends one header: either "Content-Length", or "Transfer-Encoding".
+ * If `content_length` is negative, then "Transfer-Encoding: chunked" header
+ * is sent, otherwise, "Content-Length" header is sent.
+ *
+ * NOTE: If `Transfer-Encoding` is `chunked`, then message body must be sent
+ * using `mg_send_http_chunk()` or `mg_printf_http_chunk()` functions.
+ * Otherwise, `mg_send()` or `mg_printf()` must be used.
+ * Extra headers could be set through `extra_headers` - and note `extra_headers`
+ * must NOT be terminated by a new line.
+ */
+void mg_send_head(struct mg_connection *n, int status_code,
+ int64_t content_length, const char *extra_headers);
+
+/*
+ * Send printf-formatted HTTP chunk, escaping HTML tags.
+ */
+void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...);
+
+/* Websocket opcodes, from http://tools.ietf.org/html/rfc6455 */
+#define WEBSOCKET_OP_CONTINUE 0
+#define WEBSOCKET_OP_TEXT 1
+#define WEBSOCKET_OP_BINARY 2
+#define WEBSOCKET_OP_CLOSE 8
+#define WEBSOCKET_OP_PING 9
+#define WEBSOCKET_OP_PONG 10
+
+/*
+ * If set causes the FIN flag to not be set on outbound
+ * frames. This enables sending multiple fragments of a single
+ * logical message.
+ *
+ * The WebSocket protocol mandates that if the FIN flag of a data
+ * frame is not set, the next frame must be a WEBSOCKET_OP_CONTINUE.
+ * The last frame must have the FIN bit set.
+ *
+ * Note that mongoose will automatically defragment incoming messages,
+ * so this flag is used only on outbound messages.
+ */
+#define WEBSOCKET_DONT_FIN 0x100
+
+/*
+ * Parse a HTTP message.
+ *
+ * `is_req` should be set to 1 if parsing request, 0 if reply.
+ *
+ * Return number of bytes parsed. If HTTP message is
+ * incomplete, `0` is returned. On parse error, negative number is returned.
+ */
+int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req);
+
+/*
+ * Search and return header `name` in parsed HTTP message `hm`.
+ * If header is not found, NULL is returned. Example:
+ *
+ * struct mg_str *host_hdr = mg_get_http_header(hm, "Host");
+ */
+struct mg_str *mg_get_http_header(struct http_message *hm, const char *name);
+
+/*
+ * Parse HTTP header `hdr`. Find variable `var_name` and store it's value
+ * in the buffer `buf`, `buf_size`. Return 0 if variable not found, non-zero
+ * otherwise.
+ *
+ * This function is supposed to parse
+ * cookies, authentication headers, etcetera. Example (error handling omitted):
+ *
+ * char user[20];
+ * struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
+ * mg_http_parse_header(hdr, "username", user, sizeof(user));
+ *
+ * Return length of the variable's value. If buffer is not large enough,
+ * or variable not found, 0 is returned.
+ */
+int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
+ size_t buf_size);
+
+/*
+ * Parse buffer `buf`, `buf_len` that contains multipart form data chunks.
+ * Store chunk name in a `var_name`, `var_name_len` buffer.
+ * If a chunk is an uploaded file, then `file_name`, `file_name_len` is
+ * filled with an uploaded file name. `chunk`, `chunk_len`
+ * points to the chunk data.
+ *
+ * Return: number of bytes to skip to the next chunk, or 0 if there are
+ * no more chunks.
+ *
+ * Usage example:
+ *
+ * ```c
+ * static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ * switch(ev) {
+ * case MG_EV_HTTP_REQUEST: {
+ * struct http_message *hm = (struct http_message *) ev_data;
+ * char var_name[100], file_name[100];
+ * const char *chunk;
+ * size_t chunk_len, n1, n2;
+ *
+ * n1 = n2 = 0;
+ * while ((n2 = mg_parse_multipart(hm->body.p + n1,
+ * hm->body.len - n1,
+ * var_name, sizeof(var_name),
+ * file_name, sizeof(file_name),
+ * &chunk, &chunk_len)) > 0) {
+ * printf("var: %s, file_name: %s, size: %d, chunk: [%.*s]\n",
+ * var_name, file_name, (int) chunk_len,
+ * (int) chunk_len, chunk);
+ * n1 += n2;
+ * }
+ * }
+ * break;
+ * ```
+ */
+size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
+ size_t var_name_len, char *file_name,
+ size_t file_name_len, const char **chunk,
+ size_t *chunk_len);
+
+/*
+ * Fetch an HTTP form variable.
+ *
+ * Fetch a variable `name` from a `buf` into a buffer specified by
+ * `dst`, `dst_len`. Destination is always zero-terminated. Return length
+ * of a fetched variable. If not found, 0 is returned. `buf` must be
+ * valid url-encoded buffer. If destination is too small, `-1` is returned.
+ */
+int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,
+ size_t dst_len);
+
+/*
+ * Decode URL-encoded string.
+ *
+ * Source string is specified by (`src`, `src_len`), and destination is
+ * (`dst`, `dst_len`). If `is_form_url_encoded` is non-zero, then
+ * `+` character is decoded as a blank space character. This function
+ * guarantees to `\0`-terminate the destination. If destination is too small,
+ * then source string is partially decoded and `-1` is returned. Otherwise,
+ * a length of decoded string is returned, not counting final `\0`.
+ */
+int mg_url_decode(const char *src, int src_len, char *dst, int dst_len,
+ int is_form_url_encoded);
+
+/* Create Digest authentication header for client request. */
+int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
+ const char *method, const char *uri,
+ const char *auth_domain, const char *user,
+ const char *passwd);
+
+/*
+ * Helper function that creates outbound HTTP connection.
+ *
+ * `url` is a URL to fetch. It must be properly URL-encoded, e.g. have
+ * no spaces, etc. By default, `mg_connect_http()` sends Connection and
+ * Host headers. `extra_headers` is an extra HTTP headers to send, e.g.
+ * `"User-Agent: my-app\r\n"`.
+ * If `post_data` is NULL, then GET request is created. Otherwise, POST request
+ * is created with the specified POST data. Note that if the data being posted
+ * is a form submission, the `Content-Type` header should be set accordingly
+ * (see example below).
+ *
+ * Examples:
+ *
+ * ```c
+ * nc1 = mg_connect_http(mgr, ev_handler_1, "http://www.google.com", NULL,
+ * NULL);
+ * nc2 = mg_connect_http(mgr, ev_handler_1, "https://github.com", NULL, NULL);
+ * nc3 = mg_connect_http(
+ * mgr, ev_handler_1, "my_server:8000/form_submit/",
+ * "Content-Type: application/x-www-form-urlencoded\r\n",
+ * "var_1=value_1&var_2=value_2");
+ * ```
+ */
+struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
+ mg_event_handler_t event_handler,
+ const char *url,
+ const char *extra_headers,
+ const char *post_data);
+
+/*
+ * Helper function that creates outbound HTTP connection.
+ *
+ * Mostly identical to mg_connect_http, but allows to provide extra parameters
+ * (for example, SSL parameters
+ */
+struct mg_connection *mg_connect_http_opt(struct mg_mgr *mgr,
+ mg_event_handler_t ev_handler,
+ struct mg_connect_opts opts,
+ const char *url,
+ const char *extra_headers,
+ const char *post_data);
+/*
+ * This structure defines how `mg_serve_http()` works.
+ * Best practice is to set only required settings, and leave the rest as NULL.
+ */
+struct mg_serve_http_opts {
+ /* Path to web root directory */
+ const char *document_root;
+
+ /* List of index files. Default is "" */
+ const char *index_files;
+
+ /*
+ * Leave as NULL to disable authentication.
+ * To enable directory protection with authentication, set this to ".htpasswd"
+ * Then, creating ".htpasswd" file in any directory automatically protects
+ * it with digest authentication.
+ * Use `mongoose` web server binary, or `htdigest` Apache utility to
+ * create/manipulate passwords file.
+ * Make sure `auth_domain` is set to a valid domain name.
+ */
+ const char *per_directory_auth_file;
+
+ /* Authorization domain (domain name of this web server) */
+ const char *auth_domain;
+
+ /*
+ * Leave as NULL to disable authentication.
+ * Normally, only selected directories in the document root are protected.
+ * If absolutely every access to the web server needs to be authenticated,
+ * regardless of the URI, set this option to the path to the passwords file.
+ * Format of that file is the same as ".htpasswd" file. Make sure that file
+ * is located outside document root to prevent people fetching it.
+ */
+ const char *global_auth_file;
+
+ /* Set to "no" to disable directory listing. Enabled by default. */
+ const char *enable_directory_listing;
+
+ /* SSI files pattern. If not set, "**.shtml$|**.shtm$" is used. */
+ const char *ssi_pattern;
+
+ /* IP ACL. By default, NULL, meaning all IPs are allowed to connect */
+ const char *ip_acl;
+
+ /* URL rewrites.
+ *
+ * Comma-separated list of `uri_pattern=file_or_directory_path` rewrites.
+ * When HTTP request is received, Mongoose constructs a file name from the
+ * requested URI by combining `document_root` and the URI. However, if the
+ * rewrite option is used and `uri_pattern` matches requested URI, then
+ * `document_root` is ignored. Instead, `file_or_directory_path` is used,
+ * which should be a full path name or a path relative to the web server's
+ * current working directory. Note that `uri_pattern`, as all Mongoose
+ * patterns, is a prefix pattern.
+ *
+ * If uri_pattern starts with `@` symbol, then Mongoose compares it with the
+ * HOST header of the request. If they are equal, Mongoose sets document root
+ * to `file_or_directory_path`, implementing virtual hosts support.
+ * Example: `@foo.com=/document/root/for/foo.com`
+ *
+ * If `uri_pattern` starts with `%` symbol, then Mongoose compares it with
+ * the listening port. If they match, then Mongoose issues a 301 redirect.
+ * For example, to redirect all HTTP requests to the
+ * HTTPS port, do `%80=https://my.site.com`. Note that the request URI is
+ * automatically appended to the redirect location.
+ */
+ const char *url_rewrites;
+
+ /* DAV document root. If NULL, DAV requests are going to fail. */
+ const char *dav_document_root;
+
+ /*
+ * DAV passwords file. If NULL, DAV requests are going to fail.
+ * If passwords file is set to "-", then DAV auth is disabled.
+ */
+ const char *dav_auth_file;
+
+ /* Glob pattern for the files to hide. */
+ const char *hidden_file_pattern;
+
+ /* Set to non-NULL to enable CGI, e.g. **.cgi$|**.php$" */
+ const char *cgi_file_pattern;
+
+ /* If not NULL, ignore CGI script hashbang and use this interpreter */
+ const char *cgi_interpreter;
+
+ /*
+ * Comma-separated list of Content-Type overrides for path suffixes, e.g.
+ * ".txt=text/plain; charset=utf-8,.c=text/plain"
+ */
+ const char *custom_mime_types;
+
+ /*
+ * Extra HTTP headers to add to each server response.
+ * Example: to enable CORS, set this to "Access-Control-Allow-Origin: *".
+ */
+ const char *extra_headers;
+};
+
+/*
+ * Serve given HTTP request according to the `options`.
+ *
+ * Example code snippet:
+ *
+ * ```c
+ * static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ * struct http_message *hm = (struct http_message *) ev_data;
+ * struct mg_serve_http_opts opts = { .document_root = "/var/www" }; // C99
+ *
+ * switch (ev) {
+ * case MG_EV_HTTP_REQUEST:
+ * mg_serve_http(nc, hm, opts);
+ * break;
+ * default:
+ * break;
+ * }
+ * }
+ * ```
+ */
+void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
+ struct mg_serve_http_opts opts);
+
+/*
+ * Register callback for specified http endpoint
+ * Note: if callback is registered it is called instead of
+ * callback provided in mg_bind
+ *
+ * Example code snippet:
+ *
+ * ```c
+ * static void handle_hello1(struct mg_connection *nc, int ev, void *ev_data) {
+ * (void) ev; (void) ev_data;
+ * mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[I am Hello1]");
+ * nc->flags |= MG_F_SEND_AND_CLOSE;
+ * }
+ *
+ * static void handle_hello1(struct mg_connection *nc, int ev, void *ev_data) {
+ * (void) ev; (void) ev_data;
+ * mg_printf(nc, "HTTP/1.0 200 OK\r\n\r\n[I am Hello2]");
+ * nc->flags |= MG_F_SEND_AND_CLOSE;
+ * }
+ *
+ * void init() {
+ * nc = mg_bind(&mgr, local_addr, cb1);
+ * mg_register_http_endpoint(nc, "/hello1", handle_hello1);
+ * mg_register_http_endpoint(nc, "/hello1/hello2", handle_hello2);
+ * }
+ * ```
+ */
+void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
+ mg_event_handler_t handler);
+
+#ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
+
+/* Callback prototype for `mg_file_upload_handler()`. */
+typedef struct mg_str (*mg_fu_fname_fn)(struct mg_connection *nc,
+ struct mg_str fname);
+
+/*
+ * File upload handler.
+ * This handler can be used to implement file uploads with minimum code.
+ * This handler will process MG_EV_HTTP_PART_* events and store file data into
+ * a local file.
+ * `local_name_fn` will be invoked with whatever name was provided by the client
+ * and will expect the name of the local file to open. Return value of NULL will
+ * abort file upload (client will get a "403 Forbidden" response). If non-null,
+ * the returned string must be heap-allocated and will be freed by the caller.
+ * Exception: it is ok to return the same string verbatim.
+ *
+ * Example:
+ *
+ * ```c
+ * struct mg_str upload_fname(struct mg_connection *nc, struct mg_str fname) {
+ * // Just return the same filename. Do not actually do this except in test!
+ * // fname is user-controlled and needs to be sanitized.
+ * return fname;
+ * }
+ * void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
+ * switch (ev) {
+ * ...
+ * case MG_EV_HTTP_PART_BEGIN:
+ * case MG_EV_HTTP_PART_DATA:
+ * case MG_EV_HTTP_PART_END:
+ * mg_file_upload_handler(nc, ev, ev_data, upload_fname);
+ * break;
+ * }
+ * }
+ * ```
+ */
+void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
+ mg_fu_fname_fn local_name_fn);
+#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
+
+/*
+ * Authenticate HTTP request against opened passwords file.
+ * Returns 1 if authenticated, 0 otherwise.
+ */
+int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
+ FILE *fp);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* CS_MONGOOSE_SRC_HTTP_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === JSON-RPC
+ */
+
+#ifndef CS_MONGOOSE_SRC_JSON_RPC_H_
+#define CS_MONGOOSE_SRC_JSON_RPC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* JSON-RPC request */
+struct mg_rpc_request {
+ struct json_token *message; /* Whole RPC message */
+ struct json_token *id; /* Message ID */
+ struct json_token *method; /* Method name */
+ struct json_token *params; /* Method params */
+};
+
+/* JSON-RPC response */
+struct mg_rpc_reply {
+ struct json_token *message; /* Whole RPC message */
+ struct json_token *id; /* Message ID */
+ struct json_token *result; /* Remote call result */
+};
+
+/* JSON-RPC error */
+struct mg_rpc_error {
+ struct json_token *message; /* Whole RPC message */
+ struct json_token *id; /* Message ID */
+ struct json_token *error_code; /* error.code */
+ struct json_token *error_message; /* error.message */
+ struct json_token *error_data; /* error.data, can be NULL */
+};
+
+/*
+ * Parse JSON-RPC reply contained in `buf`, `len` into JSON tokens array
+ * `toks`, `max_toks`. If buffer contains valid reply, `reply` structure is
+ * populated. The result of RPC call is located in `reply.result`. On error,
+ * `error` structure is populated. Returns: the result of calling
+ * `parse_json(buf, len, toks, max_toks)`:
+ *
+ * On success, an offset inside `json_string` is returned
+ * where parsing has finished. On failure, a negative number is
+ * returned, one of:
+ *
+ * - `#define JSON_STRING_INVALID -1`
+ * - `#define JSON_STRING_INCOMPLETE -2`
+ * - `#define JSON_TOKEN_ARRAY_TOO_SMALL -3`
+ */
+int mg_rpc_parse_reply(const char *buf, int len, struct json_token *toks,
+ int max_toks, struct mg_rpc_reply *,
+ struct mg_rpc_error *);
+
+/*
+ * Create JSON-RPC request in a given buffer.
+ *
+ * Return length of the request, which
+ * can be larger then `len` that indicates an overflow.
+ * `params_fmt` format string should conform to `json_emit()` API,
+ * see https://github.com/cesanta/frozen
+ */
+int mg_rpc_create_request(char *buf, int len, const char *method,
+ const char *id, const char *params_fmt, ...);
+
+/*
+ * Create JSON-RPC reply in a given buffer.
+ *
+ * Return length of the reply, which
+ * can be larger then `len` that indicates an overflow.
+ * `result_fmt` format string should conform to `json_emit()` API,
+ * see https://github.com/cesanta/frozen
+ */
+int mg_rpc_create_reply(char *buf, int len, const struct mg_rpc_request *req,
+ const char *result_fmt, ...);
+
+/*
+ * Create JSON-RPC error reply in a given buffer.
+ *
+ * Return length of the error, which
+ * can be larger then `len` that indicates an overflow.
+ * `fmt` format string should conform to `json_emit()` API,
+ * see https://github.com/cesanta/frozen
+ */
+int mg_rpc_create_error(char *buf, int len, struct mg_rpc_request *req,
+ int code, const char *message, const char *fmt, ...);
+
+/* JSON-RPC standard error codes */
+#define JSON_RPC_PARSE_ERROR (-32700)
+#define JSON_RPC_INVALID_REQUEST_ERROR (-32600)
+#define JSON_RPC_METHOD_NOT_FOUND_ERROR (-32601)
+#define JSON_RPC_INVALID_PARAMS_ERROR (-32602)
+#define JSON_RPC_INTERNAL_ERROR (-32603)
+#define JSON_RPC_SERVER_ERROR (-32000)
+
+/*
+ * Create JSON-RPC error in a given buffer.
+ *
+ * Return length of the error, which
+ * can be larger then `len` that indicates an overflow. See
+ * JSON_RPC_*_ERROR definitions for standard error values:
+ *
+ * - `#define JSON_RPC_PARSE_ERROR (-32700)`
+ * - `#define JSON_RPC_INVALID_REQUEST_ERROR (-32600)`
+ * - `#define JSON_RPC_METHOD_NOT_FOUND_ERROR (-32601)`
+ * - `#define JSON_RPC_INVALID_PARAMS_ERROR (-32602)`
+ * - `#define JSON_RPC_INTERNAL_ERROR (-32603)`
+ * - `#define JSON_RPC_SERVER_ERROR (-32000)`
+ */
+int mg_rpc_create_std_error(char *buf, int len, struct mg_rpc_request *req,
+ int code);
+
+typedef int (*mg_rpc_handler_t)(char *buf, int len, struct mg_rpc_request *req);
+
+/*
+ * Dispatches a JSON-RPC request.
+ *
+ * Parses JSON-RPC request contained in `buf`, `len`.
+ * Then, dispatches the request to the correct handler method.
+ * Valid method names should be specified in NULL
+ * terminated array `methods`, and corresponding handlers in `handlers`.
+ * Result is put in `dst`, `dst_len`. Return: length of the result, which
+ * can be larger then `dst_len` that indicates an overflow.
+ * Overflown bytes are not written to the buffer.
+ * If method is not found, an error is automatically generated.
+ */
+int mg_rpc_dispatch(const char *buf, int, char *dst, int dst_len,
+ const char **methods, mg_rpc_handler_t *handlers);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* CS_MONGOOSE_SRC_JSON_RPC_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in .
+ */
+
+/*
+ * === MQTT
+ */
+
+#ifndef CS_MONGOOSE_SRC_MQTT_H_
+#define CS_MONGOOSE_SRC_MQTT_H_
+
+
+struct mg_mqtt_message {
+ int cmd;
+ struct mg_str payload;
+ int qos;
+ uint8_t connack_ret_code; /* connack */
+ uint16_t message_id; /* puback */
+ char *topic;
+};
+
+struct mg_mqtt_topic_expression {
+ const char *topic;
+ uint8_t qos;
+};
+
+struct mg_send_mqtt_handshake_opts {
+ unsigned char flags; /* connection flags */
+ uint16_t keep_alive;
+ const char *will_topic;
+ const char *will_message;
+ const char *user_name;
+ const char *password;
+};
+
+/* Message types */
+#define MG_MQTT_CMD_CONNECT 1
+#define MG_MQTT_CMD_CONNACK 2
+#define MG_MQTT_CMD_PUBLISH 3
+#define MG_MQTT_CMD_PUBACK 4
+#define MG_MQTT_CMD_PUBREC 5
+#define MG_MQTT_CMD_PUBREL 6
+#define MG_MQTT_CMD_PUBCOMP 7
+#define MG_MQTT_CMD_SUBSCRIBE 8
+#define MG_MQTT_CMD_SUBACK 9
+#define MG_MQTT_CMD_UNSUBSCRIBE 10
+#define MG_MQTT_CMD_UNSUBACK 11
+#define MG_MQTT_CMD_PINGREQ 12
+#define MG_MQTT_CMD_PINGRESP 13
+#define MG_MQTT_CMD_DISCONNECT 14
+
+/* MQTT event types */
+#define MG_MQTT_EVENT_BASE 200
+#define MG_EV_MQTT_CONNECT (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_CONNECT)
+#define MG_EV_MQTT_CONNACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_CONNACK)
+#define MG_EV_MQTT_PUBLISH (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBLISH)
+#define MG_EV_MQTT_PUBACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBACK)
+#define MG_EV_MQTT_PUBREC (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBREC)
+#define MG_EV_MQTT_PUBREL (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBREL)
+#define MG_EV_MQTT_PUBCOMP (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBCOMP)
+#define MG_EV_MQTT_SUBSCRIBE (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_SUBSCRIBE)
+#define MG_EV_MQTT_SUBACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_SUBACK)
+#define MG_EV_MQTT_UNSUBSCRIBE (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_UNSUBSCRIBE)
+#define MG_EV_MQTT_UNSUBACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_UNSUBACK)
+#define MG_EV_MQTT_PINGREQ (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PINGREQ)
+#define MG_EV_MQTT_PINGRESP (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PINGRESP)
+#define MG_EV_MQTT_DISCONNECT (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_DISCONNECT)
+
+/* Message flags */
+#define MG_MQTT_RETAIN 0x1
+#define MG_MQTT_DUP 0x4
+#define MG_MQTT_QOS(qos) ((qos) << 1)
+#define MG_MQTT_GET_QOS(flags) (((flags) &0x6) >> 1)
+#define MG_MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1)
+
+/* Connection flags */
+#define MG_MQTT_CLEAN_SESSION 0x02
+#define MG_MQTT_HAS_WILL 0x04
+#define MG_MQTT_WILL_RETAIN 0x20
+#define MG_MQTT_HAS_PASSWORD 0x40
+#define MG_MQTT_HAS_USER_NAME 0x80
+#define MG_MQTT_GET_WILL_QOS(flags) (((flags) &0x18) >> 3)
+#define MG_MQTT_SET_WILL_QOS(flags, qos) \
+ (flags) = ((flags) & ~0x18) | ((qos) << 3)
+
+/* CONNACK return codes */
+#define MG_EV_MQTT_CONNACK_ACCEPTED 0
+#define MG_EV_MQTT_CONNACK_UNACCEPTABLE_VERSION 1
+#define MG_EV_MQTT_CONNACK_IDENTIFIER_REJECTED 2
+#define MG_EV_MQTT_CONNACK_SERVER_UNAVAILABLE 3
+#define MG_EV_MQTT_CONNACK_BAD_AUTH 4
+#define MG_EV_MQTT_CONNACK_NOT_AUTHORIZED 5
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * Attach built-in MQTT event handler to the given connection.
+ *
+ * The user-defined event handler will receive following extra events:
+ *
+ * - MG_EV_MQTT_CONNACK
+ * - MG_EV_MQTT_PUBLISH
+ * - MG_EV_MQTT_PUBACK
+ * - MG_EV_MQTT_PUBREC
+ * - MG_EV_MQTT_PUBREL
+ * - MG_EV_MQTT_PUBCOMP
+ * - MG_EV_MQTT_SUBACK
+ */
+void mg_set_protocol_mqtt(struct mg_connection *nc);
+
+/* Send MQTT handshake. */
+void mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id);
+
+/* Send MQTT handshake with optional parameters. */
+void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,
+ struct mg_send_mqtt_handshake_opts);
+
+/* Publish a message to a given topic. */
+void mg_mqtt_publish(struct mg_connection *nc, const char *topic,
+ uint16_t message_id, int flags, const void *data,
+ size_t len);
+
+/* Subscribe to a bunch of topics. */
+void mg_mqtt_subscribe(struct mg_connection *nc,
+ const struct mg_mqtt_topic_expression *topics,
+ size_t topics_len, uint16_t message_id);
+
+/* Unsubscribe from a bunch of topics. */
+void mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics,
+ size_t topics_len, uint16_t message_id);
+
+/* Send a DISCONNECT command. */
+void mg_mqtt_disconnect(struct mg_connection *nc);
+
+/* Send a CONNACK command with a given `return_code`. */
+void mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code);
+
+/* Send a PUBACK command with a given `message_id`. */
+void mg_mqtt_puback(struct mg_connection *nc, uint16_t message_id);
+
+/* Send a PUBREC command with a given `message_id`. */
+void mg_mqtt_pubrec(struct mg_connection *nc, uint16_t message_id);
+
+/* Send a PUBREL command with a given `message_id`. */
+void mg_mqtt_pubrel(struct mg_connection *nc, uint16_t message_id);
+
+/* Send a PUBCOMP command with a given `message_id`. */
+void mg_mqtt_pubcomp(struct mg_connection *nc, uint16_t message_id);
+
+/*
+ * Send a SUBACK command with a given `message_id`
+ * and a sequence of granted QoSs.
+ */
+void mg_mqtt_suback(struct mg_connection *nc, uint8_t *qoss, size_t qoss_len,
+ uint16_t message_id);
+
+/* Send a UNSUBACK command with a given `message_id`. */
+void mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id);
+
+/* Send a PINGREQ command. */
+void mg_mqtt_ping(struct mg_connection *nc);
+
+/* Send a PINGRESP command. */
+void mg_mqtt_pong(struct mg_connection *nc);
+
+/*
+ * Extract the next topic expression from a SUBSCRIBE command payload.
+ *
+ * Topic expression name will point to a string in the payload buffer.
+ * Return the pos of the next topic expression or -1 when the list
+ * of topics is exhausted.
+ */
+int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,
+ struct mg_str *topic, uint8_t *qos, int pos);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* CS_MONGOOSE_SRC_MQTT_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in .
+ */
+
+/*
+ * === MQTT Broker
+ */
+
+#ifndef CS_MONGOOSE_SRC_MQTT_BROKER_H_
+#define CS_MONGOOSE_SRC_MQTT_BROKER_H_
+
+#ifdef MG_ENABLE_MQTT_BROKER
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define MG_MQTT_MAX_SESSION_SUBSCRIPTIONS 512;
+
+struct mg_mqtt_broker;
+
+/* MQTT session (Broker side). */
+struct mg_mqtt_session {
+ struct mg_mqtt_broker *brk; /* Broker */
+ struct mg_mqtt_session *next, *prev; /* mg_mqtt_broker::sessions linkage */
+ struct mg_connection *nc; /* Connection with the client */
+ size_t num_subscriptions; /* Size of `subscriptions` array */
+ struct mg_mqtt_topic_expression *subscriptions;
+ void *user_data; /* User data */
+};
+
+/* MQTT broker. */
+struct mg_mqtt_broker {
+ struct mg_mqtt_session *sessions; /* Session list */
+ void *user_data; /* User data */
+};
+
+/* Initialize a MQTT broker. */
+void mg_mqtt_broker_init(struct mg_mqtt_broker *brk, void *user_data);
+
+/*
+ * Process a MQTT broker message.
+ *
+ * Listening connection expects a pointer to an initialized `mg_mqtt_broker`
+ * structure in the `user_data` field.
+ *
+ * Basic usage:
+ *
+ * ```c
+ * mg_mqtt_broker_init(&brk, NULL);
+ *
+ * if ((nc = mg_bind(&mgr, address, mg_mqtt_broker)) == NULL) {
+ * // fail;
+ * }
+ * nc->user_data = &brk;
+ * ```
+ *
+ * New incoming connections will receive a `mg_mqtt_session` structure
+ * in the connection `user_data`. The original `user_data` will be stored
+ * in the `user_data` field of the session structure. This allows the user
+ * handler to store user data before `mg_mqtt_broker` creates the session.
+ *
+ * Since only the MG_EV_ACCEPT message is processed by the listening socket,
+ * for most events the `user_data` will thus point to a `mg_mqtt_session`.
+ */
+void mg_mqtt_broker(struct mg_connection *brk, int ev, void *data);
+
+/*
+ * Iterate over all mqtt sessions connections. Example:
+ *
+ * ```c
+ * struct mg_mqtt_session *s;
+ * for (s = mg_mqtt_next(brk, NULL); s != NULL; s = mg_mqtt_next(brk, s)) {
+ * // Do something
+ * }
+ * ```
+ */
+struct mg_mqtt_session *mg_mqtt_next(struct mg_mqtt_broker *brk,
+ struct mg_mqtt_session *s);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MG_ENABLE_MQTT_BROKER */
+#endif /* CS_MONGOOSE_SRC_MQTT_BROKER_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === DNS
+ */
+
+#ifndef CS_MONGOOSE_SRC_DNS_H_
+#define CS_MONGOOSE_SRC_DNS_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define MG_DNS_A_RECORD 0x01 /* Lookup IP address */
+#define MG_DNS_CNAME_RECORD 0x05 /* Lookup CNAME */
+#define MG_DNS_AAAA_RECORD 0x1c /* Lookup IPv6 address */
+#define MG_DNS_MX_RECORD 0x0f /* Lookup mail server for domain */
+
+#define MG_MAX_DNS_QUESTIONS 32
+#define MG_MAX_DNS_ANSWERS 32
+
+#define MG_DNS_MESSAGE 100 /* High-level DNS message event */
+
+enum mg_dns_resource_record_kind {
+ MG_DNS_INVALID_RECORD = 0,
+ MG_DNS_QUESTION,
+ MG_DNS_ANSWER
+};
+
+/* DNS resource record. */
+struct mg_dns_resource_record {
+ struct mg_str name; /* buffer with compressed name */
+ int rtype;
+ int rclass;
+ int ttl;
+ enum mg_dns_resource_record_kind kind;
+ struct mg_str rdata; /* protocol data (can be a compressed name) */
+};
+
+/* DNS message (request and response). */
+struct mg_dns_message {
+ struct mg_str pkt; /* packet body */
+ uint16_t flags;
+ uint16_t transaction_id;
+ int num_questions;
+ int num_answers;
+ struct mg_dns_resource_record questions[MG_MAX_DNS_QUESTIONS];
+ struct mg_dns_resource_record answers[MG_MAX_DNS_ANSWERS];
+};
+
+struct mg_dns_resource_record *mg_dns_next_record(
+ struct mg_dns_message *msg, int query, struct mg_dns_resource_record *prev);
+
+/*
+ * Parse the record data from a DNS resource record.
+ *
+ * - A: struct in_addr *ina
+ * - AAAA: struct in6_addr *ina
+ * - CNAME: char buffer
+ *
+ * Returns -1 on error.
+ *
+ * TODO(mkm): MX
+ */
+int mg_dns_parse_record_data(struct mg_dns_message *msg,
+ struct mg_dns_resource_record *rr, void *data,
+ size_t data_len);
+
+/*
+ * Send a DNS query to the remote end.
+ */
+void mg_send_dns_query(struct mg_connection *nc, const char *name,
+ int query_type);
+
+/*
+ * Insert a DNS header to an IO buffer.
+ *
+ * Return number of bytes inserted.
+ */
+int mg_dns_insert_header(struct mbuf *io, size_t pos,
+ struct mg_dns_message *msg);
+
+/*
+ * Append already encoded questions from an existing message.
+ *
+ * This is useful when generating a DNS reply message which includes
+ * all question records.
+ *
+ * Return number of appened bytes.
+ */
+int mg_dns_copy_questions(struct mbuf *io, struct mg_dns_message *msg);
+
+/*
+ * Encode and append a DNS resource record to an IO buffer.
+ *
+ * The record metadata is taken from the `rr` parameter, while the name and data
+ * are taken from the parameters, encoded in the appropriate format depending on
+ * record type, and stored in the IO buffer. The encoded values might contain
+ * offsets within the IO buffer. It's thus important that the IO buffer doesn't
+ * get trimmed while a sequence of records are encoded while preparing a DNS
+ *reply.
+ *
+ * This function doesn't update the `name` and `rdata` pointers in the `rr`
+ *struct
+ * because they might be invalidated as soon as the IO buffer grows again.
+ *
+ * Return the number of bytes appened or -1 in case of error.
+ */
+int mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr,
+ const char *name, size_t nlen, const void *rdata,
+ size_t rlen);
+
+/* Low-level: parses a DNS response. */
+int mg_parse_dns(const char *buf, int len, struct mg_dns_message *msg);
+
+/*
+ * Uncompress a DNS compressed name.
+ *
+ * The containing dns message is required because the compressed encoding
+ * and reference suffixes present elsewhere in the packet.
+ *
+ * If name is less than `dst_len` characters long, the remainder
+ * of `dst` is terminated with `\0' characters. Otherwise, `dst` is not
+ *terminated.
+ *
+ * If `dst_len` is 0 `dst` can be NULL.
+ * Return the uncompressed name length.
+ */
+size_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,
+ char *dst, int dst_len);
+
+/*
+ * Attach built-in DNS event handler to the given listening connection.
+ *
+ * DNS event handler parses incoming UDP packets, treating them as DNS
+ * requests. If incoming packet gets successfully parsed by the DNS event
+ * handler, a user event handler will receive `MG_DNS_REQUEST` event, with
+ * `ev_data` pointing to the parsed `struct mg_dns_message`.
+ *
+ * See
+ * [captive_dns_server](https://github.com/cesanta/mongoose/tree/master/examples/captive_dns_server)
+ * example on how to handle DNS request and send DNS reply.
+ */
+void mg_set_protocol_dns(struct mg_connection *nc);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* CS_MONGOOSE_SRC_DNS_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === DNS server
+ *
+ * Disabled by default; enable with `-DMG_ENABLE_DNS_SERVER`.
+ */
+
+#ifndef CS_MONGOOSE_SRC_DNS_SERVER_H_
+#define CS_MONGOOSE_SRC_DNS_SERVER_H_
+
+#ifdef MG_ENABLE_DNS_SERVER
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define MG_DNS_SERVER_DEFAULT_TTL 3600
+
+struct mg_dns_reply {
+ struct mg_dns_message *msg;
+ struct mbuf *io;
+ size_t start;
+};
+
+/*
+ * Create a DNS reply.
+ *
+ * The reply will be based on an existing query message `msg`.
+ * The query body will be appended to the output buffer.
+ * "reply + recursion allowed" will be added to the message flags and
+ * message's num_answers will be set to 0.
+ *
+ * Answer records can be appended with `mg_dns_send_reply` or by lower
+ * level function defined in the DNS API.
+ *
+ * In order to send the reply use `mg_dns_send_reply`.
+ * It's possible to use a connection's send buffer as reply buffers,
+ * and it will work for both UDP and TCP connections.
+ *
+ * Example:
+ *
+ * ```c
+ * reply = mg_dns_create_reply(&nc->send_mbuf, msg);
+ * for (i = 0; i < msg->num_questions; i++) {
+ * rr = &msg->questions[i];
+ * if (rr->rtype == MG_DNS_A_RECORD) {
+ * mg_dns_reply_record(&reply, rr, 3600, &dummy_ip_addr, 4);
+ * }
+ * }
+ * mg_dns_send_reply(nc, &reply);
+ * ```
+ */
+struct mg_dns_reply mg_dns_create_reply(struct mbuf *io,
+ struct mg_dns_message *msg);
+
+/*
+ * Append a DNS reply record to the IO buffer and to the DNS message.
+ *
+ * The message num_answers field will be incremented. It's caller's duty
+ * to ensure num_answers is propertly initialized.
+ *
+ * Returns -1 on error.
+ */
+int mg_dns_reply_record(struct mg_dns_reply *reply,
+ struct mg_dns_resource_record *question,
+ const char *name, int rtype, int ttl, const void *rdata,
+ size_t rdata_len);
+
+/*
+ * Send a DNS reply through a connection.
+ *
+ * The DNS data is stored in an IO buffer pointed by reply structure in `r`.
+ * This function mutates the content of that buffer in order to ensure that
+ * the DNS header reflects size and flags of the mssage, that might have been
+ * updated either with `mg_dns_reply_record` or by direct manipulation of
+ * `r->message`.
+ *
+ * Once sent, the IO buffer will be trimmed unless the reply IO buffer
+ * is the connection's send buffer and the connection is not in UDP mode.
+ */
+void mg_dns_send_reply(struct mg_connection *nc, struct mg_dns_reply *r);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MG_ENABLE_DNS_SERVER */
+#endif /* CS_MONGOOSE_SRC_DNS_SERVER_H_ */
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+/*
+ * === Asynchronouns DNS resolver
+ */
+
+#ifndef CS_MONGOOSE_SRC_RESOLV_H_
+#define CS_MONGOOSE_SRC_RESOLV_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+enum mg_resolve_err {
+ MG_RESOLVE_OK = 0,
+ MG_RESOLVE_NO_ANSWERS = 1,
+ MG_RESOLVE_EXCEEDED_RETRY_COUNT = 2,
+ MG_RESOLVE_TIMEOUT = 3
+};
+
+typedef void (*mg_resolve_callback_t)(struct mg_dns_message *dns_message,
+ void *user_data, enum mg_resolve_err);
+
+/* Options for `mg_resolve_async_opt`. */
+struct mg_resolve_async_opts {
+ const char *nameserver_url;
+ int max_retries; /* defaults to 2 if zero */
+ int timeout; /* in seconds; defaults to 5 if zero */
+ int accept_literal; /* pseudo-resolve literal ipv4 and ipv6 addrs */
+ int only_literal; /* only resolves literal addrs; sync cb invocation */
+ struct mg_connection **dns_conn; /* return DNS connection */
+};
+
+/* See `mg_resolve_async_opt()` */
+int mg_resolve_async(struct mg_mgr *mgr, const char *name, int query,
+ mg_resolve_callback_t cb, void *data);
+
+/*
+ * Resolved a DNS name asynchronously.
+ *
+ * Upon successful resolution, the user callback will be invoked
+ * with the full DNS response message and a pointer to the user's
+ * context `data`.
+ *
+ * In case of timeout while performing the resolution the callback
+ * will receive a NULL `msg`.
+ *
+ * The DNS answers can be extracted with `mg_next_record` and
+ * `mg_dns_parse_record_data`:
+ *
+ * [source,c]
+ * ----
+ * struct in_addr ina;
+ * struct mg_dns_resource_record *rr = mg_next_record(msg, MG_DNS_A_RECORD,
+ * NULL);
+ * mg_dns_parse_record_data(msg, rr, &ina, sizeof(ina));
+ * ----
+ */
+int mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,
+ mg_resolve_callback_t cb, void *data,
+ struct mg_resolve_async_opts opts);
+
+/*
+ * Resolve a name from `/etc/hosts`.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int mg_resolve_from_hosts_file(const char *host, union socket_address *usa);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* CS_MONGOOSE_SRC_RESOLV_H_ */
+/*
+ * Copyright (c) 2015 Cesanta Software Limited
+ * All rights reserved
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see .
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively, you can license this software under a commercial
+ * license, as set out in .
+ */
+
+/*
+ * === CoAP
+ *
+ * CoAP message format:
+ *
+ * ```
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+ * |Ver| T | TKL | Code | Message ID | Token (if any, TKL bytes) ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+ * | Options (if any) ... |1 1 1 1 1 1 1 1| Payload (if any) ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+ * ```
+ */
+
+#ifndef CS_MONGOOSE_SRC_COAP_H_
+#define CS_MONGOOSE_SRC_COAP_H_
+
+#ifdef MG_ENABLE_COAP
+
+#define MG_COAP_MSG_TYPE_FIELD 0x2
+#define MG_COAP_CODE_CLASS_FIELD 0x4
+#define MG_COAP_CODE_DETAIL_FIELD 0x8
+#define MG_COAP_MSG_ID_FIELD 0x10
+#define MG_COAP_TOKEN_FIELD 0x20
+#define MG_COAP_OPTIOMG_FIELD 0x40
+#define MG_COAP_PAYLOAD_FIELD 0x80
+
+#define MG_COAP_ERROR 0x10000
+#define MG_COAP_FORMAT_ERROR (MG_COAP_ERROR | 0x20000)
+#define MG_COAP_IGNORE (MG_COAP_ERROR | 0x40000)
+#define MG_COAP_NOT_ENOUGH_DATA (MG_COAP_ERROR | 0x80000)
+#define MG_COAP_NETWORK_ERROR (MG_COAP_ERROR | 0x100000)
+
+#define MG_COAP_MSG_CON 0
+#define MG_COAP_MSG_NOC 1
+#define MG_COAP_MSG_ACK 2
+#define MG_COAP_MSG_RST 3
+#define MG_COAP_MSG_MAX 3
+
+#define MG_COAP_CODECLASS_REQUEST 0
+#define MG_COAP_CODECLASS_RESP_OK 2
+#define MG_COAP_CODECLASS_CLIENT_ERR 4
+#define MG_COAP_CODECLASS_SRV_ERR 5
+
+#define MG_COAP_EVENT_BASE 300
+#define MG_EV_COAP_CON (MG_COAP_EVENT_BASE + MG_COAP_MSG_CON)
+#define MG_EV_COAP_NOC (MG_COAP_EVENT_BASE + MG_COAP_MSG_NOC)
+#define MG_EV_COAP_ACK (MG_COAP_EVENT_BASE + MG_COAP_MSG_ACK)
+#define MG_EV_COAP_RST (MG_COAP_EVENT_BASE + MG_COAP_MSG_RST)
+
+/*
+ * CoAP options.
+ * Use mg_coap_add_option and mg_coap_free_options
+ * for creation and destruction.
+ */
+struct mg_coap_option {
+ struct mg_coap_option *next;
+ uint32_t number;
+ struct mg_str value;
+};
+
+/* CoAP message. See RFC 7252 for details. */
+struct mg_coap_message {
+ uint32_t flags;
+ uint8_t msg_type;
+ uint8_t code_class;
+ uint8_t code_detail;
+ uint16_t msg_id;
+ struct mg_str token;
+ struct mg_coap_option *options;
+ struct mg_str payload;
+ struct mg_coap_option *optiomg_tail;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Set CoAP protocol handler - trigger CoAP specific events */
+int mg_set_protocol_coap(struct mg_connection *nc);
+
+/*
+ * Add new option to mg_coap_message structure.
+ * Returns pointer to the newly created option.
+ */
+struct mg_coap_option *mg_coap_add_option(struct mg_coap_message *cm,
+ uint32_t number, char *value,
+ size_t len);
+
+/*
+ * Free the memory allocated for options,
+ * if cm paramater doesn't contain any option does nothing.
+ */
+void mg_coap_free_options(struct mg_coap_message *cm);
+
+/*
+ * Compose CoAP message from `mg_coap_message`
+ * and send it into `nc` connection.
+ * Return 0 on success. On error, it is a bitmask:
+ *
+ * - `#define MG_COAP_ERROR 0x10000`
+ * - `#define MG_COAP_FORMAT_ERROR (MG_COAP_ERROR | 0x20000)`
+ * - `#define MG_COAP_IGNORE (MG_COAP_ERROR | 0x40000)`
+ * - `#define MG_COAP_NOT_ENOUGH_DATA (MG_COAP_ERROR | 0x80000)`
+ * - `#define MG_COAP_NETWORK_ERROR (MG_COAP_ERROR | 0x100000)`
+ */
+uint32_t mg_coap_send_message(struct mg_connection *nc,
+ struct mg_coap_message *cm);
+
+/*
+ * Compose CoAP acknowledgement from `mg_coap_message`
+ * and send it into `nc` connection.
+ * Return value: see `mg_coap_send_message()`
+ */
+uint32_t mg_coap_send_ack(struct mg_connection *nc, uint16_t msg_id);
+
+/*
+ * Parse COAP message and fills mg_coap_message and returns cm->flags.
+ * This is a helper function.
+ *
+ * NOTE: usually CoAP work over UDP, so lack of data means format error,
+ * but in theory it is possible to use CoAP over TCP (according to RFC)
+ *
+ * The caller have to check results and treat COAP_NOT_ENOUGH_DATA according to
+ * underlying protocol:
+ *
+ * - in case of UDP COAP_NOT_ENOUGH_DATA means COAP_FORMAT_ERROR,
+ * - in case of TCP client can try to receive more data
+ *
+ * Return value: see `mg_coap_send_message()`
+ */
+uint32_t mg_coap_parse(struct mbuf *io, struct mg_coap_message *cm);
+
+/*
+ * Composes CoAP message from mg_coap_message structure.
+ * This is a helper function.
+ * Return value: see `mg_coap_send_message()`
+ */
+uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MG_ENABLE_COAP */
+
+#endif /* CS_MONGOOSE_SRC_COAP_H_ */
diff --git a/modules/ini/Common.cpp b/modules/ini/Common.cpp
index 9e8cb3cf..20289dcc 100644
--- a/modules/ini/Common.cpp
+++ b/modules/ini/Common.cpp
@@ -94,7 +94,7 @@ void DocumentRef::Validate() const
// ------------------------------------------------------------------------------------------------
SQInteger IniResult::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqIniResult");
+ static const SQChar name[] = _SC("SqIniResult");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/ini/Document.cpp b/modules/ini/Document.cpp
index dd4b0055..0a9937b9 100644
--- a/modules/ini/Document.cpp
+++ b/modules/ini/Document.cpp
@@ -15,7 +15,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Document::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqIniDocument");
+ static const SQChar name[] = _SC("SqIniDocument");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/ini/Entries.cpp b/modules/ini/Entries.cpp
index 815a7b10..e0318f44 100644
--- a/modules/ini/Entries.cpp
+++ b/modules/ini/Entries.cpp
@@ -8,7 +8,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Entries::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqIniEntries");
+ static const SQChar name[] = _SC("SqIniEntries");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/ini/Module.cpp b/modules/ini/Module.cpp
index 2e701a7c..112d56a7 100644
--- a/modules/ini/Module.cpp
+++ b/modules/ini/Module.cpp
@@ -96,7 +96,7 @@ void OnSquirrelLoad()
void OnSquirrelTerminate()
{
OutputMessage("Terminating: %s", SQINI_NAME);
- // Release the current database (if any)
+ // Release the current virtual machine, if any
DefaultVM::Set(nullptr);
}
diff --git a/modules/irc/Module.cpp b/modules/irc/Module.cpp
index 46eb75da..f8dcc1ce 100644
--- a/modules/irc/Module.cpp
+++ b/modules/irc/Module.cpp
@@ -98,7 +98,7 @@ void OnSquirrelTerminate()
OutputMessage("Terminating: %s", SQIRC_NAME);
// Terminate all session and release script resources
Session::Terminate();
- // Release the current database (if any)
+ // Release the current virtual machine, if any
DefaultVM::Set(nullptr);
}
diff --git a/modules/irc/Session.cpp b/modules/irc/Session.cpp
index 3eaf8488..b6c633bb 100644
--- a/modules/irc/Session.cpp
+++ b/modules/irc/Session.cpp
@@ -26,7 +26,7 @@ Session::Sessions Session::s_Sessions;
// ------------------------------------------------------------------------------------------------
SQInteger Session::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqIrcSession");
+ static const SQChar name[] = _SC("SqIrcSession");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/json/Common.cpp b/modules/json/Common.cpp
new file mode 100644
index 00000000..8a649854
--- /dev/null
+++ b/modules/json/Common.cpp
@@ -0,0 +1,175 @@
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+#include "Module.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+#include
+#include
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+static SQChar g_Buffer[4096]; // Common buffer to reduce memory allocations.
+
+// ------------------------------------------------------------------------------------------------
+SStr GetTempBuff()
+{
+ return g_Buffer;
+}
+
+// ------------------------------------------------------------------------------------------------
+Uint32 GetTempBuffSize()
+{
+ return sizeof(g_Buffer);
+}
+
+// ------------------------------------------------------------------------------------------------
+void SqThrowF(CSStr str, ...)
+{
+ // Initialize the argument list
+ va_list args;
+ va_start (args, str);
+ // Write the requested contents
+ if (std::vsnprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
+ {
+ std::strcpy(g_Buffer, "Unknown error has occurred");
+ }
+ // Release the argument list
+ va_end(args);
+ // Throw the exception with the resulted message
+ throw Sqrat::Exception(g_Buffer);
+}
+
+// ------------------------------------------------------------------------------------------------
+CSStr FmtStr(CSStr str, ...)
+{
+ // Initialize the argument list
+ va_list args;
+ va_start (args, str);
+ // Write the requested contents
+ if (std::vsnprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
+ {
+ g_Buffer[0] = '\0'; // Make sure the string is terminated
+ }
+ // Release the argument list
+ va_end(args);
+ // Return the data from the buffer
+ return g_Buffer;
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::StackGuard()
+ : m_VM(_SqVM), m_Top(sq_gettop(m_VM))
+{
+ /* ... */
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::StackGuard(HSQUIRRELVM vm)
+ : m_VM(vm), m_Top(sq_gettop(vm))
+{
+ /* ... */
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::~StackGuard()
+{
+ sq_pop(m_VM, sq_gettop(m_VM) - m_Top);
+}
+
+// --------------------------------------------------------------------------------------------
+StackStrF::StackStrF(HSQUIRRELVM vm, SQInteger idx, bool fmt)
+ : mPtr(nullptr)
+ , mLen(-1)
+ , mRes(SQ_OK)
+ , mObj()
+ , mVM(vm)
+{
+ const Int32 top = sq_gettop(vm);
+ // Reset the converted value object
+ sq_resetobject(&mObj);
+ // Was the string or value specified?
+ if (top <= (idx - 1))
+ {
+ mRes = sq_throwerror(vm, "Missing string or value");
+ }
+ // Do we have enough values to call the format function and are we allowed to?
+ else if (top > idx && fmt)
+ {
+ // Pointer to the generated string
+ SStr str = nullptr;
+ // Attempt to generate the specified string format
+ mRes = sqstd_format(vm, idx, &mLen, &str);
+ // Did the format succeeded but ended up with a null string pointer?
+ if (SQ_SUCCEEDED(mRes) && !str)
+ {
+ mRes = sq_throwerror(vm, "Unable to generate the string");
+ }
+ else
+ {
+ mPtr = const_cast< CSStr >(str);
+ }
+ }
+ // Is the value on the stack an actual string?
+ else if (sq_gettype(vm, idx) == OT_STRING)
+ {
+ // Obtain a reference to the string object
+ mRes = sq_getstackobj(vm, idx, &mObj);
+ // Could we retrieve the object from the stack?
+ if (SQ_SUCCEEDED(mRes))
+ {
+ // Keep a strong reference to the object
+ sq_addref(vm, &mObj);
+ // Attempt to retrieve the string value from the stack
+ mRes = sq_getstring(vm, idx, &mPtr);
+ }
+ // Did the retrieval succeeded but ended up with a null string pointer?
+ if (SQ_SUCCEEDED(mRes) && !mPtr)
+ {
+ mRes = sq_throwerror(vm, "Unable to retrieve the string");
+ }
+ }
+ // We have to try and convert it to string
+ else
+ {
+ // Attempt to convert the value from the stack to a string
+ mRes = sq_tostring(vm, idx);
+ // Could we convert the specified value to string?
+ if (SQ_SUCCEEDED(mRes))
+ {
+ // Obtain a reference to the resulted object
+ mRes = sq_getstackobj(vm, -1, &mObj);
+ // Could we retrieve the object from the stack?
+ if (SQ_SUCCEEDED(mRes))
+ {
+ // Keep a strong reference to the object
+ sq_addref(vm, &mObj);
+ // Attempt to obtain the string pointer
+ mRes = sq_getstring(vm, -1, &mPtr);
+ }
+ }
+ // Pop a value from the stack regardless of the result
+ sq_pop(vm, 1);
+ // Did the retrieval succeeded but ended up with a null string pointer?
+ if (SQ_SUCCEEDED(mRes) && !mPtr)
+ {
+ mRes = sq_throwerror(vm, "Unable to retrieve the value");
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+StackStrF::~StackStrF()
+{
+ if (mVM && !sq_isnull(mObj))
+ {
+ sq_release(mVM, &mObj);
+ }
+}
+
+} // Namespace:: SqMod
diff --git a/modules/json/Common.hpp b/modules/json/Common.hpp
new file mode 100644
index 00000000..fecd4a4f
--- /dev/null
+++ b/modules/json/Common.hpp
@@ -0,0 +1,140 @@
+#ifndef _SQJSON_COMMON_HPP_
+#define _SQJSON_COMMON_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "ModBase.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * SOFTWARE INFORMATION
+*/
+#define SQJSON_NAME "Squirrel JSON Module"
+#define SQJSON_AUTHOR "Sandu Liviu Catalin (S.L.C)"
+#define SQJSON_COPYRIGHT "Copyright (C) 2016 Sandu Liviu Catalin"
+#define SQJSON_HOST_NAME "SqModJSONHost"
+#define SQJSON_VERSION 001
+#define SQJSON_VERSION_STR "0.0.1"
+#define SQJSON_VERSION_MAJOR 0
+#define SQJSON_VERSION_MINOR 0
+#define SQJSON_VERSION_PATCH 1
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve the temporary buffer.
+*/
+SStr GetTempBuff();
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve the size of the temporary buffer.
+*/
+Uint32 GetTempBuffSize();
+
+/* ------------------------------------------------------------------------------------------------
+ * Throw a formatted exception.
+*/
+void SqThrowF(CSStr str, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Generate a formatted string.
+*/
+CSStr FmtStr(CSStr str, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Implements RAII to restore the VM stack to it's initial size on function exit.
+*/
+struct StackGuard
+{
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor.
+ */
+ StackGuard();
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ StackGuard(HSQUIRRELVM vm);
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~StackGuard();
+
+private:
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ StackGuard(const StackGuard &);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ StackGuard(StackGuard &&);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ StackGuard & operator = (const StackGuard &);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ StackGuard & operator = (StackGuard &&);
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ HSQUIRRELVM m_VM; // The VM where the stack should be restored.
+ Int32 m_Top; // The top of the stack when this instance was created.
+};
+
+/* ------------------------------------------------------------------------------------------------
+ * Helper structure for retrieving a value from the stack as a string or a formatted string.
+*/
+struct StackStrF
+{
+ // --------------------------------------------------------------------------------------------
+ CSStr mPtr; // Pointer to the C string that was retrieved.
+ SQInteger mLen; // The string length if it could be retrieved.
+ SQRESULT mRes; // The result of the retrieval attempts.
+ HSQOBJECT mObj; // Strong reference to the string object.
+ HSQUIRRELVM mVM; // The associated virtual machine.
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ StackStrF(HSQUIRRELVM vm, SQInteger idx, bool fmt = true);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF(const StackStrF & o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF(StackStrF && o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~StackStrF();
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF & operator = (const StackStrF & o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF & operator = (StackStrF && o) = delete;
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQJSON_COMMON_HPP_
diff --git a/modules/json/Module.cpp b/modules/json/Module.cpp
new file mode 100644
index 00000000..9c171b29
--- /dev/null
+++ b/modules/json/Module.cpp
@@ -0,0 +1,316 @@
+// --------------------------------------------------------------------------------------------
+#include "Module.hpp"
+#include "Common.hpp"
+
+// --------------------------------------------------------------------------------------------
+#include
+#include
+#include
+#include
+
+// --------------------------------------------------------------------------------------------
+#include
+
+// --------------------------------------------------------------------------------------------
+#if defined(WIN32) || defined(_WIN32)
+ #include
+#endif
+
+namespace SqMod {
+
+// --------------------------------------------------------------------------------------------
+PluginFuncs* _Func = nullptr;
+PluginCallbacks* _Clbk = nullptr;
+PluginInfo* _Info = nullptr;
+
+// --------------------------------------------------------------------------------------------
+HSQAPI _SqAPI = nullptr;
+HSQEXPORTS _SqMod = nullptr;
+HSQUIRRELVM _SqVM = nullptr;
+
+/* ------------------------------------------------------------------------------------------------
+ * Bind speciffic functions to certain server events.
+*/
+void BindCallbacks();
+
+/* ------------------------------------------------------------------------------------------------
+ * Undo changes made with BindCallbacks().
+*/
+void UnbindCallbacks();
+
+/* --------------------------------------------------------------------------------------------
+ * Register the module API under the specified virtual machine.
+*/
+void RegisterAPI(HSQUIRRELVM vm);
+
+/* --------------------------------------------------------------------------------------------
+ * Initialize the plugin by obtaining the API provided by the host plugin.
+*/
+void OnSquirrelInitialize()
+{
+ // Attempt to import the plugin API exported by the host plugin
+ _SqMod = sq_api_import(_Func);
+ // Did we failed to obtain the plugin exports?
+ if(!_SqMod)
+ {
+ OutputError("Failed to attach [%s] on host plugin.", SQJSON_NAME);
+ }
+ else
+ {
+ // Obtain the Squirrel API
+ _SqAPI = _SqMod->GetSquirrelAPI();
+ // Expand the Squirrel API into global functions
+ sq_api_expand(_SqAPI);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------
+ * Load the module on the virtual machine provided by the host module.
+*/
+void OnSquirrelLoad()
+{
+ // Make sure that we have a valid plugin API
+ if (!_SqMod)
+ {
+ return; // Unable to proceed.
+ }
+ // Obtain the Squirrel API and VM
+ _SqVM = _SqMod->GetSquirrelVM();
+ // Make sure that a valid virtual machine exists
+ if (!_SqVM)
+ {
+ return; // Unable to proceed.
+ }
+ // Set this as the default database
+ DefaultVM::Set(_SqVM);
+ // Register the module API
+ RegisterAPI(_SqVM);
+ // Notify about the current status
+ OutputMessage("Registered: %s", SQJSON_NAME);
+}
+
+/* --------------------------------------------------------------------------------------------
+ * The virtual machine is about to be terminated and script resources should be released.
+*/
+void OnSquirrelTerminate()
+{
+ OutputMessage("Terminating: %s", SQJSON_NAME);
+ // Release the current virtual machine, if any
+ DefaultVM::Set(nullptr);
+ // Release script resources...
+}
+
+/* --------------------------------------------------------------------------------------------
+ * Validate the module API to make sure we don't run into issues.
+*/
+bool CheckAPIVer(CCStr ver)
+{
+ // Obtain the numeric representation of the API version
+ long vernum = std::strtol(ver, nullptr, 10);
+ // Check against version mismatch
+ if (vernum == SQMOD_API_VER)
+ {
+ return true;
+ }
+ // Log the incident
+ OutputError("API version mismatch on %s", SQJSON_NAME);
+ OutputMessage("=> Requested: %ld Have: %ld", vernum, SQMOD_API_VER);
+ // Invoker should not attempt to communicate through the module API
+ return false;
+}
+
+/* --------------------------------------------------------------------------------------------
+ * React to command sent by other plugins.
+*/
+static uint8_t OnPluginCommand(uint32_t command_identifier, CCStr message)
+{
+ switch(command_identifier)
+ {
+ case SQMOD_INITIALIZE_CMD:
+ if (CheckAPIVer(message))
+ {
+ OnSquirrelInitialize();
+ }
+ break;
+ case SQMOD_LOAD_CMD:
+ OnSquirrelLoad();
+ break;
+ case SQMOD_TERMINATE_CMD:
+ OnSquirrelTerminate();
+ break;
+ default: break;
+ }
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------
+ * The server was initialized and this plugin was loaded successfully.
+*/
+static uint8_t OnServerInitialise()
+{
+ return 1;
+}
+
+static void OnServerShutdown(void)
+{
+ // The server may still send callbacks
+ UnbindCallbacks();
+}
+
+// ------------------------------------------------------------------------------------------------
+void BindCallbacks()
+{
+ _Clbk->OnServerInitialise = OnServerInitialise;
+ _Clbk->OnServerShutdown = OnServerShutdown;
+ _Clbk->OnPluginCommand = OnPluginCommand;
+}
+
+// ------------------------------------------------------------------------------------------------
+void UnbindCallbacks()
+{
+ _Clbk->OnServerInitialise = nullptr;
+ _Clbk->OnServerShutdown = nullptr;
+ _Clbk->OnPluginCommand = nullptr;
+}
+
+// --------------------------------------------------------------------------------------------
+void RegisterAPI(HSQUIRRELVM vm)
+{
+
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputMessageImpl(const char * msg, va_list args)
+{
+#if defined(WIN32) || defined(_WIN32)
+ HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CONSOLE_SCREEN_BUFFER_INFO csb_before;
+ GetConsoleScreenBufferInfo( hstdout, &csb_before);
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN);
+ std::printf("[SQMOD] ");
+
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::vprintf(msg, args);
+ std::puts("");
+
+ SetConsoleTextAttribute(hstdout, csb_before.wAttributes);
+#else
+ std::printf("%c[0;32m[SQMOD]%c[0m", 27, 27);
+ std::vprintf(msg, args);
+ std::puts("");
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputErrorImpl(const char * msg, va_list args)
+{
+#if defined(WIN32) || defined(_WIN32)
+ HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CONSOLE_SCREEN_BUFFER_INFO csb_before;
+ GetConsoleScreenBufferInfo( hstdout, &csb_before);
+ SetConsoleTextAttribute(hstdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::printf("[SQMOD] ");
+
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::vprintf(msg, args);
+ std::puts("");
+
+ SetConsoleTextAttribute(hstdout, csb_before.wAttributes);
+#else
+ std::printf("%c[0;91m[SQMOD]%c[0m", 27, 27);
+ std::vprintf(msg, args);
+ std::puts("");
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputDebug(const char * msg, ...)
+{
+#ifdef _DEBUG
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputMessageImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+#else
+ SQMOD_UNUSED_VAR(msg);
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputMessage(const char * msg, ...)
+{
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputMessageImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputError(const char * msg, ...)
+{
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputErrorImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+}
+
+} // Namespace:: SqMod
+
+// --------------------------------------------------------------------------------------------
+SQMOD_API_EXPORT unsigned int VcmpPluginInit(PluginFuncs* functions, PluginCallbacks* callbacks, PluginInfo* info)
+{
+ using namespace SqMod;
+ // Output plugin header
+ std::puts("");
+ OutputMessage("--------------------------------------------------------------------");
+ OutputMessage("Plugin: %s", SQJSON_NAME);
+ OutputMessage("Author: %s", SQJSON_AUTHOR);
+ OutputMessage("Legal: %s", SQJSON_COPYRIGHT);
+ OutputMessage("--------------------------------------------------------------------");
+ std::puts("");
+ // Attempt to find the host plugin ID
+ int host_plugin_id = functions->FindPlugin((char *)(SQMOD_HOST_NAME));
+ // See if our plugin was loaded after the host plugin
+ if (host_plugin_id < 0)
+ {
+ OutputError("%s could find the host plugin", SQJSON_NAME);
+ // Don't load!
+ return SQMOD_FAILURE;
+ }
+ // Should never reach this point but just in case
+ else if (static_cast< Uint32 >(host_plugin_id) > info->pluginId)
+ {
+ OutputError("%s loaded after the host plugin", SQJSON_NAME);
+ // Don't load!
+ return SQMOD_FAILURE;
+ }
+ // Store server proxies
+ _Func = functions;
+ _Clbk = callbacks;
+ _Info = info;
+ // Assign plugin version
+ _Info->pluginVersion = SQJSON_VERSION;
+ _Info->apiMajorVersion = PLUGIN_API_MAJOR;
+ _Info->apiMinorVersion = PLUGIN_API_MINOR;
+ // Assign the plugin name
+ std::snprintf(_Info->name, sizeof(_Info->name), "%s", SQJSON_HOST_NAME);
+ // Bind callbacks
+ BindCallbacks();
+ // Notify that the plugin was successfully loaded
+ OutputMessage("Successfully loaded %s", SQJSON_NAME);
+ // Dummy spacing
+ std::puts("");
+ // Done!
+ return SQMOD_SUCCESS;
+}
diff --git a/modules/json/Module.hpp b/modules/json/Module.hpp
new file mode 100644
index 00000000..9f392244
--- /dev/null
+++ b/modules/json/Module.hpp
@@ -0,0 +1,41 @@
+#ifndef _SQJSON_MODULE_HPP_
+#define _SQJSON_MODULE_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "SqMod.h"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Proxies to comunicate with the server.
+*/
+extern PluginFuncs* _Func;
+extern PluginCallbacks* _Clbk;
+extern PluginInfo* _Info;
+
+/* ------------------------------------------------------------------------------------------------
+ * Proxies to comunicate with the Squirrel plugin.
+*/
+extern HSQAPI _SqAPI;
+extern HSQEXPORTS _SqMod;
+extern HSQUIRRELVM _SqVM;
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a message only if the _DEBUG was defined.
+*/
+void OutputDebug(const char * msg, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a formatted user message to the console.
+*/
+void OutputMessage(const char * msg, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a formatted error message to the console.
+*/
+void OutputError(const char * msg, ...);
+
+} // Namespace:: SqMod
+
+#endif // _SQJSON_MODULE_HPP_
diff --git a/modules/mg/Common.cpp b/modules/mg/Common.cpp
new file mode 100644
index 00000000..477803a5
--- /dev/null
+++ b/modules/mg/Common.cpp
@@ -0,0 +1,198 @@
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+#include "Module.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+#include
+#include
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+static SQChar g_Buffer[4096]; // Common buffer to reduce memory allocations.
+
+// ------------------------------------------------------------------------------------------------
+SStr GetTempBuff()
+{
+ return g_Buffer;
+}
+
+// ------------------------------------------------------------------------------------------------
+Uint32 GetTempBuffSize()
+{
+ return sizeof(g_Buffer);
+}
+
+// ------------------------------------------------------------------------------------------------
+void SqThrowF(CSStr str, ...)
+{
+ // Initialize the argument list
+ va_list args;
+ va_start (args, str);
+ // Write the requested contents
+ if (std::vsnprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
+ {
+ std::strcpy(g_Buffer, "Unknown error has occurred");
+ }
+ // Release the argument list
+ va_end(args);
+ // Throw the exception with the resulted message
+ throw Sqrat::Exception(g_Buffer);
+}
+
+// ------------------------------------------------------------------------------------------------
+CSStr FmtStr(CSStr str, ...)
+{
+ // Initialize the argument list
+ va_list args;
+ va_start (args, str);
+ // Write the requested contents
+ if (std::vsnprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
+ {
+ g_Buffer[0] = '\0'; // Make sure the string is terminated
+ }
+ // Release the argument list
+ va_end(args);
+ // Return the data from the buffer
+ return g_Buffer;
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::StackGuard()
+ : m_VM(_SqVM), m_Top(sq_gettop(m_VM))
+{
+ /* ... */
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::StackGuard(HSQUIRRELVM vm)
+ : m_VM(vm), m_Top(sq_gettop(vm))
+{
+ /* ... */
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::~StackGuard()
+{
+ sq_pop(m_VM, sq_gettop(m_VM) - m_Top);
+}
+
+// --------------------------------------------------------------------------------------------
+StackStrF::StackStrF(HSQUIRRELVM vm, SQInteger idx, bool fmt)
+ : mPtr(nullptr)
+ , mLen(-1)
+ , mRes(SQ_OK)
+ , mObj()
+ , mVM(vm)
+{
+ const Int32 top = sq_gettop(vm);
+ // Reset the converted value object
+ sq_resetobject(&mObj);
+ // Was the string or value specified?
+ if (top <= (idx - 1))
+ {
+ mRes = sq_throwerror(vm, "Missing string or value");
+ }
+ // Do we have enough values to call the format function and are we allowed to?
+ else if (top > idx && fmt)
+ {
+ // Pointer to the generated string
+ SStr str = nullptr;
+ // Attempt to generate the specified string format
+ mRes = sqstd_format(vm, idx, &mLen, &str);
+ // Did the format succeeded but ended up with a null string pointer?
+ if (SQ_SUCCEEDED(mRes) && !str)
+ {
+ mRes = sq_throwerror(vm, "Unable to generate the string");
+ }
+ else
+ {
+ mPtr = const_cast< CSStr >(str);
+ }
+ }
+ // Is the value on the stack an actual string?
+ else if (sq_gettype(vm, idx) == OT_STRING)
+ {
+ // Obtain a reference to the string object
+ mRes = sq_getstackobj(vm, idx, &mObj);
+ // Could we retrieve the object from the stack?
+ if (SQ_SUCCEEDED(mRes))
+ {
+ // Keep a strong reference to the object
+ sq_addref(vm, &mObj);
+ // Attempt to retrieve the string value from the stack
+ mRes = sq_getstring(vm, idx, &mPtr);
+ }
+ // Did the retrieval succeeded but ended up with a null string pointer?
+ if (SQ_SUCCEEDED(mRes) && !mPtr)
+ {
+ mRes = sq_throwerror(vm, "Unable to retrieve the string");
+ }
+ }
+ // We have to try and convert it to string
+ else
+ {
+ // Attempt to convert the value from the stack to a string
+ mRes = sq_tostring(vm, idx);
+ // Could we convert the specified value to string?
+ if (SQ_SUCCEEDED(mRes))
+ {
+ // Obtain a reference to the resulted object
+ mRes = sq_getstackobj(vm, -1, &mObj);
+ // Could we retrieve the object from the stack?
+ if (SQ_SUCCEEDED(mRes))
+ {
+ // Keep a strong reference to the object
+ sq_addref(vm, &mObj);
+ // Attempt to obtain the string pointer
+ mRes = sq_getstring(vm, -1, &mPtr);
+ }
+ }
+ // Pop a value from the stack regardless of the result
+ sq_pop(vm, 1);
+ // Did the retrieval succeeded but ended up with a null string pointer?
+ if (SQ_SUCCEEDED(mRes) && !mPtr)
+ {
+ mRes = sq_throwerror(vm, "Unable to retrieve the value");
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+Object & NullObject()
+{
+ static Object o;
+ o.Release();
+ return o;
+}
+
+// ------------------------------------------------------------------------------------------------
+Table & NullTable()
+{
+ static Table t;
+ t.Release();
+ return t;
+}
+
+// ------------------------------------------------------------------------------------------------
+Array & NullArray()
+{
+ static Array a;
+ a.Release();
+ return a;
+}
+
+// ------------------------------------------------------------------------------------------------
+Function & NullFunction()
+{
+ static Function f;
+ f.Release();
+ return f;
+}
+
+} // Namespace:: SqMod
diff --git a/modules/mg/Common.hpp b/modules/mg/Common.hpp
new file mode 100644
index 00000000..2ae7f0f6
--- /dev/null
+++ b/modules/mg/Common.hpp
@@ -0,0 +1,229 @@
+#ifndef _SQMG_COMMON_HPP_
+#define _SQMG_COMMON_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "ModBase.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+#include
+#include
+#include
+
+// ------------------------------------------------------------------------------------------------
+#include
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * SOFTWARE INFORMATION
+*/
+#define SQMG_NAME "Squirrel Mongoose Module"
+#define SQMG_AUTHOR "Sandu Liviu Catalin (S.L.C)"
+#define SQMG_COPYRIGHT "Copyright (C) 2016 Sandu Liviu Catalin"
+#define SQMG_HOST_NAME "SqModMongooseHost"
+#define SQMG_VERSION 001
+#define SQMG_VERSION_STR "0.0.1"
+#define SQMG_VERSION_MAJOR 0
+#define SQMG_VERSION_MINOR 0
+#define SQMG_VERSION_PATCH 1
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve the temporary buffer.
+*/
+SStr GetTempBuff();
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve the size of the temporary buffer.
+*/
+Uint32 GetTempBuffSize();
+
+/* ------------------------------------------------------------------------------------------------
+ * Throw a formatted exception.
+*/
+void SqThrowF(CSStr str, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Generate a formatted string.
+*/
+CSStr FmtStr(CSStr str, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Implements RAII to restore the VM stack to it's initial size on function exit.
+*/
+struct StackGuard
+{
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor.
+ */
+ StackGuard();
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ StackGuard(HSQUIRRELVM vm);
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~StackGuard();
+
+private:
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ StackGuard(const StackGuard &);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ StackGuard(StackGuard &&);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ StackGuard & operator = (const StackGuard &);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ StackGuard & operator = (StackGuard &&);
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ HSQUIRRELVM m_VM; // The VM where the stack should be restored.
+ Int32 m_Top; // The top of the stack when this instance was created.
+};
+
+/* ------------------------------------------------------------------------------------------------
+ * Helper structure for retrieving a value from the stack as a string or a formatted string.
+*/
+struct StackStrF
+{
+ // --------------------------------------------------------------------------------------------
+ CSStr mPtr; // Pointer to the C string that was retrieved.
+ SQInteger mLen; // The string length if it could be retrieved.
+ SQRESULT mRes; // The result of the retrieval attempts.
+ HSQOBJECT mObj; // Strong reference to the string object.
+ HSQUIRRELVM mVM; // The associated virtual machine.
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ StackStrF(HSQUIRRELVM vm, SQInteger idx, bool fmt = true);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF(const StackStrF & o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF(StackStrF && o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~StackStrF();
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF & operator = (const StackStrF & o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF & operator = (StackStrF && o) = delete;
+};
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve a reference to a null script object.
+*/
+Object & NullObject();
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve a reference to a null/empty script table.
+*/
+Table & NullTable();
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve a reference to a null/empty script array.
+*/
+Array & NullArray();
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve a reference to a null script function.
+*/
+Function & NullFunction();
+
+// ------------------------------------------------------------------------------------------------
+enum struct HndInit
+{
+ InitHnd = 0
+};
+
+// ------------------------------------------------------------------------------------------------
+class Manager;
+class Connection;
+
+/* ------------------------------------------------------------------------------------------------
+ * Types of events supported by a connection.
+*/
+enum ConnectionEvents
+{
+ MGEV_UNKNOWN = SQMOD_UNKNOWN,
+ MGCE_POLL,
+ MGCE_ACCEPT,
+ MGCE_CONNECT,
+ MGCE_RECV,
+ MGCE_SEND,
+ MGCE_CLOSE,
+ MGCE_TIMER,
+ MGCE_HTTP_REQUEST,
+ MGCE_HTTP_REPLY,
+ MGCE_HTTP_CHUNK,
+ MGCE_SSI_CALL,
+ MGCE_WEBSOCKET_HANDSHAKE_REQUEST,
+ MGCE_WEBSOCKET_HANDSHAKE_DONE,
+ MGCE_WEBSOCKET_FRAME,
+ MGCE_WEBSOCKET_CONTROL_FRAME,
+ MGCE_HTTP_MULTIPART_REQUEST,
+ MGCE_HTTP_PART_BEGIN,
+ MGCE_HTTP_PART_DATA,
+ MGCE_HTTP_PART_END,
+ MGCE_MQTT_CONNECT,
+ MGCE_MQTT_CONNACK,
+ MGCE_MQTT_PUBLISH,
+ MGCE_MQTT_PUBACK,
+ MGCE_MQTT_PUBREC,
+ MGCE_MQTT_PUBREL,
+ MGCE_MQTT_PUBCOMP,
+ MGCE_MQTT_SUBSCRIBE,
+ MGCE_MQTT_SUBACK,
+ MGCE_MQTT_UNSUBSCRIBE,
+ MGCE_MQTT_UNSUBACK,
+ MGCE_MQTT_PINGREQ,
+ MGCE_MQTT_PINGRESP,
+ MGCE_MQTT_DISCONNECT,
+ MGCE_MQTT_CONNACK_ACCEPTED,
+ MGCE_MQTT_CONNACK_UNACCEPTABLE_VERSION,
+ MGCE_MQTT_CONNACK_IDENTIFIER_REJECTED,
+ MGCE_MQTT_CONNACK_SERVER_UNAVAILABLE,
+ MGCE_MQTT_CONNACK_BAD_AUTH,
+ MGCE_MQTT_CONNACK_NOT_AUTHORIZED,
+ MGCE_COAP_CON,
+ MGCE_COAP_NOC,
+ MGCE_COAP_ACK,
+ MGCE_COAP_RST,
+ MGCE_MAX
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMG_COMMON_HPP_
diff --git a/modules/mg/Connection.cpp b/modules/mg/Connection.cpp
new file mode 100644
index 00000000..b34de3b9
--- /dev/null
+++ b/modules/mg/Connection.cpp
@@ -0,0 +1,45 @@
+// ------------------------------------------------------------------------------------------------
+#include "Connection.hpp"
+#include "Manager.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+SQInteger Connection::Typename(HSQUIRRELVM vm)
+{
+ static const SQChar name[] = _SC("SqMgConnection");
+ sq_pushstring(vm, name, sizeof(name));
+ return 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+Int32 Connection::Cmp(const Connection & o) const
+{
+ if (m_Handle == o.m_Handle)
+ {
+ return 0;
+ }
+ else if (m_Handle.HndPtr() > o.m_Handle.HndPtr())
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Connection::Bind(Int32 ev, Object & env, Function & func) const
+{
+
+}
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mg/Connection.hpp b/modules/mg/Connection.hpp
new file mode 100644
index 00000000..99dea39e
--- /dev/null
+++ b/modules/mg/Connection.hpp
@@ -0,0 +1,94 @@
+#ifndef _SQMG_CONNECTION_HPP_
+#define _SQMG_CONNECTION_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Handles.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Allows management of the connection handle.
+*/
+class Connection
+{
+private:
+
+ // --------------------------------------------------------------------------------------------
+ ConnectionHnd m_Handle; // Reference to the connection instance.
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor.
+ */
+ Connection()
+ : m_Handle()
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Handle constructor.
+ */
+ Connection(const ConnectionHnd & c)
+ : m_Handle(c)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ Connection(const Connection & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ Connection(Connection && o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Connection()
+ {
+ // Let the reference handle the deallocation!
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ Connection & operator = (const Connection & o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ Connection & operator = (Connection && o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to compare two instances of this type.
+ */
+ Int32 Cmp(const Connection & o) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to convert an instance of this type to a string.
+ */
+ CSStr ToString() const
+ {
+ return _SC("");
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to retrieve the name from instances of this type.
+ */
+ static SQInteger Typename(HSQUIRRELVM vm);
+
+ /* --------------------------------------------------------------------------------------------
+ * Bind to an event emitted by this connection.
+ */
+ void Bind(Int32 ev, Object & env, Function & func) const;
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMG_CONNECTION_HPP_
diff --git a/modules/mg/HTTP.cpp b/modules/mg/HTTP.cpp
new file mode 100644
index 00000000..01f45a4a
--- /dev/null
+++ b/modules/mg/HTTP.cpp
@@ -0,0 +1,42 @@
+// ------------------------------------------------------------------------------------------------
+#include "HTTP.hpp"
+#include "Manager.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+SQInteger HTTP::Typename(HSQUIRRELVM vm)
+{
+ static const SQChar name[] = _SC("SqMgHTTP");
+ sq_pushstring(vm, name, sizeof(name));
+ return 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+Int32 HTTP::Cmp(const HTTP & o) const
+{
+ if (m_Handle == o.m_Handle)
+ {
+ return 0;
+ }
+ else if (m_Handle.HndPtr() > o.m_Handle.HndPtr())
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mg/HTTP.hpp b/modules/mg/HTTP.hpp
new file mode 100644
index 00000000..a626ecb6
--- /dev/null
+++ b/modules/mg/HTTP.hpp
@@ -0,0 +1,94 @@
+#ifndef _SQMG_HTTP_HPP_
+#define _SQMG_HTTP_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Handles.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Allows management of the connection handle.
+*/
+class HTTP
+{
+private:
+
+ // --------------------------------------------------------------------------------------------
+ ConnectionHnd m_Handle; // Reference to the connection instance.
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor.
+ */
+ HTTP()
+ : m_Handle()
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Handle constructor.
+ */
+ HTTP(const ConnectionHnd & c)
+ : m_Handle(c)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ HTTP(const HTTP & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ HTTP(HTTP && o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~HTTP()
+ {
+ // Let the reference handle the deallocation!
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ HTTP & operator = (const HTTP & o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ HTTP & operator = (HTTP && o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to compare two instances of this type.
+ */
+ Int32 Cmp(const HTTP & o) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to convert an instance of this type to a string.
+ */
+ CSStr ToString() const
+ {
+ return _SC("");
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to retrieve the name from instances of this type.
+ */
+ static SQInteger Typename(HSQUIRRELVM vm);
+
+ /* --------------------------------------------------------------------------------------------
+ *
+ */
+
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMG_HTTP_HPP_
diff --git a/modules/mg/Handles.cpp b/modules/mg/Handles.cpp
new file mode 100644
index 00000000..55a3f40d
--- /dev/null
+++ b/modules/mg/Handles.cpp
@@ -0,0 +1,249 @@
+// ------------------------------------------------------------------------------------------------
+#include "Handles.hpp"
+#include "Module.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+ManagerHnd::Managers ManagerHnd::s_Managers;
+
+// ------------------------------------------------------------------------------------------------
+void ManagerHnd::Validate() const
+{
+ // Is the handle valid?
+ if ((m_Hnd == nullptr))
+ {
+ STHROWF("Invalid Mongoose manager reference");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ManagerHnd::Validate(CSStr act) const
+{
+ // Is the handle valid?
+ if ((m_Hnd == nullptr))
+ {
+ STHROWF("Invalid Mongoose manager reference while: %s", act);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+ManagerHnd::Handle::Handle()
+ : mMgr()
+ , mRef(1)
+{
+ // Initialize event manager object
+ mg_mgr_init(&mMgr, this);
+ // Add this instance to the pool
+ s_Managers.push_back(this);
+}
+
+// ------------------------------------------------------------------------------------------------
+ManagerHnd::Handle::~Handle()
+{
+ // Attempt to release manager resources
+ mg_mgr_free(&mMgr);
+ // Remove this from the pool
+ s_Managers.erase(std::remove(s_Managers.begin(), s_Managers.end(), this), s_Managers.end());
+}
+
+// ------------------------------------------------------------------------------------------------
+Int64 ManagerHnd::Handle::Pool(Int32 milli)
+{
+ // Pool the manager for events and store the returned timestamp
+ std::time_t timestamp = mg_mgr_poll(&mMgr, milli);
+ // Create a tm structure pointing to the unix epoch time
+ std::tm epoch_strt;
+ epoch_strt.tm_sec = 0;
+ epoch_strt.tm_min = 0;
+ epoch_strt.tm_hour = 0;
+ epoch_strt.tm_mday = 1;
+ epoch_strt.tm_mon = 0;
+ epoch_strt.tm_year = 70;
+ epoch_strt.tm_isdst = -1;
+ // Convert it to a time structure
+ std::time_t basetime = std::mktime(&epoch_strt);
+ // Return obtained timestamp
+ return static_cast< Int64 >(std::difftime(timestamp, basetime));
+}
+
+// ------------------------------------------------------------------------------------------------
+void ConnectionHnd::Validate() const
+{
+ // Is the handle valid?
+ if ((m_Hnd == nullptr))
+ {
+ STHROWF("Invalid Mongoose connection reference");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ConnectionHnd::Validate(CSStr act) const
+{
+ // Is the handle valid?
+ if ((m_Hnd == nullptr))
+ {
+ STHROWF("Invalid Mongoose connection reference while: %s", act);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ConnectionHnd::EvFwd(Pointer nc, Int32 ev_type, void * ev_data)
+{
+ if (!nc->user_data)
+ {
+ _SqMod->LogErr("Event dispatched without valid instance");
+ }
+ else
+ {
+ static_cast< Handle * >(nc->user_data)->Event(ev_type, ev_data);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+ConnectionHnd::Handle::Handle(Pointer con)
+ : mCon(con)
+ , mRef(1)
+{
+ // Validate the connection handle
+ if (!mCon)
+ {
+ STHROWF("Invalid Mongoose connection handle");
+ }
+ // Associate with the connection instance
+ mCon->user_data = this;
+}
+
+// ------------------------------------------------------------------------------------------------
+ConnectionHnd::Handle::~Handle()
+{
+ // Tell the manager to close this connection as soon as possible
+ mCon->flags |= MG_F_CLOSE_IMMEDIATELY;
+}
+
+// ------------------------------------------------------------------------------------------------
+void ConnectionHnd::Handle::Event(Int32 ev_type, void * ev_data)
+{
+ switch (ev_type)
+ {
+ case MG_EV_POLL:
+ {
+ if (!mOnPOLL.IsNull())
+ {
+ mOnPOLL.Execute();
+ }
+ } break;
+ case MG_EV_ACCEPT:
+ {
+ if (!mOnACCEPT.IsNull())
+ {
+ mOnACCEPT.Execute();
+ }
+ } break;
+ case MG_EV_CONNECT:
+ {
+ if (!mOnCONNECT.IsNull())
+ {
+ mOnCONNECT.Execute(*static_cast< Int32 * >(ev_data));
+ }
+ } break;
+ case MG_EV_RECV:
+ {
+ if (!mOnRECV.IsNull())
+ {
+ mOnRECV.Execute(*static_cast< Int32 * >(ev_data));
+ }
+ } break;
+ case MG_EV_SEND:
+ {
+ if (!mOnSEND.IsNull())
+ {
+ mOnSEND.Execute(*static_cast< Int32 * >(ev_data));
+ }
+ } break;
+ case MG_EV_CLOSE:
+ {
+ if (!mOnCLOSE.IsNull())
+ {
+ mOnCLOSE.Execute();
+ }
+ } break;
+ case MG_EV_TIMER:
+ {
+ if (!mOnTIMER.IsNull())
+ {
+ mOnTIMER.Execute();
+ }
+ } break;
+ default:
+ {
+ if (!mOnUNKNOWN.IsNull())
+ {
+ mOnUNKNOWN.Execute();
+ }
+ } break;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+Function & ConnectionHnd::Handle::GetEvent(Int32 ev_type)
+{
+ // Identify the requested event type
+ switch (ev_type)
+ {
+ case MGCE_POLL: return mOnPOLL;
+ case MGCE_ACCEPT: return mOnACCEPT;
+ case MGCE_CONNECT: return mOnCONNECT;
+ case MGCE_RECV: return mOnRECV;
+ case MGCE_SEND: return mOnSEND;
+ case MGCE_CLOSE: return mOnCLOSE;
+ case MGCE_TIMER: return mOnTIMER;
+ case MGCE_HTTP_REQUEST: return mOnHTTP_REQUEST;
+ case MGCE_HTTP_REPLY: return mOnHTTP_REPLY;
+ case MGCE_HTTP_CHUNK: return mOnHTTP_CHUNK;
+ case MGCE_SSI_CALL: return mOnSSI_CALL;
+ case MGCE_WEBSOCKET_HANDSHAKE_REQUEST: return mOnWEBSOCKET_HANDSHAKE_REQUEST;
+ case MGCE_WEBSOCKET_HANDSHAKE_DONE: return mOnWEBSOCKET_HANDSHAKE_DONE;
+ case MGCE_WEBSOCKET_FRAME: return mOnWEBSOCKET_FRAME;
+ case MGCE_WEBSOCKET_CONTROL_FRAME: return mOnWEBSOCKET_CONTROL_FRAME;
+ case MGCE_HTTP_MULTIPART_REQUEST: return mOnHTTP_MULTIPART_REQUEST;
+ case MGCE_HTTP_PART_BEGIN: return mOnHTTP_PART_BEGIN;
+ case MGCE_HTTP_PART_DATA: return mOnHTTP_PART_DATA;
+ case MGCE_HTTP_PART_END: return mOnHTTP_PART_END;
+ case MGCE_MQTT_CONNECT: return mOnMQTT_CONNECT;
+ case MGCE_MQTT_CONNACK: return mOnMQTT_CONNACK;
+ case MGCE_MQTT_PUBLISH: return mOnMQTT_PUBLISH;
+ case MGCE_MQTT_PUBACK: return mOnMQTT_PUBACK;
+ case MGCE_MQTT_PUBREC: return mOnMQTT_PUBREC;
+ case MGCE_MQTT_PUBREL: return mOnMQTT_PUBREL;
+ case MGCE_MQTT_PUBCOMP: return mOnMQTT_PUBCOMP;
+ case MGCE_MQTT_SUBSCRIBE: return mOnMQTT_SUBSCRIBE;
+ case MGCE_MQTT_SUBACK: return mOnMQTT_SUBACK;
+ case MGCE_MQTT_UNSUBSCRIBE: return mOnMQTT_UNSUBSCRIBE;
+ case MGCE_MQTT_UNSUBACK: return mOnMQTT_UNSUBACK;
+ case MGCE_MQTT_PINGREQ: return mOnMQTT_PINGREQ;
+ case MGCE_MQTT_PINGRESP: return mOnMQTT_PINGRESP;
+ case MGCE_MQTT_DISCONNECT: return mOnMQTT_DISCONNECT;
+ case MGCE_MQTT_CONNACK_ACCEPTED: return mOnMQTT_CONNACK_ACCEPTED;
+ case MGCE_MQTT_CONNACK_UNACCEPTABLE_VERSION: return mOnMQTT_CONNACK_UNACCEPTABLE_VERSION;
+ case MGCE_MQTT_CONNACK_IDENTIFIER_REJECTED: return mOnMQTT_CONNACK_IDENTIFIER_REJECTED;
+ case MGCE_MQTT_CONNACK_SERVER_UNAVAILABLE: return mOnMQTT_CONNACK_SERVER_UNAVAILABLE;
+ case MGCE_MQTT_CONNACK_BAD_AUTH: return mOnMQTT_CONNACK_BAD_AUTH;
+ case MGCE_MQTT_CONNACK_NOT_AUTHORIZED: return mOnMQTT_CONNACK_NOT_AUTHORIZED;
+ case MGCE_COAP_CON: return mOnCOAP_CON;
+ case MGCE_COAP_NOC: return mOnCOAP_NOC;
+ case MGCE_COAP_ACK: return mOnCOAP_ACK;
+ case MGCE_COAP_RST: return mOnCOAP_RST;
+ case MGEV_UNKNOWN: return mOnUNKNOWN;
+ default: break;
+ }
+ // Default to a null function
+ return NullFunction();
+}
+
+} // Namespace:: SqMod
diff --git a/modules/mg/Handles.hpp b/modules/mg/Handles.hpp
new file mode 100644
index 00000000..3ec6ed5b
--- /dev/null
+++ b/modules/mg/Handles.hpp
@@ -0,0 +1,626 @@
+#ifndef _SQMG_HANDLES_HPP_
+#define _SQMG_HANDLES_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Manages a reference counted manager and its associated resources.
+*/
+class ManagerHnd
+{
+ // --------------------------------------------------------------------------------------------
+ friend class Manager;
+ friend class Connection;
+
+public:
+
+ // --------------------------------------------------------------------------------------------
+ typedef mg_mgr Type; // The managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type* Pointer; // Pointer to the managed type.
+ typedef const Type* ConstPtr; // Constant pointer to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type& Reference; // Reference to the managed type.
+ typedef const Type& ConstRef; // Constant reference to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef unsigned int Counter; // Reference counter type.
+
+ /* --------------------------------------------------------------------------------------------
+ * Validate the manager handle and throw an error if invalid.
+ */
+ void Validate() const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Validate the manager handle and throw an error if invalid.
+ */
+ void Validate(CSStr act) const;
+
+protected:
+
+ /* --------------------------------------------------------------------------------------------
+ * The structure that holds the data associated with a physically simulated world.
+ */
+ struct Handle
+ {
+ // ----------------------------------------------------------------------------------------
+ Type mMgr; // The managed manager structure.
+ Counter mRef; // Reference count to the managed handles.
+
+ /* ----------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ Handle();
+
+ /* ----------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Handle();
+
+ /* ----------------------------------------------------------------------------------------
+ * Pool the managed connections for events.
+ */
+ Int64 Pool(Int32 milli);
+ };
+
+ // --------------------------------------------------------------------------------------------
+ typedef std::vector< Handle * > Managers; // List of created managers.
+
+ // --------------------------------------------------------------------------------------------
+ static Managers s_Managers;
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ Handle* m_Hnd;
+
+ /* --------------------------------------------------------------------------------------------
+ * Grab a strong reference to a connection handle.
+ */
+ void Grab()
+ {
+ if (m_Hnd)
+ {
+ ++(m_Hnd->mRef);
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Drop a strong reference to a connection handle.
+ */
+ void Drop()
+ {
+ if (m_Hnd && --(m_Hnd->mRef) == 0)
+ {
+ delete m_Hnd; // Let the destructor take care of cleaning up (if necessary)
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ ManagerHnd(HndInit init)
+ : m_Hnd(init == HndInit::InitHnd ? new Handle() : nullptr)
+ {
+ /* ... */
+ }
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor (null).
+ */
+ ManagerHnd()
+ : m_Hnd(nullptr)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Handle constructor.
+ */
+ ManagerHnd(Handle * hnd)
+ : m_Hnd(hnd)
+ {
+ Grab();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ ManagerHnd(const ManagerHnd & o)
+ : m_Hnd(o.m_Hnd)
+ {
+ Grab();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ ManagerHnd(ManagerHnd && o)
+ : m_Hnd(o.m_Hnd)
+ {
+ o.m_Hnd = nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~ManagerHnd()
+ {
+ Drop();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ ManagerHnd & operator = (const ManagerHnd & o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ Drop();
+ m_Hnd = o.m_Hnd;
+ Grab();
+ }
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ ManagerHnd & operator = (ManagerHnd && o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ m_Hnd = o.m_Hnd;
+ o.m_Hnd = nullptr;
+ }
+
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an equality comparison between two connection handles.
+ */
+ bool operator == (const ManagerHnd & o) const
+ {
+ return (m_Hnd == o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an inequality comparison between two connection handles.
+ */
+ bool operator != (const ManagerHnd & o) const
+ {
+ return (m_Hnd != o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to boolean for use in boolean operations.
+ */
+ operator bool () const
+ {
+ return (m_Hnd != nullptr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer ()
+ {
+ return (m_Hnd != nullptr) ? &(m_Hnd->mMgr) : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer () const
+ {
+ return (m_Hnd != nullptr) ? &(m_Hnd->mMgr) : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Reference ()
+ {
+ assert((m_Hnd != nullptr));
+ return (m_Hnd->mMgr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator ConstRef () const
+ {
+ assert((m_Hnd != nullptr));
+ return (m_Hnd->mMgr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Member operator for dereferencing the managed pointer.
+ */
+ Handle * operator -> () const
+ {
+ assert(m_Hnd != nullptr);
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Indirection operator for obtaining a reference of the managed pointer.
+ */
+ Handle & operator * () const
+ {
+ assert(m_Hnd != nullptr);
+ return *m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr()
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr() const
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the number of active references to the managed instance.
+ */
+ Counter Count() const
+ {
+ return (m_Hnd != nullptr) ? m_Hnd->mRef : 0;
+ }
+};
+
+/* ------------------------------------------------------------------------------------------------
+ * Manages a reference counted connection and its associated resources.
+*/
+class ConnectionHnd
+{
+ // --------------------------------------------------------------------------------------------
+ friend class Manager;
+ friend class Connection;
+
+public:
+
+ // --------------------------------------------------------------------------------------------
+ typedef mg_connection Type; // The managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type* Pointer; // Pointer to the managed type.
+ typedef const Type* ConstPtr; // Constant pointer to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type& Reference; // Reference to the managed type.
+ typedef const Type& ConstRef; // Constant reference to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef unsigned int Counter; // Reference counter type.
+
+ /* --------------------------------------------------------------------------------------------
+ * Validate the connection handle and throw an error if invalid.
+ */
+ void Validate() const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Validate the connection handle and throw an error if invalid.
+ */
+ void Validate(CSStr act) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Forward connection events to the associated instance.
+ */
+ static void EvFwd(Pointer nc, Int32 ev_type, void * ev_data);
+
+protected:
+
+ /* --------------------------------------------------------------------------------------------
+ * The structure that holds the data associated with a physically simulated world.
+ */
+ struct Handle
+ {
+ // ----------------------------------------------------------------------------------------
+ Pointer mCon; // The managed connection structure.
+ Counter mRef; // Reference count to the managed handles.
+
+ // ----------------------------------------------------------------------------------------
+ Function mOnPOLL;
+ Function mOnACCEPT;
+ Function mOnCONNECT;
+ Function mOnRECV;
+ Function mOnSEND;
+ Function mOnCLOSE;
+ Function mOnTIMER;
+ Function mOnHTTP_REQUEST;
+ Function mOnHTTP_REPLY;
+ Function mOnHTTP_CHUNK;
+ Function mOnSSI_CALL;
+ Function mOnWEBSOCKET_HANDSHAKE_REQUEST;
+ Function mOnWEBSOCKET_HANDSHAKE_DONE;
+ Function mOnWEBSOCKET_FRAME;
+ Function mOnWEBSOCKET_CONTROL_FRAME;
+ Function mOnHTTP_MULTIPART_REQUEST;
+ Function mOnHTTP_PART_BEGIN;
+ Function mOnHTTP_PART_DATA;
+ Function mOnHTTP_PART_END;
+ Function mOnMQTT_CONNECT;
+ Function mOnMQTT_CONNACK;
+ Function mOnMQTT_PUBLISH;
+ Function mOnMQTT_PUBACK;
+ Function mOnMQTT_PUBREC;
+ Function mOnMQTT_PUBREL;
+ Function mOnMQTT_PUBCOMP;
+ Function mOnMQTT_SUBSCRIBE;
+ Function mOnMQTT_SUBACK;
+ Function mOnMQTT_UNSUBSCRIBE;
+ Function mOnMQTT_UNSUBACK;
+ Function mOnMQTT_PINGREQ;
+ Function mOnMQTT_PINGRESP;
+ Function mOnMQTT_DISCONNECT;
+ Function mOnMQTT_CONNACK_ACCEPTED;
+ Function mOnMQTT_CONNACK_UNACCEPTABLE_VERSION;
+ Function mOnMQTT_CONNACK_IDENTIFIER_REJECTED;
+ Function mOnMQTT_CONNACK_SERVER_UNAVAILABLE;
+ Function mOnMQTT_CONNACK_BAD_AUTH;
+ Function mOnMQTT_CONNACK_NOT_AUTHORIZED;
+ Function mOnCOAP_CON;
+ Function mOnCOAP_NOC;
+ Function mOnCOAP_ACK;
+ Function mOnCOAP_RST;
+ Function mOnUNKNOWN;
+
+ /* ----------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ Handle(Pointer con);
+
+ /* ----------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Handle();
+
+ /* ----------------------------------------------------------------------------------------
+ * Handle a certain event for this connection.
+ */
+ void Event(Int32 ev_type, void * ev_data);
+
+ /* ----------------------------------------------------------------------------------------
+ * Retrieve the sript callback associated with a certain identifier.
+ */
+ Function & GetEvent(Int32 ev_type);
+ };
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ Handle* m_Hnd;
+
+ /* --------------------------------------------------------------------------------------------
+ * Grab a strong reference to a connection handle.
+ */
+ void Grab()
+ {
+ if (m_Hnd)
+ {
+ ++(m_Hnd->mRef);
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Drop a strong reference to a connection handle.
+ */
+ void Drop()
+ {
+ if (m_Hnd && --(m_Hnd->mRef) == 0)
+ {
+ delete m_Hnd; // Let the destructor take care of cleaning up (if necessary)
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ ConnectionHnd(Pointer con)
+ : m_Hnd(new Handle(con))
+ {
+ /* ... */
+ }
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor (null).
+ */
+ ConnectionHnd()
+ : m_Hnd(nullptr)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Handle constructor.
+ */
+ ConnectionHnd(Handle * hnd)
+ : m_Hnd(hnd)
+ {
+ Grab();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ ConnectionHnd(const ConnectionHnd & o)
+ : m_Hnd(o.m_Hnd)
+ {
+ Grab();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ ConnectionHnd(ConnectionHnd && o)
+ : m_Hnd(o.m_Hnd)
+ {
+ o.m_Hnd = nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~ConnectionHnd()
+ {
+ Drop();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ ConnectionHnd & operator = (const ConnectionHnd & o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ Drop();
+ m_Hnd = o.m_Hnd;
+ Grab();
+ }
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ ConnectionHnd & operator = (ConnectionHnd && o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ m_Hnd = o.m_Hnd;
+ o.m_Hnd = nullptr;
+ }
+
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an equality comparison between two connection handles.
+ */
+ bool operator == (const ConnectionHnd & o) const
+ {
+ return (m_Hnd == o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an inequality comparison between two connection handles.
+ */
+ bool operator != (const ConnectionHnd & o) const
+ {
+ return (m_Hnd != o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to boolean for use in boolean operations.
+ */
+ operator bool () const
+ {
+ return (m_Hnd != nullptr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer ()
+ {
+ return (m_Hnd != nullptr) ? (m_Hnd->mCon) : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer () const
+ {
+ return (m_Hnd != nullptr) ? (m_Hnd->mCon) : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Reference ()
+ {
+ assert((m_Hnd != nullptr));
+ return *(m_Hnd->mCon);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator ConstRef () const
+ {
+ assert((m_Hnd != nullptr));
+ return *(m_Hnd->mCon);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Member operator for dereferencing the managed pointer.
+ */
+ Handle * operator -> () const
+ {
+ assert(m_Hnd != nullptr);
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Indirection operator for obtaining a reference of the managed pointer.
+ */
+ Handle & operator * () const
+ {
+ assert(m_Hnd != nullptr);
+ return *m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr()
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr() const
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the number of active references to the managed instance.
+ */
+ Counter Count() const
+ {
+ return (m_Hnd != nullptr) ? m_Hnd->mRef : 0;
+ }
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMG_HANDLES_HPP_
diff --git a/modules/mg/Manager.cpp b/modules/mg/Manager.cpp
new file mode 100644
index 00000000..26250a56
--- /dev/null
+++ b/modules/mg/Manager.cpp
@@ -0,0 +1,155 @@
+// -----------------------------------------------------------------------------------------------
+#include "Manager.hpp"
+#include "Connection.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+#include
+#include
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+SQInteger Manager::Typename(HSQUIRRELVM vm)
+{
+ static const SQChar name[] = _SC("SqMgManager");
+ sq_pushstring(vm, name, sizeof(name));
+ return 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+Int32 Manager::Cmp(const Manager & o) const
+{
+ if (m_Handle == o.m_Handle)
+ {
+ return 0;
+ }
+ else if (m_Handle.HndPtr() > o.m_Handle.HndPtr())
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::Bind(CSStr addr) const
+{
+ return Bind(addr, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::Bind(CSStr addr, Uint32 flags) const
+{
+ // Validate the managed handle
+ m_Handle.Validate();
+ // Validate the specified address
+ if (!addr)
+ {
+ STHROWF("Invalid bind address: null");
+ }
+ // Create a binding options structure
+ mg_bind_opts opts;
+ // Initialize the structure to 0
+ std::memset(&opts, 0, sizeof(mg_bind_opts));
+ // Assign the specified flags
+ opts.flags = flags;
+ // Attemmt to create the connection handle
+ mg_connection * connection = mg_bind_opt(m_Handle, addr, ConnectionHnd::EvFwd, opts);
+ // Validate the obtained connection handle
+ if (!connection)
+ {
+ STHROWF("Unable to create listening connection [%s]", *opts.error_string);
+ }
+ // Return the obtained connection
+ return Connection(ConnectionHnd(connection));
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::Connect(CSStr addr) const
+{
+ return Connect(addr, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::Connect(CSStr addr, Uint32 flags) const
+{
+ // Validate the managed handle
+ m_Handle.Validate();
+ // Validate the specified address
+ if (!addr)
+ {
+ STHROWF("Invalid bind address: null");
+ }
+ // Create a binding options structure
+ mg_connect_opts opts;
+ // Initialize the structure to 0
+ std::memset(&opts, 0, sizeof(mg_connect_opts));
+ // Assign the specified flags
+ opts.flags = flags;
+ // Attemmt to create the connection handle
+ mg_connection * connection = mg_connect_opt(m_Handle, addr, ConnectionHnd::EvFwd, opts);
+ // Validate the obtained connection handle
+ if (!connection)
+ {
+ STHROWF("Unable to create listening connection [%s]", *opts.error_string);
+ }
+ // Return the obtained connection
+ return Connection(ConnectionHnd(connection));
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectWS(CSStr url) const
+{
+ return ConnectWS(url, nullptr, nullptr, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectWS(CSStr url, CSStr protocol) const
+{
+ return ConnectWS(url, protocol, nullptr, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectWS(CSStr url, CSStr protocol, CSStr headers) const
+{
+ return ConnectWS(url, protocol, headers, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectWS(CSStr url, CSStr protocol, CSStr headers, Uint32 flags) const
+{
+ return Connection();
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectHTTP(CSStr url) const
+{
+ return ConnectHTTP(url, nullptr, nullptr, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectHTTP(CSStr url, CSStr headers) const
+{
+ return ConnectHTTP(url, headers, nullptr, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectHTTP(CSStr url, CSStr headers, CSStr post) const
+{
+ return ConnectHTTP(url, headers, post, 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection Manager::ConnectHTTP(CSStr url, CSStr headers, CSStr post, Uint32 flags) const
+{
+ return Connection();
+}
+
+} // Namespace:: SqMod
diff --git a/modules/mg/Manager.hpp b/modules/mg/Manager.hpp
new file mode 100644
index 00000000..21290e6e
--- /dev/null
+++ b/modules/mg/Manager.hpp
@@ -0,0 +1,149 @@
+#ifndef _SQMG_MANAGER_HPP_
+#define _SQMG_MANAGER_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Handles.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Allows management of the connection manager.
+*/
+class Manager
+{
+private:
+
+ // --------------------------------------------------------------------------------------------
+ ManagerHnd m_Handle; // Reference to the manager instance.
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor.
+ */
+ Manager()
+ : m_Handle(HndInit::InitHnd)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Handle constructor.
+ */
+ Manager(const ManagerHnd & m)
+ : m_Handle(m)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ Manager(const Manager & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ Manager(Manager && o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Manager()
+ {
+ // Let the reference handle the deallocation!
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ Manager & operator = (const Manager & o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ Manager & operator = (Manager && o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to compare two instances of this type.
+ */
+ Int32 Cmp(const Manager & o) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to convert an instance of this type to a string.
+ */
+ CSStr ToString() const
+ {
+ return _SC("");
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to retrieve the name from instances of this type.
+ */
+ static SQInteger Typename(HSQUIRRELVM vm);
+
+ /* --------------------------------------------------------------------------------------------
+ * Create listening connection.
+ */
+ Connection Bind(CSStr addr) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Create listening connection.
+ */
+ Connection Bind(CSStr addr, Uint32 flags) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Connect to a remote host.
+ */
+ Connection Connect(CSStr addr) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Connect to a remote host.
+ */
+ Connection Connect(CSStr addr, Uint32 flags) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates an outbound WebSocket connection.
+ */
+ Connection ConnectWS(CSStr url) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates an outbound WebSocket connection.
+ */
+ Connection ConnectWS(CSStr url, CSStr protocol) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates an outbound WebSocket connection.
+ */
+ Connection ConnectWS(CSStr url, CSStr protocol, CSStr headers) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates an outbound WebSocket connection.
+ */
+ Connection ConnectWS(CSStr url, CSStr protocol, CSStr headers, Uint32 flags) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates outbound HTTP connection.
+ */
+ Connection ConnectHTTP(CSStr url) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates outbound HTTP connection.
+ */
+ Connection ConnectHTTP(CSStr url, CSStr headers) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates outbound HTTP connection.
+ */
+ Connection ConnectHTTP(CSStr url, CSStr headers, CSStr post) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Creates outbound HTTP connection.
+ */
+ Connection ConnectHTTP(CSStr url, CSStr headers, CSStr post, Uint32 flags) const;
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMG_MANAGER_HPP_
diff --git a/modules/mg/Message.cpp b/modules/mg/Message.cpp
new file mode 100644
index 00000000..e69de29b
diff --git a/modules/mg/Message.hpp b/modules/mg/Message.hpp
new file mode 100644
index 00000000..e69de29b
diff --git a/modules/mg/Module.cpp b/modules/mg/Module.cpp
new file mode 100644
index 00000000..9af014cf
--- /dev/null
+++ b/modules/mg/Module.cpp
@@ -0,0 +1,427 @@
+// --------------------------------------------------------------------------------------------
+#include "Module.hpp"
+#include "Common.hpp"
+#include "Manager.hpp"
+#include "Connection.hpp"
+
+// --------------------------------------------------------------------------------------------
+#include
+#include
+#include
+#include
+
+// --------------------------------------------------------------------------------------------
+#include
+
+// --------------------------------------------------------------------------------------------
+#if defined(WIN32) || defined(_WIN32)
+ #include
+#endif
+
+namespace SqMod {
+
+// --------------------------------------------------------------------------------------------
+PluginFuncs* _Func = nullptr;
+PluginCallbacks* _Clbk = nullptr;
+PluginInfo* _Info = nullptr;
+
+// --------------------------------------------------------------------------------------------
+HSQAPI _SqAPI = nullptr;
+HSQEXPORTS _SqMod = nullptr;
+HSQUIRRELVM _SqVM = nullptr;
+
+/* ------------------------------------------------------------------------------------------------
+ * Bind speciffic functions to certain server events.
+*/
+void BindCallbacks();
+
+/* ------------------------------------------------------------------------------------------------
+ * Undo changes made with BindCallbacks().
+*/
+void UnbindCallbacks();
+
+/* --------------------------------------------------------------------------------------------
+ * Register the module API under the specified virtual machine.
+*/
+void RegisterAPI(HSQUIRRELVM vm);
+
+/* --------------------------------------------------------------------------------------------
+ * Initialize the plugin by obtaining the API provided by the host plugin.
+*/
+void OnSquirrelInitialize()
+{
+ // Attempt to import the plugin API exported by the host plugin
+ _SqMod = sq_api_import(_Func);
+ // Did we failed to obtain the plugin exports?
+ if(!_SqMod)
+ {
+ OutputError("Failed to attach [%s] on host plugin.", SQMG_NAME);
+ }
+ else
+ {
+ // Obtain the Squirrel API
+ _SqAPI = _SqMod->GetSquirrelAPI();
+ // Expand the Squirrel API into global functions
+ sq_api_expand(_SqAPI);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------
+ * Load the module on the virtual machine provided by the host module.
+*/
+void OnSquirrelLoad()
+{
+ // Make sure that we have a valid plugin API
+ if (!_SqMod)
+ {
+ return; // Unable to proceed.
+ }
+ // Obtain the Squirrel API and VM
+ _SqVM = _SqMod->GetSquirrelVM();
+ // Make sure that a valid virtual machine exists
+ if (!_SqVM)
+ {
+ return; // Unable to proceed.
+ }
+ // Set this as the default database
+ DefaultVM::Set(_SqVM);
+ // Register the module API
+ RegisterAPI(_SqVM);
+ // Notify about the current status
+ OutputMessage("Registered: %s", SQMG_NAME);
+}
+
+/* --------------------------------------------------------------------------------------------
+ * The virtual machine is about to be terminated and script resources should be released.
+*/
+void OnSquirrelTerminate()
+{
+ OutputMessage("Terminating: %s", SQMG_NAME);
+ // Release null objects just in case
+ NullObject().Release();
+ NullTable().Release();
+ NullArray().Release();
+ NullFunction().ReleaseGently();
+ // Release the current virtual machine, if any
+ DefaultVM::Set(nullptr);
+ // Release script resources...
+}
+
+/* --------------------------------------------------------------------------------------------
+ * Validate the module API to make sure we don't run into issues.
+*/
+bool CheckAPIVer(CCStr ver)
+{
+ // Obtain the numeric representation of the API version
+ long vernum = std::strtol(ver, nullptr, 10);
+ // Check against version mismatch
+ if (vernum == SQMOD_API_VER)
+ {
+ return true;
+ }
+ // Log the incident
+ OutputError("API version mismatch on %s", SQMG_NAME);
+ OutputMessage("=> Requested: %ld Have: %ld", vernum, SQMOD_API_VER);
+ // Invoker should not attempt to communicate through the module API
+ return false;
+}
+
+/* --------------------------------------------------------------------------------------------
+ * React to command sent by other plugins.
+*/
+static uint8_t OnPluginCommand(uint32_t command_identifier, CCStr message)
+{
+ switch(command_identifier)
+ {
+ case SQMOD_INITIALIZE_CMD:
+ if (CheckAPIVer(message))
+ {
+ OnSquirrelInitialize();
+ }
+ break;
+ case SQMOD_LOAD_CMD:
+ OnSquirrelLoad();
+ break;
+ case SQMOD_TERMINATE_CMD:
+ OnSquirrelTerminate();
+ break;
+ default: break;
+ }
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------
+ * The server was initialized and this plugin was loaded successfully.
+*/
+static uint8_t OnServerInitialise()
+{
+ return 1;
+}
+
+static void OnServerShutdown(void)
+{
+ // The server may still send callbacks
+ UnbindCallbacks();
+}
+
+// ------------------------------------------------------------------------------------------------
+void BindCallbacks()
+{
+ _Clbk->OnServerInitialise = OnServerInitialise;
+ _Clbk->OnServerShutdown = OnServerShutdown;
+ _Clbk->OnPluginCommand = OnPluginCommand;
+}
+
+// ------------------------------------------------------------------------------------------------
+void UnbindCallbacks()
+{
+ _Clbk->OnServerInitialise = nullptr;
+ _Clbk->OnServerShutdown = nullptr;
+ _Clbk->OnPluginCommand = nullptr;
+}
+
+// --------------------------------------------------------------------------------------------
+void RegisterAPI(HSQUIRRELVM vm)
+{
+ Table mgns(vm);
+
+ mgns.Bind(_SC("Manager"), Class< Manager >(vm, _SC("SqMgManager"))
+ // Constructors
+ .Ctor()
+ .Ctor< const Manager & >()
+ // Core Metamethods
+ .Func(_SC("_cmp"), &Manager::Cmp)
+ .SquirrelFunc(_SC("_typename"), &Manager::Typename)
+ .Func(_SC("_tostring"), &Manager::ToString)
+ // Properties
+ //.Prop(_SC("Valid"), &Manager::IsValid)
+ // Member Methods
+ //.Func(_SC("Check"), &Manager::Check)
+ // Overloaded Methods
+ .Overload< Connection (Manager::*)(CSStr) const >(_SC("Bind"), &Manager::Bind)
+ .Overload< Connection (Manager::*)(CSStr, Uint32) const >(_SC("Bind"), &Manager::Bind)
+ .Overload< Connection (Manager::*)(CSStr) const >(_SC("Connect"), &Manager::Connect)
+ .Overload< Connection (Manager::*)(CSStr, Uint32) const >(_SC("Connect"), &Manager::Connect)
+ );
+
+ mgns.Bind(_SC("Connection"), Class< Connection >(vm, _SC("SqMgConnection"))
+ // Constructors
+ .Ctor()
+ .Ctor< const Connection & >()
+ // Core Metamethods
+ .Func(_SC("_cmp"), &Connection::Cmp)
+ .SquirrelFunc(_SC("_typename"), &Connection::Typename)
+ .Func(_SC("_tostring"), &Connection::ToString)
+ // Properties
+ //.Prop(_SC("Valid"), &Connection::IsValid)
+ // Member Methods
+ //.Func(_SC("Check"), &Connection::Check)
+ );
+
+ RootTable(vm).Bind(_SC("SqMg"), mgns);
+
+ ConstTable(vm).Enum(_SC("EMgF"), Enumeration(vm)
+ .Const(_SC("LISTENING"), MG_F_LISTENING)
+ .Const(_SC("UDP"), MG_F_UDP)
+ .Const(_SC("RESOLVING"), MG_F_RESOLVING)
+ .Const(_SC("CONNECTING"), MG_F_CONNECTING)
+ .Const(_SC("SSL_HANDSHAKE_DONE"), MG_F_SSL_HANDSHAKE_DONE)
+ .Const(_SC("WANT_READ"), MG_F_WANT_READ)
+ .Const(_SC("WANT_WRITE"), MG_F_WANT_WRITE)
+ .Const(_SC("IS_WEBSOCKET"), MG_F_IS_WEBSOCKET)
+ .Const(_SC("SEND_AND_CLOSE"), MG_F_SEND_AND_CLOSE)
+ .Const(_SC("CLOSE_IMMEDIATELY"), MG_F_CLOSE_IMMEDIATELY)
+ .Const(_SC("WEBSOCKET_NO_DEFRAG"), MG_F_WEBSOCKET_NO_DEFRAG)
+ .Const(_SC("DELETE_CHUNK"), MG_F_DELETE_CHUNK)
+ .Const(_SC("USER_1"), MG_F_USER_1)
+ .Const(_SC("USER_2"), MG_F_USER_2)
+ .Const(_SC("USER_3"), MG_F_USER_3)
+ .Const(_SC("USER_4"), MG_F_USER_4)
+ .Const(_SC("USER_5"), MG_F_USER_5)
+ .Const(_SC("USER_6"), MG_F_USER_6)
+ );
+
+ ConstTable(vm).Enum(_SC("MgEv"), Enumeration(vm)
+ .Const(_SC("UNKNOWN"), static_cast< Int32 >(MGEV_UNKNOWN))
+ .Const(_SC("POLL"), static_cast< Int32 >(MGCE_POLL))
+ .Const(_SC("ACCEPT"), static_cast< Int32 >(MGCE_ACCEPT))
+ .Const(_SC("CONNECT"), static_cast< Int32 >(MGCE_CONNECT))
+ .Const(_SC("RECV"), static_cast< Int32 >(MGCE_RECV))
+ .Const(_SC("SEND"), static_cast< Int32 >(MGCE_SEND))
+ .Const(_SC("CLOSE"), static_cast< Int32 >(MGCE_CLOSE))
+ .Const(_SC("TIMER"), static_cast< Int32 >(MGCE_TIMER))
+ .Const(_SC("HTTP_REQUEST"), static_cast< Int32 >(MGCE_HTTP_REQUEST))
+ .Const(_SC("HTTP_REPLY"), static_cast< Int32 >(MGCE_HTTP_REPLY))
+ .Const(_SC("HTTP_CHUNK"), static_cast< Int32 >(MGCE_HTTP_CHUNK))
+ .Const(_SC("SSI_CALL"), static_cast< Int32 >(MGCE_SSI_CALL))
+ .Const(_SC("WEBSOCKET_HANDSHAKE_REQUEST"), static_cast< Int32 >(MGCE_WEBSOCKET_HANDSHAKE_REQUEST))
+ .Const(_SC("WEBSOCKET_HANDSHAKE_DONE"), static_cast< Int32 >(MGCE_WEBSOCKET_HANDSHAKE_DONE))
+ .Const(_SC("WEBSOCKET_FRAME"), static_cast< Int32 >(MGCE_WEBSOCKET_FRAME))
+ .Const(_SC("WEBSOCKET_CONTROL_FRAME"), static_cast< Int32 >(MGCE_WEBSOCKET_CONTROL_FRAME))
+ .Const(_SC("HTTP_MULTIPART_REQUEST"), static_cast< Int32 >(MGCE_HTTP_MULTIPART_REQUEST))
+ .Const(_SC("HTTP_PART_BEGIN"), static_cast< Int32 >(MGCE_HTTP_PART_BEGIN))
+ .Const(_SC("HTTP_PART_DATA"), static_cast< Int32 >(MGCE_HTTP_PART_DATA))
+ .Const(_SC("HTTP_PART_END"), static_cast< Int32 >(MGCE_HTTP_PART_END))
+ .Const(_SC("MQTT_CONNECT"), static_cast< Int32 >(MGCE_MQTT_CONNECT))
+ .Const(_SC("MQTT_CONNACK"), static_cast< Int32 >(MGCE_MQTT_CONNACK))
+ .Const(_SC("MQTT_PUBLISH"), static_cast< Int32 >(MGCE_MQTT_PUBLISH))
+ .Const(_SC("MQTT_PUBACK"), static_cast< Int32 >(MGCE_MQTT_PUBACK))
+ .Const(_SC("MQTT_PUBREC"), static_cast< Int32 >(MGCE_MQTT_PUBREC))
+ .Const(_SC("MQTT_PUBREL"), static_cast< Int32 >(MGCE_MQTT_PUBREL))
+ .Const(_SC("MQTT_PUBCOMP"), static_cast< Int32 >(MGCE_MQTT_PUBCOMP))
+ .Const(_SC("MQTT_SUBSCRIBE"), static_cast< Int32 >(MGCE_MQTT_SUBSCRIBE))
+ .Const(_SC("MQTT_SUBACK"), static_cast< Int32 >(MGCE_MQTT_SUBACK))
+ .Const(_SC("MQTT_UNSUBSCRIBE"), static_cast< Int32 >(MGCE_MQTT_UNSUBSCRIBE))
+ .Const(_SC("MQTT_UNSUBACK"), static_cast< Int32 >(MGCE_MQTT_UNSUBACK))
+ .Const(_SC("MQTT_PINGREQ"), static_cast< Int32 >(MGCE_MQTT_PINGREQ))
+ .Const(_SC("MQTT_PINGRESP"), static_cast< Int32 >(MGCE_MQTT_PINGRESP))
+ .Const(_SC("MQTT_DISCONNECT"), static_cast< Int32 >(MGCE_MQTT_DISCONNECT))
+ .Const(_SC("MQTT_CONNACK_ACCEPTED"), static_cast< Int32 >(MGCE_MQTT_CONNACK_ACCEPTED))
+ .Const(_SC("MQTT_CONNACK_UNACCEPTABLE_VERSION"), static_cast< Int32 >(MGCE_MQTT_CONNACK_UNACCEPTABLE_VERSION))
+ .Const(_SC("MQTT_CONNACK_IDENTIFIER_REJECTED"), static_cast< Int32 >(MGCE_MQTT_CONNACK_IDENTIFIER_REJECTED))
+ .Const(_SC("MQTT_CONNACK_SERVER_UNAVAILABLE"), static_cast< Int32 >(MGCE_MQTT_CONNACK_SERVER_UNAVAILABLE))
+ .Const(_SC("MQTT_CONNACK_BAD_AUTH"), static_cast< Int32 >(MGCE_MQTT_CONNACK_BAD_AUTH))
+ .Const(_SC("MQTT_CONNACK_NOT_AUTHORIZED"), static_cast< Int32 >(MGCE_MQTT_CONNACK_NOT_AUTHORIZED))
+ .Const(_SC("COAP_CON"), static_cast< Int32 >(MGCE_COAP_CON))
+ .Const(_SC("COAP_NOC"), static_cast< Int32 >(MGCE_COAP_NOC))
+ .Const(_SC("COAP_ACK"), static_cast< Int32 >(MGCE_COAP_ACK))
+ .Const(_SC("COAP_RST"), static_cast< Int32 >(MGCE_COAP_RST))
+ .Const(_SC("MAX"), static_cast< Int32 >(MGCE_MAX))
+ );
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputMessageImpl(const char * msg, va_list args)
+{
+#if defined(WIN32) || defined(_WIN32)
+ HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CONSOLE_SCREEN_BUFFER_INFO csb_before;
+ GetConsoleScreenBufferInfo( hstdout, &csb_before);
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN);
+ std::printf("[SQMOD] ");
+
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::vprintf(msg, args);
+ std::puts("");
+
+ SetConsoleTextAttribute(hstdout, csb_before.wAttributes);
+#else
+ std::printf("%c[0;32m[SQMOD]%c[0m", 27, 27);
+ std::vprintf(msg, args);
+ std::puts("");
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputErrorImpl(const char * msg, va_list args)
+{
+#if defined(WIN32) || defined(_WIN32)
+ HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CONSOLE_SCREEN_BUFFER_INFO csb_before;
+ GetConsoleScreenBufferInfo( hstdout, &csb_before);
+ SetConsoleTextAttribute(hstdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::printf("[SQMOD] ");
+
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::vprintf(msg, args);
+ std::puts("");
+
+ SetConsoleTextAttribute(hstdout, csb_before.wAttributes);
+#else
+ std::printf("%c[0;91m[SQMOD]%c[0m", 27, 27);
+ std::vprintf(msg, args);
+ std::puts("");
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputDebug(const char * msg, ...)
+{
+#ifdef _DEBUG
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputMessageImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+#else
+ SQMOD_UNUSED_VAR(msg);
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputMessage(const char * msg, ...)
+{
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputMessageImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputError(const char * msg, ...)
+{
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputErrorImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+}
+
+} // Namespace:: SqMod
+
+// --------------------------------------------------------------------------------------------
+SQMOD_API_EXPORT unsigned int VcmpPluginInit(PluginFuncs* functions, PluginCallbacks* callbacks, PluginInfo* info)
+{
+ using namespace SqMod;
+ // Output plugin header
+ std::puts("");
+ OutputMessage("--------------------------------------------------------------------");
+ OutputMessage("Plugin: %s", SQMG_NAME);
+ OutputMessage("Author: %s", SQMG_AUTHOR);
+ OutputMessage("Legal: %s", SQMG_COPYRIGHT);
+ OutputMessage("--------------------------------------------------------------------");
+ std::puts("");
+ // Attempt to find the host plugin ID
+ int host_plugin_id = functions->FindPlugin((char *)(SQMOD_HOST_NAME));
+ // See if our plugin was loaded after the host plugin
+ if (host_plugin_id < 0)
+ {
+ OutputError("%s could find the host plugin", SQMG_NAME);
+ // Don't load!
+ return SQMOD_FAILURE;
+ }
+ // Should never reach this point but just in case
+ else if (static_cast< Uint32 >(host_plugin_id) > info->pluginId)
+ {
+ OutputError("%s loaded after the host plugin", SQMG_NAME);
+ // Don't load!
+ return SQMOD_FAILURE;
+ }
+ // Store server proxies
+ _Func = functions;
+ _Clbk = callbacks;
+ _Info = info;
+ // Assign plugin version
+ _Info->pluginVersion = SQMG_VERSION;
+ _Info->apiMajorVersion = PLUGIN_API_MAJOR;
+ _Info->apiMinorVersion = PLUGIN_API_MINOR;
+ // Assign the plugin name
+ std::snprintf(_Info->name, sizeof(_Info->name), "%s", SQMG_HOST_NAME);
+ // Bind callbacks
+ BindCallbacks();
+ // Notify that the plugin was successfully loaded
+ OutputMessage("Successfully loaded %s", SQMG_NAME);
+ // Dummy spacing
+ std::puts("");
+ // Done!
+ return SQMOD_SUCCESS;
+}
diff --git a/modules/mg/Module.hpp b/modules/mg/Module.hpp
new file mode 100644
index 00000000..68b1336c
--- /dev/null
+++ b/modules/mg/Module.hpp
@@ -0,0 +1,41 @@
+#ifndef _SQMG_MODULE_HPP_
+#define _SQMG_MODULE_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "SqMod.h"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Proxies to comunicate with the server.
+*/
+extern PluginFuncs* _Func;
+extern PluginCallbacks* _Clbk;
+extern PluginInfo* _Info;
+
+/* ------------------------------------------------------------------------------------------------
+ * Proxies to comunicate with the Squirrel plugin.
+*/
+extern HSQAPI _SqAPI;
+extern HSQEXPORTS _SqMod;
+extern HSQUIRRELVM _SqVM;
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a message only if the _DEBUG was defined.
+*/
+void OutputDebug(const char * msg, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a formatted user message to the console.
+*/
+void OutputMessage(const char * msg, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a formatted error message to the console.
+*/
+void OutputError(const char * msg, ...);
+
+} // Namespace:: SqMod
+
+#endif // _SQMG_MODULE_HPP_
diff --git a/modules/mg/Options.cpp b/modules/mg/Options.cpp
new file mode 100644
index 00000000..c6b9403e
--- /dev/null
+++ b/modules/mg/Options.cpp
@@ -0,0 +1,9 @@
+// ------------------------------------------------------------------------------------------------
+#include "Options.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace:: SqMod
diff --git a/modules/mg/Options.hpp b/modules/mg/Options.hpp
new file mode 100644
index 00000000..0976d3f3
--- /dev/null
+++ b/modules/mg/Options.hpp
@@ -0,0 +1,12 @@
+#ifndef _OPTIONS_HPP_
+#define _OPTIONS_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+} // Namespace:: SqMod
+
+#endif // _OPTIONS_HPP_
diff --git a/modules/mg/String.cpp b/modules/mg/String.cpp
new file mode 100644
index 00000000..e69de29b
diff --git a/modules/mg/String.hpp b/modules/mg/String.hpp
new file mode 100644
index 00000000..e69de29b
diff --git a/modules/mg/WebSocket.cpp b/modules/mg/WebSocket.cpp
new file mode 100644
index 00000000..cc8964a1
--- /dev/null
+++ b/modules/mg/WebSocket.cpp
@@ -0,0 +1,42 @@
+// ------------------------------------------------------------------------------------------------
+#include "WebSocket.hpp"
+#include "Manager.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+SQInteger WebSocket::Typename(HSQUIRRELVM vm)
+{
+ static const SQChar name[] = _SC("SqMgWebSocket");
+ sq_pushstring(vm, name, sizeof(name));
+ return 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+Int32 WebSocket::Cmp(const WebSocket & o) const
+{
+ if (m_Handle == o.m_Handle)
+ {
+ return 0;
+ }
+ else if (m_Handle.HndPtr() > o.m_Handle.HndPtr())
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mg/WebSocket.hpp b/modules/mg/WebSocket.hpp
new file mode 100644
index 00000000..46eb7d3d
--- /dev/null
+++ b/modules/mg/WebSocket.hpp
@@ -0,0 +1,94 @@
+#ifndef _SQMG_WEBSOCKET_HPP_
+#define _SQMG_WEBSOCKET_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Handles.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Allows management of the connection handle.
+*/
+class WebSocket
+{
+private:
+
+ // --------------------------------------------------------------------------------------------
+ ConnectionHnd m_Handle; // Reference to the connection instance.
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor.
+ */
+ WebSocket()
+ : m_Handle()
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Handle constructor.
+ */
+ WebSocket(const ConnectionHnd & c)
+ : m_Handle(c)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ WebSocket(const WebSocket & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ WebSocket(WebSocket && o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~WebSocket()
+ {
+ // Let the reference handle the deallocation!
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ WebSocket & operator = (const WebSocket & o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ WebSocket & operator = (WebSocket && o);
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to compare two instances of this type.
+ */
+ Int32 Cmp(const WebSocket & o) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to convert an instance of this type to a string.
+ */
+ CSStr ToString() const
+ {
+ return _SC("");
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Used by the script engine to retrieve the name from instances of this type.
+ */
+ static SQInteger Typename(HSQUIRRELVM vm);
+
+ /* --------------------------------------------------------------------------------------------
+ *
+ */
+
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMG_WEBSOCKET_HPP_
diff --git a/modules/mmdb/Module.cpp b/modules/mmdb/Module.cpp
index 7442cf0e..242406c2 100644
--- a/modules/mmdb/Module.cpp
+++ b/modules/mmdb/Module.cpp
@@ -51,7 +51,9 @@ void OnSquirrelInitialize()
_SqMod = sq_api_import(_Func);
// Did we failed to obtain the plugin exports?
if(!_SqMod)
+ {
OutputError("Failed to attach [%s] on host plugin.", SQMMDB_NAME);
+ }
else
{
// Obtain the Squirrel API
@@ -68,12 +70,16 @@ void OnSquirrelLoad()
{
// Make sure that we have a valid plugin API
if (!_SqMod)
- return; /* Unable to proceed. */
+ {
+ return; // Unable to proceed.
+ }
// Obtain the Squirrel API and VM
_SqVM = _SqMod->GetSquirrelVM();
// Make sure that a valid virtual machine exists
if (!_SqVM)
- return; /* Unable to proceed. */
+ {
+ return; // Unable to proceed.
+ }
// Set this as the default database
DefaultVM::Set(_SqVM);
// Register the module API
@@ -88,7 +94,7 @@ void OnSquirrelLoad()
void OnSquirrelTerminate()
{
OutputMessage("Terminating: %s", SQMMDB_NAME);
- // Release the current database (if any)
+ // Release the current virtual machine, if any
DefaultVM::Set(nullptr);
// Release script resources...
}
@@ -102,7 +108,9 @@ bool CheckAPIVer(CCStr ver)
long vernum = strtol(ver, nullptr, 10);
// Check against version mismatch
if (vernum == SQMOD_API_VER)
+ {
return true;
+ }
// Log the incident
OutputError("API version mismatch on %s", SQMMDB_NAME);
OutputMessage("=> Requested: %ld Have: %ld", vernum, SQMOD_API_VER);
@@ -119,7 +127,9 @@ static uint8_t OnPluginCommand(uint32_t command_identifier, CCStr message)
{
case SQMOD_INITIALIZE_CMD:
if (CheckAPIVer(message))
+ {
OnSquirrelInitialize();
+ }
break;
case SQMOD_LOAD_CMD:
OnSquirrelLoad();
diff --git a/modules/mysql/Account.cpp b/modules/mysql/Account.cpp
new file mode 100644
index 00000000..63d1839c
--- /dev/null
+++ b/modules/mysql/Account.cpp
@@ -0,0 +1,122 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+Account::Account(CSStr host, CSStr user, CSStr pass, CSStr schema, Uint16 port, CSStr socket)
+ : m_Host(host ? host : _SC(""))
+ , m_User(user ? user : _SC(""))
+ , m_Pass(pass ? pass : _SC(""))
+ , m_Port(port)
+ , m_Schema(schema ? schema : _SC(""))
+ , m_Socket(socket ? socket : _SC(""))
+ , m_SSL_Key()
+ , m_SSL_Cert()
+ , m_SSL_CA()
+ , m_SSL_CA_Path()
+ , m_SSL_Cipher()
+ , m_Autocommit()
+ , m_Options()
+{
+ // Attempt to extract the port number from the host
+ String::size_type pos = m_Host.find(':');
+ // Was it specified in the host
+ if (pos != String::npos)
+ {
+ try
+ {
+ // Attempt to extract the numeric value
+ ULong num = std::stol(m_Host.substr(pos + 1));
+ // Is the port number withing range?
+ if (num > 0xFFFF)
+ {
+ SHROWF("Port number out of range: %lu");
+ }
+ // Assign the extracted port number
+ m_Port = static_cast< Uint16 >(num);
+ // Remove everything after the port number
+ m_Host = m_Host.substr(0, pos);
+ }
+ catch (const std::exception & e)
+ {
+ SHROWF("Cannot extract the port number [%s]", e.what());
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Account::SetSSL(CSStr key, CSStr cert, CSStr ca, CSStr ca_path, CSStr cipher)
+{
+ if (!key || *key == '\0')
+ {
+ m_SSL_Key.clear();
+ m_SSL_Cert.clear();
+ m_SSL_CA.clear();
+ m_SSL_CA_Path.clear();
+ m_SSL_Cipher.clear();
+ }
+ else
+ {
+ m_SSL_Key.assign(key ? key : _SC(""));
+ m_SSL_Cert.assign(cert ? cert : _SC(""));
+ m_SSL_CA.assign(ca ? ca : _SC(""));
+ m_SSL_CA_Path.assign(ca_path ? ca_path : _SC(""));
+ m_SSL_Cipher.assign(cipher ? cipher : _SC(""));
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+Table Account::GetOptions() const
+{
+ // Allocate an empty table
+ Table tbl(DefaultVM::Get());
+ // Insert every option into the table
+ for (const auto & opt : m_Options)
+ {
+ tbl.SetValue(opt.first.c_str(), opt.second);
+ }
+ // Return the resulted table
+ return tbl;
+}
+
+// ------------------------------------------------------------------------------------------------
+const String & Account::GetOption(CSStr name) const
+{
+ // Make sure the specified name is valid
+ if (!name || *name == '\0')
+ {
+ SHROWF("Invalid or empty option name");
+ }
+ // Attempt to find the requested option
+ const Options::const_iterator itr = m_Options.find(name);
+ // Return the result
+ return (itr == m_Options.cend()) ? s_String : itr->second;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Account::SetOption(CSStr name, CSStr value)
+{
+ // Make sure the specified name is valid
+ if (!name || *name == '\0')
+ {
+ SHROWF("Invalid or empty option name");
+ }
+ // Assign the specified value
+ m_Options[name] = value ? value : _SC("");
+}
+
+// ------------------------------------------------------------------------------------------------
+void Account::RemoveOption(CSStr name) const
+{
+ // Make sure the specified name is valid
+ if (!name || *name == '\0')
+ {
+ SHROWF("Invalid or empty option name");
+ }
+ // Erase the specified value
+ m_Options.erase(name);
+}
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Account.hpp b/modules/mysql/Account.hpp
new file mode 100644
index 00000000..b1f222cb
--- /dev/null
+++ b/modules/mysql/Account.hpp
@@ -0,0 +1,272 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Helper class containing shared connection information to avoid repetition.
+*/
+class Account
+{
+public:
+
+ // --------------------------------------------------------------------------------------------
+ typedef std::unordered_map< String, String > Options;
+
+ /* --------------------------------------------------------------------------------------------
+ * Base Constructor.
+ */
+ Account(CSStr host, CSStr user)
+ : Account(host, user, _SC(""), _SC(""), 3306, _SC(""))
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base Constructor.
+ */
+ Account(CSStr host, CSStr user, CSStr pass)
+ : Account(host, user, pass, _SC(""), 3306, _SC(""))
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base Constructor.
+ */
+ Account(CSStr host, CSStr user, CSStr pass, CSStr schema)
+ : Account(host, user, pass, schema, 3306, _SC(""))
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base Constructor.
+ */
+ Account(CSStr host, CSStr user, CSStr pass, CSStr schema, Uint16 port)
+ : Account(host, user, pass, schema, 3306, _SC(""))
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base Constructor.
+ */
+ Account(CSStr host, CSStr user, CSStr pass, CSStr schema, Uint16 port, CSStr socket);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor (disabled).
+ */
+ Account(const Account & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor (disabled).
+ */
+ Account(Account && o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Account() = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator (disabled).
+ */
+ Account & operator = (const Account & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator (disabled).
+ */
+ Account & operator = (Account && o) = default;
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ String m_Host; // Host address.
+ String m_User; // User name.
+ String m_Pass; // User password.
+ Uint16 m_Port; // Server port.
+
+ // --------------------------------------------------------------------------------------------
+ String m_Schema; // Server schema.
+ String m_Socket; // Unix socket.
+
+ // --------------------------------------------------------------------------------------------
+ String m_SSL_Key; // SSL key.
+ String m_SSL_Cert; // SSL certificate.
+ String m_SSL_CA; // SSL certificate authority.
+ String m_SSL_CA_Path; // SSL certificate authority path.
+ String m_SSL_Cipher; // SSL Cipher.
+
+ // --------------------------------------------------------------------------------------------
+ bool m_Autocommit; // Toggle autocommit.
+
+ // --------------------------------------------------------------------------------------------
+ Options m_Options; // Option container.
+
+ // --------------------------------------------------------------------------------------------
+ static const String s_String; // Dummy used to return a reference to a null string.
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the host address specified during creation.
+ */
+ const String & GetHost() const
+ {
+ return m_Host;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the user name specified during creation.
+ */
+ const String & GetUser() const
+ {
+ return m_User;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the password specified during creation.
+ */
+ const String & GetPass() const
+ {
+ return m_Pass;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the server port specified during creation.
+ */
+ const Uint16 GetPort() const
+ {
+ return m_Port;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the server schema.
+ */
+ const String & GetSchema() const
+ {
+ return m_Schema;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Set the server schema.
+ */
+ const String & GetSocket() const
+ {
+ return m_Socket;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the SSL key.
+ */
+ const String & GetSSL_Key() const
+ {
+ return m_SSL_Key;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the SSL certificate.
+ */
+ const String & GetSSL_Cert() const
+ {
+ return m_SSL_Cipher;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the SSL certificate authority.
+ */
+ const String & GetSSL_CA() const
+ {
+ return m_SSL_CA;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the SSL certificate authority path.
+ */
+ const String & GetSSL_CA_Path() const
+ {
+ return m_SSL_CA_Path;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the SSL cipher.
+ */
+ const String & GetSSL_Cipher() const
+ {
+ return m_SSL_Cipher;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Set the SSL information.
+ */
+ void SetSSL(CSStr key, CSStr cert, CSStr ca, CSStr ca_path, CSStr cipher);
+
+ /* --------------------------------------------------------------------------------------------
+ * See whether autocommit is enabled or not.
+ */
+ bool GetAutocommit() const
+ {
+ return m_Autocommit;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Set whether autocommit should be enabled or not.
+ */
+ void SetAutocommit(bool toggle)
+ {
+ m_Autocommit = toggle;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the entire options container.
+ */
+ Table GetOptions() const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Get a value from the options container.
+ */
+ const String & GetOption(CSStr name) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Set a value in the options container.
+ */
+ void SetOption(CSStr name, CSStr value);
+
+ /* --------------------------------------------------------------------------------------------
+ * Remove a value from the options container.
+ */
+ void RemoveOption(CSStr name) const;
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the number of values in the options container.
+ */
+ Uint32 OptionsCount() const
+ {
+ return static_cast< Uint32 >(m_Options.size());
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * See whether the options container is empty or not.
+ */
+ bool OptionsEmpty() const
+ {
+ return m_Options.empty();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Remove all the options from the options container.
+ */
+ void OptionsClear()
+ {
+ m_Options.clear();
+ }
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Bind.cpp b/modules/mysql/Bind.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Bind.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Bind.hpp b/modules/mysql/Bind.hpp
new file mode 100644
index 00000000..16bc1d05
--- /dev/null
+++ b/modules/mysql/Bind.hpp
@@ -0,0 +1,17 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Column.cpp b/modules/mysql/Column.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Column.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Column.hpp b/modules/mysql/Column.hpp
new file mode 100644
index 00000000..16bc1d05
--- /dev/null
+++ b/modules/mysql/Column.hpp
@@ -0,0 +1,17 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Common.cpp b/modules/mysql/Common.cpp
new file mode 100644
index 00000000..959ef08c
--- /dev/null
+++ b/modules/mysql/Common.cpp
@@ -0,0 +1,73 @@
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+#include "Module.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+#include
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+static SQChar g_Buffer[4096]; // Common buffer to reduce memory allocations.
+
+// ------------------------------------------------------------------------------------------------
+SStr GetTempBuff()
+{
+ return g_Buffer;
+}
+
+// ------------------------------------------------------------------------------------------------
+Uint32 GetTempBuffSize()
+{
+ return sizeof(g_Buffer);
+}
+
+// ------------------------------------------------------------------------------------------------
+void SqThrowF(CSStr str, ...)
+{
+ // Initialize the argument list
+ va_list args;
+ va_start (args, str);
+ // Write the requested contents
+ if (snprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
+ strcpy(g_Buffer, "Unknown error has occurred");
+ // Release the argument list
+ va_end(args);
+ // Throw the exception with the resulted message
+ throw Sqrat::Exception(g_Buffer);
+}
+
+// ------------------------------------------------------------------------------------------------
+CSStr FmtStr(CSStr str, ...)
+{
+ // Initialize the argument list
+ va_list args;
+ va_start (args, str);
+ // Write the requested contents
+ if (snprintf(g_Buffer, sizeof(g_Buffer), str, args) < 0)
+ g_Buffer[0] = 0; /* make sure the string is terminated */
+ // Release the argument list
+ va_end(args);
+ // Return the data from the buffer
+ return g_Buffer;
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::StackGuard(HSQUIRRELVM vm)
+ : m_Top(sq_gettop(vm)), m_VM(vm)
+{
+ /* ... */
+}
+
+// ------------------------------------------------------------------------------------------------
+StackGuard::~StackGuard()
+{
+ sq_pop(m_VM, sq_gettop(m_VM) - m_Top);
+}
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Common.hpp b/modules/mysql/Common.hpp
new file mode 100644
index 00000000..82275f65
--- /dev/null
+++ b/modules/mysql/Common.hpp
@@ -0,0 +1,153 @@
+#ifndef _SQMYSQL_COMMON_HPP_
+#define _SQMYSQL_COMMON_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "ModBase.hpp"
+
+/* ------------------------------------------------------------------------------------------------
+ * Forward declaration of various MySQL types to avoid including the header where not necessary.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct st_mysql MYSQL;
+typedef struct st_mysql_stmt MYSQL_STMT;
+typedef struct st_mysql_bind MYSQL_BIND;
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * SOFTWARE INFORMATION
+*/
+#define SQMYSQL_NAME "Squirrel MySQL Module"
+#define SQMYSQL_AUTHOR "Sandu Liviu Catalin (S.L.C)"
+#define SQMYSQL_COPYRIGHT "Copyright (C) 2016 Sandu Liviu Catalin"
+#define SQMYSQL_HOST_NAME "SqModMySQLHost"
+#define SQMYSQL_VERSION 001
+#define SQMYSQL_VERSION_STR "0.0.1"
+#define SQMYSQL_VERSION_MAJOR 0
+#define SQMYSQL_VERSION_MINOR 0
+#define SQMYSQL_VERSION_PATCH 1
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve the temporary buffer.
+*/
+SStr GetTempBuff();
+
+/* ------------------------------------------------------------------------------------------------
+ * Retrieve the size of the temporary buffer.
+*/
+Uint32 GetTempBuffSize();
+
+/* ------------------------------------------------------------------------------------------------
+ * Throw a formatted exception.
+*/
+void SqThrowF(CSStr str, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Generate a formatted string.
+*/
+CSStr FmtStr(CSStr str, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Implements RAII to restore the VM stack to it's initial size on function exit.
+*/
+struct StackGuard
+{
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor.
+ */
+ StackGuard();
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ StackGuard(HSQUIRRELVM vm);
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~StackGuard();
+
+private:
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ StackGuard(const StackGuard &);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ StackGuard(StackGuard &&);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ StackGuard & operator = (const StackGuard &);
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ StackGuard & operator = (StackGuard &&);
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ HSQUIRRELVM m_VM; // The VM where the stack should be restored.
+ Int32 m_Top; // The top of the stack when this instance was created.
+};
+
+/* ------------------------------------------------------------------------------------------------
+ * Helper structure for retrieving a value from the stack as a string or a formatted string.
+*/
+struct StackStrF
+{
+ // --------------------------------------------------------------------------------------------
+ CSStr mPtr; // Pointer to the C string that was retrieved.
+ SQInteger mLen; // The string length if it could be retrieved.
+ SQRESULT mRes; // The result of the retrieval attempts.
+ HSQOBJECT mObj; // Strong reference to the string object.
+ HSQUIRRELVM mVM; // The associated virtual machine.
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ StackStrF(HSQUIRRELVM vm, SQInteger idx, bool fmt = true);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF(const StackStrF & o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF(StackStrF && o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~StackStrF();
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF & operator = (const StackStrF & o) = delete;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor. (disabled)
+ */
+ StackStrF & operator = (StackStrF && o) = delete;
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_COMMON_HPP_
diff --git a/modules/mysql/Connection.cpp b/modules/mysql/Connection.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Connection.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Connection.hpp b/modules/mysql/Connection.hpp
new file mode 100644
index 00000000..03442d96
--- /dev/null
+++ b/modules/mysql/Connection.hpp
@@ -0,0 +1,164 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * ...
+*/
+class Connection
+{
+
+protected:
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ Connection(const Account & acc);
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ Connection(const Connection & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ Connection(Connection && o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Connection() = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ Connection & operator = (const Connection & o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ Connection & operator = (Connection && o) = default;
+
+ /* --------------------------------------------------------------------------------------------
+ * Validate the managed connection handle and throw exception if it doesn't exist.
+ */
+ void Validate() const;
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ ConnHnd m_Handle; // Handle to the actual database connection.
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the hosted connection handle.
+ */
+ operator ConnHnd ()
+ {
+ return m_Handle;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the hosted connection handle.
+ */
+ operator const ConnHnd & () const
+ {
+ return m_Handle;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Attempt to connect to the database.
+ */
+ bool Connect();
+
+ /* --------------------------------------------------------------------------------------------
+ * Disconnect from the currently connected database.
+ */
+ void Disconnect();
+
+ /* --------------------------------------------------------------------------------------------
+ * See whether a successful connection was made or not.
+ */
+ bool Connected() const
+ {
+ return (m_Handle != nullptr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the account used to make the connection.
+ */
+ const Account::Ref & GetAccount() const
+ {
+ return m_Account;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the server schema.
+ */
+ const String & GetSchema() const
+ {
+ return m_Schema;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Set the server schema.
+ */
+ void SetSchema(const String & schema);
+
+ /* --------------------------------------------------------------------------------------------
+ * Get the character set.
+ */
+ const String & GetCharset() const
+ {
+ return m_Charset;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Set the character set.
+ */
+ void SetCharset(const String & charset);
+
+ /* --------------------------------------------------------------------------------------------
+ * See whether auto-commit is enabled or not.
+ */
+ bool GetAutocommit() const
+ {
+ return m_Autocommit;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Set whether auto-commit should be enabled or not.
+ */
+ void SetAutocommit(bool toggle);
+
+ /* --------------------------------------------------------------------------------------------
+ * Execute a query on the server.
+ */
+ Uint64 Execute(const String & query);
+
+ /* --------------------------------------------------------------------------------------------
+ * Execute a query on the server.
+ */
+ Uint64 Insert(const String & query);
+
+ /* --------------------------------------------------------------------------------------------
+ * Execute a query on the server.
+ */
+ Uint64 Query(const String & query);
+
+ /* --------------------------------------------------------------------------------------------
+ *
+ */
+ StatementRef GetStatement(const String & query);
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Datetime.cpp b/modules/mysql/Datetime.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Datetime.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Datetime.hpp b/modules/mysql/Datetime.hpp
new file mode 100644
index 00000000..16bc1d05
--- /dev/null
+++ b/modules/mysql/Datetime.hpp
@@ -0,0 +1,17 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Decimal.cpp b/modules/mysql/Decimal.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Decimal.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Decimal.hpp b/modules/mysql/Decimal.hpp
new file mode 100644
index 00000000..16bc1d05
--- /dev/null
+++ b/modules/mysql/Decimal.hpp
@@ -0,0 +1,17 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Handles.cpp b/modules/mysql/Handles.cpp
new file mode 100644
index 00000000..2ae452bd
--- /dev/null
+++ b/modules/mysql/Handles.cpp
@@ -0,0 +1,25 @@
+// ------------------------------------------------------------------------------------------------
+#include "Handles.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+ConnHnd::Handle::~Handle()
+{
+
+}
+
+// ------------------------------------------------------------------------------------------------
+void ConnHnd::Handle::Create(CSStr host, CSStr user, CSStr pass, CSStr schema, Uint16 port, CSStr socket)
+{
+
+}
+
+// ------------------------------------------------------------------------------------------------
+Int32 ConnHnd::Handle::Flush(Uint32 num, Object & env, Function & func)
+{
+ return 0;
+}
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Handles.hpp b/modules/mysql/Handles.hpp
new file mode 100644
index 00000000..6490f8cf
--- /dev/null
+++ b/modules/mysql/Handles.hpp
@@ -0,0 +1,691 @@
+#ifndef _SQMYSQL_HANDLES_HPP_
+#define _SQMYSQL_HANDLES_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+#include
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Forward declarations.
+*/
+class Connection;
+class Statement;
+class Column;
+class Transaction;
+
+/* ------------------------------------------------------------------------------------------------
+ * Manages a reference counted database connection handle.
+*/
+class ConnHnd
+{
+ // --------------------------------------------------------------------------------------------
+ friend class Connection;
+ friend class Statement;
+
+public:
+
+ // --------------------------------------------------------------------------------------------
+ typedef MYSQL Type; // The managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type* Pointer; // Pointer to the managed type.
+ typedef const Type* ConstPtr; // Constant pointer to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type& Reference; // Reference to the managed type.
+ typedef const Type& ConstRef; // Constant reference to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef unsigned int Counter; // Reference counter type.
+
+protected:
+
+ // --------------------------------------------------------------------------------------------
+ typedef std::vector< String > QueryList; // Container used to queue queries.
+
+ /* --------------------------------------------------------------------------------------------
+ * The structure that holds the data associated with a certain connection.
+ */
+ struct Handle
+ {
+ // ----------------------------------------------------------------------------------------
+ Pointer mPtr; // The connection handle resource.
+ Counter mRef; // Reference count to the managed handle.
+
+ // ----------------------------------------------------------------------------------------
+ Uint32 mErrNo; // Last received error string.
+ String mErrStr; // Last received error message.
+
+ // --------------------------------------------------------------------------------------------
+ String mHost; // Host address.
+ String mUser; // User name user.
+ String mPass; // User password.
+ Uint16 mPort; // Server port.
+
+ // --------------------------------------------------------------------------------------------
+ String mSchema; // Server schema.
+ String mSocket; // Unix socket.
+
+ // --------------------------------------------------------------------------------------------
+ String mSSL_Key; // SSL key.
+ String mSSL_Cert; // SSL certificate.
+ String mSSL_CA; // SSL certificate authority.
+ String mSSL_CA_Path; // SSL certificate authority path.
+ String mSSL_Cipher; // SSL Cipher.
+
+ // --------------------------------------------------------------------------------------------
+ Options mOptions; // Option container.
+
+ // ----------------------------------------------------------------------------------------
+ QueryList mQueue; // A queue of queries to be executed in groups.
+
+ // --------------------------------------------------------------------------------------------
+ bool mAutocommit; // Whether autocommit is enabled on this connection.
+ bool mInTransaction; // Whether the connection is in the middle of a transaction.
+
+ /* ----------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ Handle(Counter counter)
+ : mPtr(nullptr)
+ , mRef(counter)
+ , mErrNo(0)
+ , mErrStr(_SC(""))
+ , mHost(_SC(""))
+ , mUser(_SC(""))
+ , mPass(_SC(""))
+ , mPort(3306)
+ , mSchema(_SC(""))
+ , mSocket(_SC(""))
+ , mSSL_Key(_SC(""))
+ , mSSL_Cert(_SC(""))
+ , mSSL_CA(_SC(""))
+ , mSSL_CA_Path(_SC(""))
+ , mSSL_Cipher(_SC(""))
+ , mOptions(_SC(""))
+ , mQueue()
+ , mAutocommit(false)
+ , mInTransaction(false)
+ {
+ /* ... */
+ }
+
+ /* ----------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Handle();
+
+ /* ----------------------------------------------------------------------------------------
+ * Create the database connection resource.
+ */
+ void Create(CSStr host, CSStr user, CSStr pass, CSStr schema, Uint16 port, CSStr socket);
+
+ /* ----------------------------------------------------------------------------------------
+ * Execute a specific amount of queries from the queue.
+ */
+ Int32 Flush(Uint32 num, Object & env, Function & func);
+ };
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ Handle* m_Hnd;
+
+ /* --------------------------------------------------------------------------------------------
+ * Grab a strong reference to a connection handle.
+ */
+ void Grab()
+ {
+ if (m_Hnd)
+ {
+ ++(m_Hnd->mRef);
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Drop a strong reference to a connection handle.
+ */
+ void Drop()
+ {
+ if (m_Hnd && --(m_Hnd->mRef) == 0)
+ {
+ delete m_Hnd; // Let the destructor take care of cleaning up (if necessary)
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ ConnHnd(CSStr name)
+ : m_Hnd(name ? new Handle(1) : nullptr)
+ {
+ /* ... */
+ }
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor (null).
+ */
+ ConnHnd()
+ : m_Hnd(nullptr)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ ConnHnd(const ConnHnd & o)
+ : m_Hnd(o.m_Hnd)
+ {
+ Grab();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ ConnHnd(ConnHnd && o)
+ : m_Hnd(o.m_Hnd)
+ {
+ o.m_Hnd = nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~ConnHnd()
+ {
+ Drop();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ ConnHnd & operator = (const ConnHnd & o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ Drop();
+ m_Hnd = o.m_Hnd;
+ Grab();
+ }
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ ConnHnd & operator = (ConnHnd && o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ m_Hnd = o.m_Hnd;
+ o.m_Hnd = nullptr;
+ }
+
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Status assignment operator.
+ */
+ ConnHnd & operator = (Int32 status)
+ {
+ if (m_Hnd)
+ {
+ m_Hnd->mErrNo = status;
+ }
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an equality comparison between two connection handles.
+ */
+ bool operator == (const ConnHnd & o) const
+ {
+ return (m_Hnd == o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an inequality comparison between two connection handles.
+ */
+ bool operator != (const ConnHnd & o) const
+ {
+ return (m_Hnd != o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an equality comparison with an integer value status.
+ */
+ bool operator == (Int32 status) const
+ {
+ if (m_Hnd)
+ {
+ return (m_Hnd->mErrNo == status);
+ }
+ return false;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an inequality comparison with an integer value status.
+ */
+ bool operator != (Int32 status) const
+ {
+ if (m_Hnd)
+ {
+ return (m_Hnd->mErrNo != status);
+ }
+ return false;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to boolean for use in boolean operations.
+ */
+ operator bool () const
+ {
+ return m_Hnd && m_Hnd->mPtr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer ()
+ {
+ return m_Hnd ? m_Hnd->mPtr : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer () const
+ {
+ return m_Hnd ? m_Hnd->mPtr : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Reference ()
+ {
+ assert(m_Hnd && m_Hnd->mPtr);
+ return *(m_Hnd->mPtr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator ConstRef () const
+ {
+ assert(m_Hnd && m_Hnd->mPtr);
+ return *(m_Hnd->mPtr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Member operator for dereferencing the managed pointer.
+ */
+ Handle * operator -> () const
+ {
+ assert(m_Hnd);
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Indirection operator for obtaining a reference of the managed pointer.
+ */
+ Handle & operator * () const
+ {
+ assert(m_Hnd);
+ return *m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr()
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr() const
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the number of active references to the managed instance.
+ */
+ Counter Count() const
+ {
+ return m_Hnd ? m_Hnd->mRef : 0;
+ }
+};
+
+/* ------------------------------------------------------------------------------------------------
+ * Manages a reference counted database statement handle.
+*/
+class StmtHnd
+{
+ // --------------------------------------------------------------------------------------------
+ friend class Connection;
+ friend class Statement;
+
+public:
+
+ // --------------------------------------------------------------------------------------------
+ typedef MYSQL_STMT Type; // The managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type* Pointer; // Pointer to the managed type.
+ typedef const Type* ConstPtr; // Constant pointer to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef Type& Reference; // Reference to the managed type.
+ typedef const Type& ConstRef; // Constant reference to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef MYSQL_BIND BindType; // The managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef BindType* BindPointer; // Pointer to the managed type.
+ typedef const BindType* BindConstPtr; // Constant pointer to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef BindType& BindReference; // Reference to the managed type.
+ typedef const BindType& BindConstRef; // Constant reference to the managed type.
+
+ // --------------------------------------------------------------------------------------------
+ typedef unsigned int Counter; // Reference counter type.
+
+protected:
+
+ // --------------------------------------------------------------------------------------------
+ typedef std::map< String, int > Indexes; // Container used to identify column indexes.
+
+ /* --------------------------------------------------------------------------------------------
+ * The structure that holds the data associated with a certain statement.
+ */
+ struct Handle
+ {
+ // --------------------------------------------------------------------------------------------
+ Pointer mPtr; // The statement handle resource.
+ Counter mRef; // Reference count to the managed handle.
+
+ // ----------------------------------------------------------------------------------------
+ Int32 mErrNo; // The last status code of this connection handle.
+
+ // ----------------------------------------------------------------------------------------
+ MYSQL_BIND* mBinds;
+
+ /* ----------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ Handle(const ConnHnd & conn, Counter counter)
+ : mPtr(nullptr)
+ , mRef(counter)
+ , mErrNo(0)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~Handle();
+
+ /* --------------------------------------------------------------------------------------------
+ * Create the database statement resource.
+ */
+ void Create(CSStr query);
+ };
+
+private:
+
+ // --------------------------------------------------------------------------------------------
+ Handle* m_Hnd;
+
+ /* --------------------------------------------------------------------------------------------
+ * Grab a strong reference to a statement handle.
+ */
+ void Grab()
+ {
+ if (m_Hnd)
+ {
+ ++(m_Hnd->mRef);
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Drop a strong reference to a statement handle.
+ */
+ void Drop()
+ {
+ if (m_Hnd && --(m_Hnd->mRef) == 0)
+ {
+ delete m_Hnd; // Let the destructor take care of cleaning up (if necessary)
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Base constructor.
+ */
+ StmtHnd(const ConnHnd & db)
+ : m_Hnd(new Handle(db, 1))
+ {
+ /* ... */
+ }
+
+public:
+
+ /* --------------------------------------------------------------------------------------------
+ * Default constructor (null).
+ */
+ StmtHnd()
+ : m_Hnd(nullptr)
+ {
+ /* ... */
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy constructor.
+ */
+ StmtHnd(const StmtHnd & o)
+ : m_Hnd(o.m_Hnd)
+
+ {
+ Grab();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move constructor.
+ */
+ StmtHnd(StmtHnd && o)
+ : m_Hnd(o.m_Hnd)
+ {
+ o.m_Hnd = nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Destructor.
+ */
+ ~StmtHnd()
+ {
+ Drop();
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Copy assignment operator.
+ */
+ StmtHnd & operator = (const StmtHnd & o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ Drop();
+ m_Hnd = o.m_Hnd;
+ Grab();
+ }
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Move assignment operator.
+ */
+ StmtHnd & operator = (StmtHnd && o)
+ {
+ if (m_Hnd != o.m_Hnd)
+ {
+ m_Hnd = o.m_Hnd;
+ o.m_Hnd = nullptr;
+ }
+
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Status assignment operator.
+ */
+ StmtHnd & operator = (Int32 status)
+ {
+ if (m_Hnd)
+ {
+ m_Hnd->mErrNo = status;
+ }
+ return *this;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an equality comparison between two statement handles.
+ */
+ bool operator == (const StmtHnd & o) const
+ {
+ return (m_Hnd == o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an inequality comparison between two statement handles.
+ */
+ bool operator != (const StmtHnd & o) const
+ {
+ return (m_Hnd != o.m_Hnd);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an equality comparison with an integer value status.
+ */
+ bool operator == (Int32 status) const
+ {
+ if (m_Hnd)
+ {
+ return (m_Hnd->mErrNo == status);
+ }
+ return false;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Perform an inequality comparison with an integer status value.
+ */
+ bool operator != (Int32 status) const
+ {
+ if (m_Hnd)
+ {
+ return (m_Hnd->mErrNo != status);
+ }
+ return false;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to boolean for use in boolean operations.
+ */
+ operator bool () const
+ {
+ return m_Hnd && m_Hnd->mPtr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer ()
+ {
+ return m_Hnd ? m_Hnd->mPtr : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Pointer () const
+ {
+ return m_Hnd ? m_Hnd->mPtr : nullptr;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator Reference ()
+ {
+ assert(m_Hnd && m_Hnd->mPtr);
+ return *(m_Hnd->mPtr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Implicit conversion to the managed instance.
+ */
+ operator ConstRef () const
+ {
+ assert(m_Hnd && m_Hnd->mPtr);
+ return *(m_Hnd->mPtr);
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Member operator for dereferencing the managed pointer.
+ */
+ Handle * operator -> () const
+ {
+ assert(m_Hnd);
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Indirection operator for obtaining a reference of the managed pointer.
+ */
+ Handle & operator * () const
+ {
+ assert(m_Hnd);
+ return *m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr()
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the raw handle structure pointer.
+ */
+ Handle * HndPtr() const
+ {
+ return m_Hnd;
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ * Retrieve the number of active references to the managed instance.
+ */
+ Counter Count() const
+ {
+ return m_Hnd ? m_Hnd->mRef : 0;
+ }
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_HANDLES_HPP_
diff --git a/modules/mysql/Module.cpp b/modules/mysql/Module.cpp
new file mode 100644
index 00000000..69494fb3
--- /dev/null
+++ b/modules/mysql/Module.cpp
@@ -0,0 +1,305 @@
+// --------------------------------------------------------------------------------------------
+#include "Module.hpp"
+#include "Common.hpp"
+
+// --------------------------------------------------------------------------------------------
+#include
+
+// --------------------------------------------------------------------------------------------
+#include
+#include
+#include
+
+// --------------------------------------------------------------------------------------------
+#if defined(WIN32) || defined(_WIN32)
+ #include
+#endif
+
+namespace SqMod {
+
+// --------------------------------------------------------------------------------------------
+PluginFuncs* _Func = nullptr;
+PluginCallbacks* _Clbk = nullptr;
+PluginInfo* _Info = nullptr;
+
+// --------------------------------------------------------------------------------------------
+HSQAPI _SqAPI = nullptr;
+HSQEXPORTS _SqMod = nullptr;
+HSQUIRRELVM _SqVM = nullptr;
+
+/* ------------------------------------------------------------------------------------------------
+ * Bind speciffic functions to certain server events.
+*/
+void BindCallbacks();
+
+/* ------------------------------------------------------------------------------------------------
+ * Undo changes made with BindCallbacks().
+*/
+void UnbindCallbacks();
+
+/* --------------------------------------------------------------------------------------------
+ * Register the module API under the specified virtual machine.
+*/
+void RegisterAPI(HSQUIRRELVM vm);
+
+/* --------------------------------------------------------------------------------------------
+ * Initialize the plugin by obtaining the API provided by the host plugin.
+*/
+void OnSquirrelInitialize()
+{
+ // Attempt to import the plugin API exported by the host plugin
+ _SqMod = sq_api_import(_Func);
+ // Did we failed to obtain the plugin exports?
+ if(!_SqMod)
+ OutputError("Failed to attach [%s] on host plugin.", SQMYSQL_NAME);
+ else
+ {
+ // Obtain the Squirrel API
+ _SqAPI = _SqMod->GetSquirrelAPI();
+ // Expand the Squirrel API into global functions
+ sq_api_expand(_SqAPI);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------
+ * Load the module on the virtual machine provided by the host module.
+*/
+void OnSquirrelLoad()
+{
+ // Make sure that we have a valid plugin API
+ if (!_SqMod)
+ return; /* Unable to proceed. */
+ // Obtain the Squirrel API and VM
+ _SqVM = _SqMod->GetSquirrelVM();
+ // Make sure that a valid virtual machine exists
+ if (!_SqVM)
+ return; /* Unable to proceed. */
+ // Set this as the default database
+ DefaultVM::Set(_SqVM);
+ // Register the module API
+ RegisterAPI(_SqVM);
+ // Notify about the current status
+ OutputMessage("Registered: %s", SQMYSQL_NAME);
+}
+
+/* --------------------------------------------------------------------------------------------
+ * The virtual machine is about to be terminated and script resources should be released.
+*/
+void OnSquirrelTerminate()
+{
+ OutputMessage("Terminating: %s", SQMYSQL_NAME);
+ // Release the current virtual machine, if any
+ DefaultVM::Set(nullptr);
+ // Release script resources...
+}
+
+/* --------------------------------------------------------------------------------------------
+ * Validate the module API to make sure we don't run into issues.
+*/
+bool CheckAPIVer(CCStr ver)
+{
+ // Obtain the numeric representation of the API version
+ long vernum = strtol(ver, nullptr, 10);
+ // Check against version mismatch
+ if (vernum == SQMOD_API_VER)
+ return true;
+ // Log the incident
+ OutputError("API version mismatch on %s", SQMYSQL_NAME);
+ OutputMessage("=> Requested: %ld Have: %ld", vernum, SQMOD_API_VER);
+ // Invoker should not attempt to communicate through the module API
+ return false;
+}
+
+/* --------------------------------------------------------------------------------------------
+ * React to command sent by other plugins.
+*/
+static uint8_t OnPluginCommand(uint32_t command_identifier, CCStr message)
+{
+ switch(command_identifier)
+ {
+ case SQMOD_INITIALIZE_CMD:
+ if (CheckAPIVer(message))
+ OnSquirrelInitialize();
+ break;
+ case SQMOD_LOAD_CMD:
+ OnSquirrelLoad();
+ break;
+ case SQMOD_TERMINATE_CMD:
+ OnSquirrelTerminate();
+ break;
+ default: break;
+ }
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------
+ * The server was initialized and this plugin was loaded successfully.
+*/
+static uint8_t OnServerInitialise()
+{
+ return 1;
+}
+
+static void OnServerShutdown(void)
+{
+ // The server may still send callbacks
+ UnbindCallbacks();
+}
+
+// ------------------------------------------------------------------------------------------------
+void BindCallbacks()
+{
+ _Clbk->OnServerInitialise = OnServerInitialise;
+ _Clbk->OnServerShutdown = OnServerShutdown;
+ _Clbk->OnPluginCommand = OnPluginCommand;
+}
+
+// ------------------------------------------------------------------------------------------------
+void UnbindCallbacks()
+{
+ _Clbk->OnServerInitialise = nullptr;
+ _Clbk->OnServerShutdown = nullptr;
+ _Clbk->OnPluginCommand = nullptr;
+}
+
+// --------------------------------------------------------------------------------------------
+void RegisterAPI(HSQUIRRELVM vm)
+{
+ SQMOD_UNUSED_VAR(vm);
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputMessageImpl(const char * msg, va_list args)
+{
+#if defined(WIN32) || defined(_WIN32)
+ HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CONSOLE_SCREEN_BUFFER_INFO csb_before;
+ GetConsoleScreenBufferInfo( hstdout, &csb_before);
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN);
+ std::printf("[SQMOD] ");
+
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::vprintf(msg, args);
+ std::puts("");
+
+ SetConsoleTextAttribute(hstdout, csb_before.wAttributes);
+#else
+ std::printf("%c[0;32m[SQMOD]%c[0m", 27, 27);
+ std::vprintf(msg, args);
+ std::puts("");
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputErrorImpl(const char * msg, va_list args)
+{
+#if defined(WIN32) || defined(_WIN32)
+ HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CONSOLE_SCREEN_BUFFER_INFO csb_before;
+ GetConsoleScreenBufferInfo( hstdout, &csb_before);
+ SetConsoleTextAttribute(hstdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::printf("[SQMOD] ");
+
+ SetConsoleTextAttribute(hstdout, FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_INTENSITY);
+ std::vprintf(msg, args);
+ std::puts("");
+
+ SetConsoleTextAttribute(hstdout, csb_before.wAttributes);
+#else
+ std::printf("%c[0;91m[SQMOD]%c[0m", 27, 27);
+ std::vprintf(msg, args);
+ std::puts("");
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputDebug(const char * msg, ...)
+{
+#ifdef _DEBUG
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputMessageImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+#else
+ SQMOD_UNUSED_VAR(msg);
+#endif
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputMessage(const char * msg, ...)
+{
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputMessageImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+}
+
+// --------------------------------------------------------------------------------------------
+void OutputError(const char * msg, ...)
+{
+ // Initialize the arguments list
+ va_list args;
+ va_start(args, msg);
+ // Call the output function
+ OutputErrorImpl(msg, args);
+ // Finalize the arguments list
+ va_end(args);
+}
+
+} // Namespace:: SqMod
+
+// --------------------------------------------------------------------------------------------
+SQMOD_API_EXPORT unsigned int VcmpPluginInit(PluginFuncs* functions, PluginCallbacks* callbacks, PluginInfo* info)
+{
+ using namespace SqMod;
+ // Output plugin header
+ puts("");
+ OutputMessage("--------------------------------------------------------------------");
+ OutputMessage("Plugin: %s", SQMYSQL_NAME);
+ OutputMessage("Author: %s", SQMYSQL_AUTHOR);
+ OutputMessage("Legal: %s", SQMYSQL_COPYRIGHT);
+ OutputMessage("--------------------------------------------------------------------");
+ puts("");
+ // Attempt to find the host plugin ID
+ int host_plugin_id = functions->FindPlugin((char *)(SQMOD_HOST_NAME));
+ // See if our plugin was loaded after the host plugin
+ if (host_plugin_id < 0)
+ {
+ OutputError("%s could find the host plugin", SQMYSQL_NAME);
+ // Don't load!
+ return SQMOD_FAILURE;
+ }
+ // Should never reach this point but just in case
+ else if (static_cast< Uint32 >(host_plugin_id) > info->pluginId)
+ {
+ OutputError("%s loaded after the host plugin", SQMYSQL_NAME);
+ // Don't load!
+ return SQMOD_FAILURE;
+ }
+ // Store server proxies
+ _Func = functions;
+ _Clbk = callbacks;
+ _Info = info;
+ // Assign plugin version
+ _Info->pluginVersion = SQMYSQL_VERSION;
+ _Info->apiMajorVersion = PLUGIN_API_MAJOR;
+ _Info->apiMinorVersion = PLUGIN_API_MINOR;
+ // Assign the plugin name
+ std::snprintf(_Info->name, sizeof(_Info->name), "%s", SQMYSQL_HOST_NAME);
+ // Bind callbacks
+ BindCallbacks();
+ // Notify that the plugin was successfully loaded
+ OutputMessage("Successfully loaded %s", SQMYSQL_NAME);
+ // Dummy spacing
+ puts("");
+ // Done!
+ return SQMOD_SUCCESS;
+}
diff --git a/modules/mysql/Module.hpp b/modules/mysql/Module.hpp
new file mode 100644
index 00000000..08e962be
--- /dev/null
+++ b/modules/mysql/Module.hpp
@@ -0,0 +1,41 @@
+#ifndef _SQMYSQL_MODULE_HPP_
+#define _SQMYSQL_MODULE_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "SqMod.h"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ * Proxies to comunicate with the server.
+*/
+extern PluginFuncs* _Func;
+extern PluginCallbacks* _Clbk;
+extern PluginInfo* _Info;
+
+/* ------------------------------------------------------------------------------------------------
+ * Proxies to comunicate with the Squirrel plugin.
+*/
+extern HSQAPI _SqAPI;
+extern HSQEXPORTS _SqMod;
+extern HSQUIRRELVM _SqVM;
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a message only if the _DEBUG was defined.
+*/
+void OutputDebug(const char * msg, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a formatted user message to the console.
+*/
+void OutputMessage(const char * msg, ...);
+
+/* ------------------------------------------------------------------------------------------------
+ * Output a formatted error message to the console.
+*/
+void OutputError(const char * msg, ...);
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_MODULE_HPP_
diff --git a/modules/mysql/Result.cpp b/modules/mysql/Result.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Result.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Result.hpp b/modules/mysql/Result.hpp
new file mode 100644
index 00000000..16bc1d05
--- /dev/null
+++ b/modules/mysql/Result.hpp
@@ -0,0 +1,17 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Savepoint.cpp b/modules/mysql/Savepoint.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Savepoint.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Savepoint.hpp b/modules/mysql/Savepoint.hpp
new file mode 100644
index 00000000..16bc1d05
--- /dev/null
+++ b/modules/mysql/Savepoint.hpp
@@ -0,0 +1,17 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Statement.cpp b/modules/mysql/Statement.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Statement.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Statement.hpp b/modules/mysql/Statement.hpp
new file mode 100644
index 00000000..d61a8cab
--- /dev/null
+++ b/modules/mysql/Statement.hpp
@@ -0,0 +1,23 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+class Statement
+{
+public:
+ Statement();
+ ~Statement();
+
+};
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/mysql/Transaction.cpp b/modules/mysql/Transaction.cpp
new file mode 100644
index 00000000..15fab2a6
--- /dev/null
+++ b/modules/mysql/Transaction.cpp
@@ -0,0 +1,10 @@
+// ------------------------------------------------------------------------------------------------
+#include "Account.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+// ------------------------------------------------------------------------------------------------
+
+
+} // Namespace:: SqMod
diff --git a/modules/mysql/Transaction.hpp b/modules/mysql/Transaction.hpp
new file mode 100644
index 00000000..16bc1d05
--- /dev/null
+++ b/modules/mysql/Transaction.hpp
@@ -0,0 +1,17 @@
+#ifndef _SQMYSQL_ACCOUNT_HPP_
+#define _SQMYSQL_ACCOUNT_HPP_
+
+// ------------------------------------------------------------------------------------------------
+#include "Common.hpp"
+
+// ------------------------------------------------------------------------------------------------
+namespace SqMod {
+
+/* ------------------------------------------------------------------------------------------------
+ *
+*/
+
+
+} // Namespace:: SqMod
+
+#endif // _SQMYSQL_ACCOUNT_HPP_
diff --git a/modules/sample/Module.cpp b/modules/sample/Module.cpp
index 524ef62e..5e705f12 100644
--- a/modules/sample/Module.cpp
+++ b/modules/sample/Module.cpp
@@ -95,7 +95,7 @@ void OnSquirrelLoad()
void OnSquirrelTerminate()
{
OutputMessage("Terminating: %s", SQSAMPLE_NAME);
- // Release the current database (if any)
+ // Release the current virtual machine, if any
DefaultVM::Set(nullptr);
// Release script resources...
}
diff --git a/modules/sqlite/Column.cpp b/modules/sqlite/Column.cpp
index d6639c9c..c9ab3635 100644
--- a/modules/sqlite/Column.cpp
+++ b/modules/sqlite/Column.cpp
@@ -16,7 +16,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Column::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqSQLiteColumn");
+ static const SQChar name[] = _SC("SqSQLiteColumn");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/sqlite/Connection.cpp b/modules/sqlite/Connection.cpp
index 7a4d4b28..2b333937 100644
--- a/modules/sqlite/Connection.cpp
+++ b/modules/sqlite/Connection.cpp
@@ -12,7 +12,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Connection::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqSQLiteConnection");
+ static const SQChar name[] = _SC("SqSQLiteConnection");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/sqlite/Module.cpp b/modules/sqlite/Module.cpp
index b85bc7a0..18326437 100644
--- a/modules/sqlite/Module.cpp
+++ b/modules/sqlite/Module.cpp
@@ -97,7 +97,7 @@ void OnSquirrelLoad()
void OnSquirrelTerminate()
{
OutputMessage("Terminating: %s", SQSQLITE_NAME);
- // Release the current database (if any)
+ // Release the current virtual machine, if any
DefaultVM::Set(nullptr);
}
@@ -362,7 +362,6 @@ void RegisterAPI(HSQUIRRELVM vm)
RootTable(vm).Bind(_SC("SQLite"), sqlns);
-
ConstTable(vm).Enum(_SC("ESQLite"), Enumeration(vm)
.Const(_SC("ABORT"), SQLITE_ABORT)
.Const(_SC("ABORT_ROLLBACK"), SQLITE_ABORT_ROLLBACK)
diff --git a/modules/sqlite/Statement.cpp b/modules/sqlite/Statement.cpp
index 13421412..6d37c308 100644
--- a/modules/sqlite/Statement.cpp
+++ b/modules/sqlite/Statement.cpp
@@ -22,7 +22,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Statement::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqSQLiteStatement");
+ static const SQChar name[] = _SC("SqSQLiteStatement");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/xml/Attribute.cpp b/modules/xml/Attribute.cpp
index acd5ad9f..2a228e88 100644
--- a/modules/xml/Attribute.cpp
+++ b/modules/xml/Attribute.cpp
@@ -11,7 +11,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Attribute::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqXmlAttribute");
+ static const SQChar name[] = _SC("SqXmlAttribute");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/xml/Document.cpp b/modules/xml/Document.cpp
index 48ba37e9..f3254d3c 100644
--- a/modules/xml/Document.cpp
+++ b/modules/xml/Document.cpp
@@ -9,7 +9,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Document::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqXmlDocument");
+ static const SQChar name[] = _SC("SqXmlDocument");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/xml/Module.cpp b/modules/xml/Module.cpp
index 08d880f4..87f9963c 100644
--- a/modules/xml/Module.cpp
+++ b/modules/xml/Module.cpp
@@ -98,7 +98,7 @@ void OnSquirrelLoad()
void OnSquirrelTerminate()
{
OutputMessage("Terminating: %s", SQXML_NAME);
- // Release the current database (if any)
+ // Release the current virtual machine, if any
DefaultVM::Set(nullptr);
}
diff --git a/modules/xml/Node.cpp b/modules/xml/Node.cpp
index 32e97a6a..1fb308ac 100644
--- a/modules/xml/Node.cpp
+++ b/modules/xml/Node.cpp
@@ -10,7 +10,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Node::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqXmlNode");
+ static const SQChar name[] = _SC("SqXmlNode");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/modules/xml/Text.cpp b/modules/xml/Text.cpp
index 43c234b3..49bde447 100644
--- a/modules/xml/Text.cpp
+++ b/modules/xml/Text.cpp
@@ -15,7 +15,7 @@ namespace SqMod {
// ------------------------------------------------------------------------------------------------
SQInteger Text::Typename(HSQUIRRELVM vm)
{
- static SQChar name[] = _SC("SqXmlText");
+ static const SQChar name[] = _SC("SqXmlText");
sq_pushstring(vm, name, sizeof(name));
return 1;
}
diff --git a/shared/ModBase.hpp b/shared/ModBase.hpp
index 409bad5b..79c95cf0 100644
--- a/shared/ModBase.hpp
+++ b/shared/ModBase.hpp
@@ -1,11 +1,12 @@
-#ifndef _SQBASE_HPP_
-#define _SQBASE_HPP_
+#ifndef _SQMODBASE_HPP_
+#define _SQMODBASE_HPP_
// ------------------------------------------------------------------------------------------------
#include
// ------------------------------------------------------------------------------------------------
#include
+#include
#include
/* ------------------------------------------------------------------------------------------------
@@ -94,7 +95,7 @@
#endif
#else
// Unsupported system
- #error This operating system is not supported by the Squirrel Modules
+ #error This operating system is not supported by the Squirrel Module
#endif
#ifndef SQMOD_ARCHITECTURE
@@ -108,6 +109,7 @@
/* ------------------------------------------------------------------------------------------------
* SQUIRREL FORWARD DECLARATIONS
*/
+
extern "C" {
typedef struct tagSQObject SQObject;
struct SQVM;
@@ -118,6 +120,7 @@ extern "C" {
/* ------------------------------------------------------------------------------------------------
* SQRAT FORWARD DECLARATIONS
*/
+
namespace Sqrat {
class Array;
class Object;
@@ -189,7 +192,8 @@ typedef Uint32 SizeT;
/* ------------------------------------------------------------------------------------------------
* STRING TYPE
*/
-typedef ::std::basic_string String;
+
+typedef std::basic_string String;
typedef char * CStr;
typedef const char * CCStr;
@@ -200,6 +204,7 @@ typedef const SQChar * CSStr;
/* ------------------------------------------------------------------------------------------------
* SHORT SQUIRREL TYPENAMES
*/
+
typedef SQUnsignedInteger32 SQUint32;
typedef SQUnsignedInteger SQUint;
typedef SQInteger SQInt;
@@ -207,6 +212,12 @@ typedef SQInteger SQInt;
// ------------------------------------------------------------------------------------------------
using namespace Sqrat;
+/* ------------------------------------------------------------------------------------------------
+ * Squirrel compatible stl string.
+*/
+
+typedef std::basic_string< SQChar > String;
+
/* ------------------------------------------------------------------------------------------------
* FORWARD DECLARATIONS
*/
@@ -323,12 +334,12 @@ using namespace Sqrat;
* COLOR PACKING
*/
-#define SQMOD_PACK_RGB(r, g, b) (Uint32)(r << 16 | g << 8 | b)
-#define SQMOD_PACK_RGBA(r, g, b, a) (Uint32)(r << 24 | g << 16 | b << 8 | a)
-#define SQMOD_PACK_ARGB(a, r, g, b) (Uint32)(a << 24 | r << 16 | g << 8 | b)
+#define SQMOD_PACK_RGB(r, g, b) static_cast< Uint32 >(r << 16 | g << 8 | b)
+#define SQMOD_PACK_RGBA(r, g, b, a) static_cast< Uint32 >(r << 24 | g << 16 | b << 8 | a)
+#define SQMOD_PACK_ARGB(a, r, g, b) static_cast< Uint32 >(a << 24 | r << 16 | g << 8 | b)
-#define SQMOD_PACK_RGB_TO_RGBA(r, g, b) (Uint32)(r << 24 | g << 16 | b << 8 | 0)
-#define SQMOD_PACK_RGB_TO_ARGB(r, g, b) (Uint32)(0 << 24 | r << 16 | g << 8 | b)
+#define SQMOD_PACK_RGB_TO_RGBA(r, g, b) static_cast< Uint32 >(r << 24 | g << 16 | b << 8 | 0)
+#define SQMOD_PACK_RGB_TO_ARGB(r, g, b) static_cast< Uint32 >(0 << 24 | r << 16 | g << 8 | b)
/* ------------------------------------------------------------------------------------------------
* GENERAL RESPONSES
@@ -364,13 +375,10 @@ using namespace Sqrat;
#define SQMOD_BLIP_POOL 128
#define SQMOD_CHECKPOINT_POOL 2000
-#define SQMOD_FORCEFIELD_POOL 2000
#define SQMOD_KEYBIND_POOL 256
#define SQMOD_OBJECT_POOL 3000
#define SQMOD_PICKUP_POOL 2000
#define SQMOD_PLAYER_POOL 100
-#define SQMOD_SPRITE_POOL 128
-#define SQMOD_TEXTDRAW_POOL 256
#define SQMOD_VEHICLE_POOL 1000
/* ------------------------------------------------------------------------------------------------
@@ -382,7 +390,7 @@ using namespace Sqrat;
#define SQMOD_CREATE_POOL -4
#define SQMOD_CREATE_AUTOMATIC -5
#define SQMOD_CREATE_OVERWRITE -6
-#define SQMOD_CREATE_RESURECT -7
+#define SQMOD_CREATE_IMPORT -7
#define SQMOD_DESTROY_DEFAULT 0
#define SQMOD_DESTROY_MANUAL -3
@@ -413,36 +421,11 @@ using namespace Sqrat;
*/
#define SQMOD_STACK_SIZE 2048
+#define SQMOD_MAX_ROUTINES 1024
#define SQMOD_MAX_CMD_ARGS 12
#define SQMOD_PLAYER_MSG_PREFIXES 16
#define SQMOD_PLAYER_TMP_BUFFER 128
-/* ------------------------------------------------------------------------------------------------
- * ENTITY POOL UPDATE IDENTIFIERS
-*/
-#define SQMOD_ENTITY_POOL_VEHICLE 1
-#define SQMOD_ENTITY_POOL_OBJECT 2
-#define SQMOD_ENTITY_POOL_PICKUP 3
-#define SQMOD_ENTITY_POOL_RADIO 4
-#define SQMOD_ENTITY_POOL_SPRITE 5
-#define SQMOD_ENTITY_POOL_TEXTDRAW 6
-#define SQMOD_ENTITY_POOL_BLIP 7
-#define SQMOD_ENTITY_POOL_MAX 8
-
-/* ------------------------------------------------------------------------------------------------
- * PLAYER STATE IDENTIFIERS
-*/
-#define SQMOD_PLAYER_STATE_NONE 0
-#define SQMOD_PLAYER_STATE_NORMAL 1
-#define SQMOD_PLAYER_STATE_SHOOTING 2
-#define SQMOD_PLAYER_STATE_DRIVER 3
-#define SQMOD_PLAYER_STATE_PASSENGER 4
-#define SQMOD_PLAYER_STATE_ENTERING_AS_DRIVER 5
-#define SQMOD_PLAYER_STATE_ENTERING_AS_PASSENGER 6
-#define SQMOD_PLAYER_STATE_EXITING_VEHICLE 7
-#define SQMOD_PLAYER_STATE_UNSPAWNED 8
-#define SQMOD_PLAYER_STATE_MAX 9
-
/* ------------------------------------------------------------------------------------------------
* PLAYER ACTION IDENTIFIERS
*/
@@ -461,43 +444,6 @@ using namespace Sqrat;
#define SQMOD_PLAYER_ACTION_EXITING_VEHICLE 60
#define SQMOD_PLAYER_ACTION_MAX 61
-/* ------------------------------------------------------------------------------------------------
- * VEHICLE UPDATE IDENTIFIERS
-*/
-#define SQMOD_VEHICLEUPD_DRIVER 0
-#define SQMOD_VEHICLEUPD_OTHER 1
-#define SQMOD_VEHICLEUPD_MAX 2
-
-/* ------------------------------------------------------------------------------------------------
- * PLAYER UPDATE IDENTIFIERS
-*/
-#define SQMOD_PLAYERUPD_ONFOOT 0
-#define SQMOD_PLAYERUPD_AIM 1
-#define SQMOD_PLAYERUPD_DRIVER 2
-#define SQMOD_PLAYERUPD_PASSENGER 3
-#define SQMOD_PLAYERUPD_MAX 4
-
-/* ------------------------------------------------------------------------------------------------
- * PART REASON IDENTIFIERS
-*/
-#define SQMOD_PARTREASON_TIMEOUT 0
-#define SQMOD_PARTREASON_DISCONNECTED 1
-#define SQMOD_PARTREASON_KICKEDBANNED 2
-#define SQMOD_PARTREASON_CRASHED 3
-#define SQMOD_PARTREASON_MAX 4
-
-/* ------------------------------------------------------------------------------------------------
- * BODY PART IDENTIFIERS
-*/
-#define SQMOD_BODYPART_BODY 0
-#define SQMOD_BODYPART_TORSO 1
-#define SQMOD_BODYPART_LEFTARM 2
-#define SQMOD_BODYPART_RIGHTARM 3
-#define SQMOD_BODYPART_LEFTLEG 4
-#define SQMOD_BODYPART_RIGHTLEG 5
-#define SQMOD_BODYPART_HEAD 6
-#define SQMOD_BODYPART_MAX 7
-
/* ------------------------------------------------------------------------------------------------
* WEATHER IDENTIFIERS
*/
@@ -1193,4 +1139,4 @@ using namespace Sqrat;
#define SQMOD_ASCII_UNDEFINED 127
#define SQMOD_ASCII_MAX 128
-#endif // _SQBASE_HPP_
+#endif // _SQMODBASE_HPP_