|
@@ -2960,6 +2960,23 @@ enum {
|
|
PROTOCOL_TYPE_HTTP2 = 2
|
|
PROTOCOL_TYPE_HTTP2 = 2
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+#define mg_cry_internal(conn, fmt, ...) \
|
|
|
|
+ mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__)
|
|
|
|
+
|
|
|
|
+#define mg_cry_ctx_internal(ctx, fmt, ...) \
|
|
|
|
+ mg_cry_internal_wrap(NULL, ctx, __func__, __LINE__, fmt, __VA_ARGS__)
|
|
|
|
+
|
|
|
|
+static void mg_cry_internal_wrap(const struct mg_connection *conn,
|
|
|
|
+ struct mg_context *ctx,
|
|
|
|
+ const char *func,
|
|
|
|
+ unsigned line,
|
|
|
|
+ const char *fmt,
|
|
|
|
+ ...) PRINTF_ARGS(5, 6);
|
|
|
|
+
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+#include "mod_zlib.inl"
|
|
|
|
+#endif
|
|
|
|
+
|
|
struct mg_connection {
|
|
struct mg_connection {
|
|
int connection_type; /* see CONNECTION_TYPE_* above */
|
|
int connection_type; /* see CONNECTION_TYPE_* above */
|
|
int protocol_type; /* see PROTOCOL_TYPE_*: 0=http/1.x, 1=ws, 2=http/2 */
|
|
int protocol_type; /* see PROTOCOL_TYPE_*: 0=http/1.x, 1=ws, 2=http/2 */
|
|
@@ -3010,6 +3027,17 @@ struct mg_connection {
|
|
* pages */
|
|
* pages */
|
|
#if defined(USE_WEBSOCKET)
|
|
#if defined(USE_WEBSOCKET)
|
|
int in_websocket_handling; /* 1 if in read_websocket */
|
|
int in_websocket_handling; /* 1 if in read_websocket */
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+ /* Parameters for websocket data compression according to https://tools.ietf.org/html/rfc7692 */
|
|
|
|
+ int websocket_deflate_server_max_windows_bits;
|
|
|
|
+ int websocket_deflate_client_max_windows_bits;
|
|
|
|
+ int websocket_deflate_server_no_context_takeover;
|
|
|
|
+ int websocket_deflate_client_no_context_takeover;
|
|
|
|
+ int websocket_deflate_initialized;
|
|
|
|
+ int websocket_deflate_flush;
|
|
|
|
+ z_stream websocket_deflate_state;
|
|
|
|
+ z_stream websocket_inflate_state;
|
|
|
|
+#endif
|
|
#endif
|
|
#endif
|
|
int handled_requests; /* Number of requests handled by this connection
|
|
int handled_requests; /* Number of requests handled by this connection
|
|
*/
|
|
*/
|
|
@@ -3041,20 +3069,6 @@ struct de {
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
-#define mg_cry_internal(conn, fmt, ...) \
|
|
|
|
- mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__)
|
|
|
|
-
|
|
|
|
-#define mg_cry_ctx_internal(ctx, fmt, ...) \
|
|
|
|
- mg_cry_internal_wrap(NULL, ctx, __func__, __LINE__, fmt, __VA_ARGS__)
|
|
|
|
-
|
|
|
|
-static void mg_cry_internal_wrap(const struct mg_connection *conn,
|
|
|
|
- struct mg_context *ctx,
|
|
|
|
- const char *func,
|
|
|
|
- unsigned line,
|
|
|
|
- const char *fmt,
|
|
|
|
- ...) PRINTF_ARGS(5, 6);
|
|
|
|
-
|
|
|
|
-
|
|
|
|
#if !defined(NO_THREAD_NAME)
|
|
#if !defined(NO_THREAD_NAME)
|
|
#if defined(_WIN32) && defined(_MSC_VER)
|
|
#if defined(_WIN32) && defined(_MSC_VER)
|
|
/* Set the thread name for debugging purposes in Visual Studio
|
|
/* Set the thread name for debugging purposes in Visual Studio
|
|
@@ -4283,6 +4297,71 @@ get_req_headers(const struct mg_request_info *ri,
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
+#if defined(USE_WEBSOCKET) && defined(USE_ZLIB)
|
|
|
|
+int websocket_deflate_initialize(struct mg_connection *conn) {
|
|
|
|
+ fflush(stdout);
|
|
|
|
+ z_stream websocket_deflate_state;
|
|
|
|
+ int websocket_deflate_server_max_windows_bits;
|
|
|
|
+ int websocket_deflate_client_max_windows_bits;
|
|
|
|
+ int websocket_deflate_server_no_context_takeover;
|
|
|
|
+ int websocket_deflate_client_no_context_takeover;
|
|
|
|
+
|
|
|
|
+ uint8_t deflate_bits;
|
|
|
|
+ uint8_t inflate_bits;
|
|
|
|
+
|
|
|
|
+ // if (server)
|
|
|
|
+ deflate_bits = conn->websocket_deflate_server_max_windows_bits;
|
|
|
|
+ inflate_bits = conn->websocket_deflate_client_max_windows_bits;
|
|
|
|
+ /*
|
|
|
|
+ deflate_bits = conn->websocket_deflate_client_max_windows_bits;
|
|
|
|
+ inflate_bits = conn->websocket_deflate_server_max_windows_bits;
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ int ret = deflateInit2(&conn->websocket_deflate_state, Z_DEFAULT_COMPRESSION,
|
|
|
|
+ Z_DEFLATED, -1 * deflate_bits,
|
|
|
|
+ 4, // memory level 1-9
|
|
|
|
+ Z_DEFAULT_STRATEGY);
|
|
|
|
+ if (ret != Z_OK) return ret;
|
|
|
|
+
|
|
|
|
+ ret = inflateInit2(&conn->websocket_inflate_state, -1 * inflate_bits);
|
|
|
|
+ if (ret != Z_OK) return ret;
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ if ((m_server_no_context_takeover && is_server) ||
|
|
|
|
+ (m_client_no_context_takeover && !is_server)) {
|
|
|
|
+ */
|
|
|
|
+ conn->websocket_deflate_flush = Z_FULL_FLUSH;
|
|
|
|
+ //} else
|
|
|
|
+ // conn->websocket_deflate_flush = Z_SYNC_FLUSH;
|
|
|
|
+
|
|
|
|
+ conn->websocket_deflate_initialized = 1;
|
|
|
|
+ return Z_OK;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void websocket_deflate_negotiate(struct mg_connection *conn) {
|
|
|
|
+ const char *extensions = mg_get_header(conn, "Sec-WebSocket-Extensions");
|
|
|
|
+ if (extensions && strstr(extensions, "permessage-deflate")) {
|
|
|
|
+ conn->accept_gzip = 1;
|
|
|
|
+ conn->websocket_deflate_client_max_windows_bits = 15;
|
|
|
|
+ conn->websocket_deflate_server_max_windows_bits = 15;
|
|
|
|
+ } else {
|
|
|
|
+ conn->accept_gzip = 0;
|
|
|
|
+ conn->websocket_deflate_client_max_windows_bits = 0;
|
|
|
|
+ conn->websocket_deflate_server_max_windows_bits = 0;
|
|
|
|
+ }
|
|
|
|
+ conn->websocket_deflate_initialized = 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void websocket_deflate_response(struct mg_connection *conn) {
|
|
|
|
+ if (conn->accept_gzip) {
|
|
|
|
+ mg_printf(conn,
|
|
|
|
+ "Sec-WebSocket-Extensions: permessage-deflate; "
|
|
|
|
+ "client_no_context_takeover; server_no_context_takeover\r\n");
|
|
|
|
+ };
|
|
|
|
+}
|
|
|
|
+#endif
|
|
|
|
+
|
|
|
|
+
|
|
const char *
|
|
const char *
|
|
mg_get_header(const struct mg_connection *conn, const char *name)
|
|
mg_get_header(const struct mg_connection *conn, const char *name)
|
|
{
|
|
{
|
|
@@ -10039,11 +10118,6 @@ fclose_on_exec(struct mg_file_access *filep, struct mg_connection *conn)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
-#if defined(USE_ZLIB)
|
|
|
|
-#include "mod_zlib.inl"
|
|
|
|
-#endif
|
|
|
|
-
|
|
|
|
-
|
|
|
|
#if !defined(NO_FILESYSTEMS)
|
|
#if !defined(NO_FILESYSTEMS)
|
|
static void
|
|
static void
|
|
handle_static_file_request(struct mg_connection *conn,
|
|
handle_static_file_request(struct mg_connection *conn,
|
|
@@ -12534,6 +12608,12 @@ send_websocket_handshake(struct mg_connection *conn, const char *websock_key)
|
|
"Connection: Upgrade\r\n"
|
|
"Connection: Upgrade\r\n"
|
|
"Sec-WebSocket-Accept: %s\r\n",
|
|
"Sec-WebSocket-Accept: %s\r\n",
|
|
b64_sha);
|
|
b64_sha);
|
|
|
|
+
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+ // Send negotiated compression extension parameters
|
|
|
|
+ websocket_deflate_response(conn);
|
|
|
|
+#endif
|
|
|
|
+
|
|
if (conn->request_info.acceptedWebSocketSubprotocol) {
|
|
if (conn->request_info.acceptedWebSocketSubprotocol) {
|
|
mg_printf(conn,
|
|
mg_printf(conn,
|
|
"Sec-WebSocket-Protocol: %s\r\n\r\n",
|
|
"Sec-WebSocket-Protocol: %s\r\n\r\n",
|
|
@@ -12772,14 +12852,51 @@ read_websocket(struct mg_connection *conn,
|
|
} else {
|
|
} else {
|
|
/* Exit the loop if callback signals to exit (server side),
|
|
/* Exit the loop if callback signals to exit (server side),
|
|
* or "connection close" opcode received (client side). */
|
|
* or "connection close" opcode received (client side). */
|
|
- if ((ws_data_handler != NULL)
|
|
|
|
- && !ws_data_handler(conn,
|
|
|
|
|
|
+ if (ws_data_handler != NULL) {
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+ if (mop & 0x40) {
|
|
|
|
+ /* Inflate the data received if bit RSV1 is set. */
|
|
|
|
+ if (!conn->websocket_deflate_initialized) {
|
|
|
|
+ if (websocket_deflate_initialize(conn) != Z_OK)
|
|
|
|
+ exit_by_callback = 1;
|
|
|
|
+ }
|
|
|
|
+ if (!exit_by_callback) {
|
|
|
|
+ Bytef *inflated = mg_calloc(10 * 1024 * 1024, sizeof(Bytef));
|
|
|
|
+ // Add trailing 0x00 0x00 0xff 0xff bytes
|
|
|
|
+ data[data_len] = '\x00';
|
|
|
|
+ data[data_len + 1] = '\x00';
|
|
|
|
+ data[data_len + 2] = '\xff';
|
|
|
|
+ data[data_len + 3] = '\xff';
|
|
|
|
+ conn->websocket_inflate_state.avail_in = data_len + 4;
|
|
|
|
+ conn->websocket_inflate_state.next_in = data;
|
|
|
|
+ conn->websocket_inflate_state.avail_out = 10 * 1024 * 1024;
|
|
|
|
+ conn->websocket_inflate_state.next_out = inflated;
|
|
|
|
+ int ret = inflate(&conn->websocket_inflate_state, Z_SYNC_FLUSH);
|
|
|
|
+ // TODO loop allocate bigger buffer if needed
|
|
|
|
+ if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR ||
|
|
|
|
+ ret == Z_MEM_ERROR || ret < 0)
|
|
|
|
+ exit_by_callback = 1;
|
|
|
|
+ inflated[10 * 1024 * 1024 -
|
|
|
|
+ conn->websocket_inflate_state.avail_out] = '\0';
|
|
|
|
+ // if (conn->websocket_inflate_state.avail_out == 0)
|
|
|
|
+ // TODO output buffer overflow
|
|
|
|
+ if (!ws_data_handler(conn, mop, (char *)inflated,
|
|
|
|
+ 10 * 1024 * 1024 -
|
|
|
|
+ conn->websocket_inflate_state.avail_out,
|
|
|
|
+ callback_data)) {
|
|
|
|
+ exit_by_callback = 1;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } else
|
|
|
|
+#endif
|
|
|
|
+ if (!ws_data_handler(conn,
|
|
mop,
|
|
mop,
|
|
(char *)data,
|
|
(char *)data,
|
|
(size_t)data_len,
|
|
(size_t)data_len,
|
|
callback_data)) {
|
|
callback_data)) {
|
|
exit_by_callback = 1;
|
|
exit_by_callback = 1;
|
|
}
|
|
}
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
/* It a buffer has been allocated, free it again */
|
|
/* It a buffer has been allocated, free it again */
|
|
@@ -12877,12 +12994,46 @@ mg_websocket_write_exec(struct mg_connection *conn,
|
|
size_t headerLen;
|
|
size_t headerLen;
|
|
int retval;
|
|
int retval;
|
|
|
|
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+ uLong deflated_size;
|
|
|
|
+ Bytef *deflated;
|
|
|
|
+ // Deflate websocket messages over 100kb
|
|
|
|
+ int use_deflate = dataLen > 100 * 1024 && conn->accept_gzip;
|
|
|
|
+#endif
|
|
|
|
+
|
|
#if defined(GCC_DIAGNOSTIC)
|
|
#if defined(GCC_DIAGNOSTIC)
|
|
/* Disable spurious conversion warning for GCC */
|
|
/* Disable spurious conversion warning for GCC */
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wconversion"
|
|
#pragma GCC diagnostic ignored "-Wconversion"
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
+ /* TODO: Check if this lock should be moved to user land.
|
|
|
|
+ * Currently the server sets this lock for websockets, but
|
|
|
|
+ * not for any other connection. It must be set for every
|
|
|
|
+ * conn read/written by more than one thread, no matter if
|
|
|
|
+ * it is a websocket or regular connection. */
|
|
|
|
+ (void)mg_lock_connection(conn);
|
|
|
|
+
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+ if (use_deflate) {
|
|
|
|
+ if (!conn->websocket_deflate_initialized) {
|
|
|
|
+ if (websocket_deflate_initialize(conn) != Z_OK) return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Deflating the message
|
|
|
|
+ header[0] = 0xC0u | (unsigned char)((unsigned)opcode & 0xf);
|
|
|
|
+ conn->websocket_deflate_state.avail_in = dataLen;
|
|
|
|
+ conn->websocket_deflate_state.next_in = (unsigned char *)data;
|
|
|
|
+ deflated_size = compressBound(dataLen);
|
|
|
|
+ deflated = mg_calloc(deflated_size, sizeof(Bytef));
|
|
|
|
+ conn->websocket_deflate_state.avail_out = deflated_size;
|
|
|
|
+ conn->websocket_deflate_state.next_out = deflated =
|
|
|
|
+ mg_calloc(deflated_size, sizeof(Bytef));
|
|
|
|
+ deflate(&conn->websocket_deflate_state, conn->websocket_deflate_flush);
|
|
|
|
+ dataLen = deflated_size - conn->websocket_deflate_state.avail_out -
|
|
|
|
+ 4; // Strip trailing 0x00 0x00 0xff 0xff bytes
|
|
|
|
+ } else
|
|
|
|
+#endif
|
|
header[0] = 0x80u | (unsigned char)((unsigned)opcode & 0xf);
|
|
header[0] = 0x80u | (unsigned char)((unsigned)opcode & 0xf);
|
|
|
|
|
|
#if defined(GCC_DIAGNOSTIC)
|
|
#if defined(GCC_DIAGNOSTIC)
|
|
@@ -12923,20 +13074,19 @@ mg_websocket_write_exec(struct mg_connection *conn,
|
|
* push(), although that is only a problem if the packet is large or
|
|
* push(), although that is only a problem if the packet is large or
|
|
* outgoing buffer is full). */
|
|
* outgoing buffer is full). */
|
|
|
|
|
|
- /* TODO: Check if this lock should be moved to user land.
|
|
|
|
- * Currently the server sets this lock for websockets, but
|
|
|
|
- * not for any other connection. It must be set for every
|
|
|
|
- * conn read/written by more than one thread, no matter if
|
|
|
|
- * it is a websocket or regular connection. */
|
|
|
|
- (void)mg_lock_connection(conn);
|
|
|
|
-
|
|
|
|
retval = mg_write(conn, header, headerLen);
|
|
retval = mg_write(conn, header, headerLen);
|
|
if (retval != (int)headerLen) {
|
|
if (retval != (int)headerLen) {
|
|
/* Did not send complete header */
|
|
/* Did not send complete header */
|
|
retval = -1;
|
|
retval = -1;
|
|
} else {
|
|
} else {
|
|
if (dataLen > 0) {
|
|
if (dataLen > 0) {
|
|
- retval = mg_write(conn, data, dataLen);
|
|
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+ if (use_deflate) {
|
|
|
|
+ retval = mg_write(conn, deflated, dataLen);
|
|
|
|
+ mg_free(deflated);
|
|
|
|
+ } else
|
|
|
|
+#endif
|
|
|
|
+ retval = mg_write(conn, data, dataLen);
|
|
}
|
|
}
|
|
/* if dataLen == 0, the header length (2) is returned */
|
|
/* if dataLen == 0, the header length (2) is returned */
|
|
}
|
|
}
|
|
@@ -13153,6 +13303,10 @@ handle_websocket_request(struct mg_connection *conn,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#if defined(USE_ZLIB)
|
|
|
|
+ websocket_deflate_negotiate(conn);
|
|
|
|
+#endif
|
|
|
|
+
|
|
if ((ws_connect_handler != NULL)
|
|
if ((ws_connect_handler != NULL)
|
|
&& (ws_connect_handler(conn, cbData) != 0)) {
|
|
&& (ws_connect_handler(conn, cbData) != 0)) {
|
|
/* C callback has returned non-zero, do not proceed with
|
|
/* C callback has returned non-zero, do not proceed with
|