mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
908 lines
24 KiB
C
908 lines
24 KiB
C
/*
|
|
This file is part of libmicrohttpd
|
|
Copyright (C) 2020 Christian Grothoff, Silvio Clecio (and other
|
|
contributing authors)
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* @file websocket_threaded_example.c
|
|
* @brief example for how to provide a tiny threaded websocket server
|
|
* @author Silvio Clecio (silvioprog)
|
|
*/
|
|
|
|
/* TODO: allow to send large messages. */
|
|
|
|
#include "platform.h"
|
|
#include <pthread.h>
|
|
#include <microhttpd.h>
|
|
|
|
#define CHAT_PAGE \
|
|
"<html>\n" \
|
|
"<head>\n" \
|
|
"<title>WebSocket chat</title>\n" \
|
|
"<script>\n" \
|
|
"document.addEventListener('DOMContentLoaded', function() {\n" \
|
|
" const ws = new WebSocket('ws:// ' + window.location.host);\n" /* \
|
|
" const btn = document.getElementById('send');\n" \
|
|
" const msg = document.getElementById('msg');\n" \
|
|
" const log = document.getElementById('log');\n" \
|
|
" ws.onopen = function() {\n" \
|
|
" log.value += 'Connected\\n';\n" \
|
|
" };\n" \
|
|
" ws.onclose = function() {\n" \
|
|
" log.value += 'Disconnected\\n';\n" \
|
|
" };\n" \
|
|
" ws.onmessage = function(ev) {\n" \
|
|
" log.value += ev.data + '\\n';\n" \
|
|
" };\n" \
|
|
" btn.onclick = function() {\n" \
|
|
" log.value += '<You>: ' + msg.value + '\\n';\n" \
|
|
" ws.send(msg.value);\n" \
|
|
" };\n" \
|
|
" msg.onkeyup = function(ev) {\n" \
|
|
" if (ev.keyCode === 13) {\n" \
|
|
" ev.preventDefault();\n" \
|
|
" ev.stopPropagation();\n" \
|
|
" btn.click();\n" \
|
|
" msg.value = '';\n" \
|
|
" }\n" \
|
|
" };\n" \
|
|
"});\n" \
|
|
"</script>\n" \
|
|
"</head>\n" \
|
|
"<body>\n" \
|
|
"<input type='text' id='msg' autofocus/>\n" \
|
|
"<input type='button' id='send' value='Send' /><br /><br />\n" \
|
|
"<textarea id='log' rows='20' cols='28'></textarea>\n" \
|
|
"</body>\n" \
|
|
"</html>" */
|
|
#define BAD_REQUEST_PAGE \
|
|
"<html>\n" \
|
|
"<head>\n" \
|
|
"<title>WebSocket chat</title>\n" \
|
|
"</head>\n" \
|
|
"<body>\n" \
|
|
"Bad Request\n" \
|
|
"</body>\n" \
|
|
"</html>\n"
|
|
#define UPGRADE_REQUIRED_PAGE \
|
|
"<html>\n" \
|
|
"<head>\n" \
|
|
"<title>WebSocket chat</title>\n" \
|
|
"</head>\n" \
|
|
"<body>\n" \
|
|
"Upgrade required\n" \
|
|
"</body>\n" \
|
|
"</html>\n"
|
|
|
|
#define WS_SEC_WEBSOCKET_VERSION "13"
|
|
#define WS_UPGRADE_VALUE "websocket"
|
|
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
#define WS_GUID_LEN 36
|
|
#define WS_KEY_LEN 24
|
|
#define WS_KEY_GUID_LEN ((WS_KEY_LEN) + (WS_GUID_LEN))
|
|
#define WS_FIN 128
|
|
#define WS_OPCODE_TEXT_FRAME 1
|
|
#define WS_OPCODE_CON_CLOSE_FRAME 8
|
|
|
|
#define MAX_CLIENTS 10
|
|
|
|
static MHD_socket CLIENT_SOCKS[MAX_CLIENTS];
|
|
|
|
static pthread_mutex_t MUTEX = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
struct WsData
|
|
{
|
|
struct MHD_UpgradeResponseHandle *urh;
|
|
MHD_socket sock;
|
|
};
|
|
|
|
|
|
/********** begin SHA-1 **********/
|
|
|
|
|
|
#define SHA1HashSize 20
|
|
|
|
#define SHA1CircularShift(bits, word) \
|
|
(((word) << (bits)) | ((word) >> (32 - (bits))))
|
|
|
|
enum SHA1_RESULT
|
|
{
|
|
SHA1_RESULT_SUCCESS = 0,
|
|
SHA1_RESULT_NULL = 1,
|
|
SHA1_RESULT_STATE_ERROR = 2
|
|
};
|
|
|
|
struct SHA1Context
|
|
{
|
|
uint32_t intermediate_hash[SHA1HashSize / 4];
|
|
uint32_t length_low;
|
|
uint32_t length_high;
|
|
int_least16_t message_block_index;
|
|
unsigned char message_block[64];
|
|
int computed;
|
|
int corrupted;
|
|
};
|
|
|
|
static void
|
|
SHA1ProcessMessageBlock (struct SHA1Context *context)
|
|
{
|
|
const uint32_t K[] = { 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
|
|
int i;
|
|
uint32_t temp;
|
|
uint32_t W[80];
|
|
uint32_t A, B, C, D, E;
|
|
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
W[i] = context->message_block[i * 4] << 24;
|
|
W[i] |= context->message_block[i * 4 + 1] << 16;
|
|
W[i] |= context->message_block[i * 4 + 2] << 8;
|
|
W[i] |= context->message_block[i * 4 + 3];
|
|
}
|
|
for (i = 16; i < 80; i++)
|
|
{
|
|
W[i]
|
|
= SHA1CircularShift (1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]);
|
|
}
|
|
A = context->intermediate_hash[0];
|
|
B = context->intermediate_hash[1];
|
|
C = context->intermediate_hash[2];
|
|
D = context->intermediate_hash[3];
|
|
E = context->intermediate_hash[4];
|
|
for (i = 0; i < 20; i++)
|
|
{
|
|
temp = SHA1CircularShift (5, A) + ((B & C) | ((~B) & D)) + E + W[i]
|
|
+ K[0];
|
|
E = D;
|
|
D = C;
|
|
C = SHA1CircularShift (30, B);
|
|
B = A;
|
|
A = temp;
|
|
}
|
|
for (i = 20; i < 40; i++)
|
|
{
|
|
temp = SHA1CircularShift (5, A) + (B ^ C ^ D) + E + W[i] + K[1];
|
|
E = D;
|
|
D = C;
|
|
C = SHA1CircularShift (30, B);
|
|
B = A;
|
|
A = temp;
|
|
}
|
|
for (i = 40; i < 60; i++)
|
|
{
|
|
temp = SHA1CircularShift (5, A) + ((B & C) | (B & D) | (C & D)) + E
|
|
+ W[i] + K[2];
|
|
E = D;
|
|
D = C;
|
|
C = SHA1CircularShift (30, B);
|
|
B = A;
|
|
A = temp;
|
|
}
|
|
for (i = 60; i < 80; i++)
|
|
{
|
|
temp = SHA1CircularShift (5, A) + (B ^ C ^ D) + E + W[i] + K[3];
|
|
E = D;
|
|
D = C;
|
|
C = SHA1CircularShift (30, B);
|
|
B = A;
|
|
A = temp;
|
|
}
|
|
context->intermediate_hash[0] += A;
|
|
context->intermediate_hash[1] += B;
|
|
context->intermediate_hash[2] += C;
|
|
context->intermediate_hash[3] += D;
|
|
context->intermediate_hash[4] += E;
|
|
context->message_block_index = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
SHA1PadMessage (struct SHA1Context *context)
|
|
{
|
|
if (context->message_block_index > 55)
|
|
{
|
|
context->message_block[context->message_block_index++] = 0x80;
|
|
while (context->message_block_index < 64)
|
|
{
|
|
context->message_block[context->message_block_index++] = 0;
|
|
}
|
|
SHA1ProcessMessageBlock (context);
|
|
while (context->message_block_index < 56)
|
|
{
|
|
context->message_block[context->message_block_index++] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
context->message_block[context->message_block_index++] = 0x80;
|
|
while (context->message_block_index < 56)
|
|
{
|
|
context->message_block[context->message_block_index++] = 0;
|
|
}
|
|
}
|
|
context->message_block[56] = context->length_high >> 24;
|
|
context->message_block[57] = context->length_high >> 16;
|
|
context->message_block[58] = context->length_high >> 8;
|
|
context->message_block[59] = context->length_high;
|
|
context->message_block[60] = context->length_low >> 24;
|
|
context->message_block[61] = context->length_low >> 16;
|
|
context->message_block[62] = context->length_low >> 8;
|
|
context->message_block[63] = context->length_low;
|
|
SHA1ProcessMessageBlock (context);
|
|
}
|
|
|
|
|
|
static enum SHA1_RESULT
|
|
SHA1Reset (struct SHA1Context *context)
|
|
{
|
|
if (! context)
|
|
{
|
|
return SHA1_RESULT_NULL;
|
|
}
|
|
context->length_low = 0;
|
|
context->length_high = 0;
|
|
context->message_block_index = 0;
|
|
context->intermediate_hash[0] = 0x67452301;
|
|
context->intermediate_hash[1] = 0xEFCDAB89;
|
|
context->intermediate_hash[2] = 0x98BADCFE;
|
|
context->intermediate_hash[3] = 0x10325476;
|
|
context->intermediate_hash[4] = 0xC3D2E1F0;
|
|
context->computed = 0;
|
|
context->corrupted = 0;
|
|
return SHA1_RESULT_SUCCESS;
|
|
}
|
|
|
|
|
|
static enum SHA1_RESULT
|
|
SHA1Result (struct SHA1Context *context, unsigned char
|
|
Message_Digest[SHA1HashSize])
|
|
{
|
|
int i;
|
|
|
|
if (! context || ! Message_Digest)
|
|
{
|
|
return SHA1_RESULT_NULL;
|
|
}
|
|
if (context->corrupted)
|
|
{
|
|
return context->corrupted;
|
|
}
|
|
if (! context->computed)
|
|
{
|
|
SHA1PadMessage (context);
|
|
for (i = 0; i < 64; ++i)
|
|
{
|
|
context->message_block[i] = 0;
|
|
}
|
|
context->length_low = 0;
|
|
context->length_high = 0;
|
|
context->computed = 1;
|
|
}
|
|
for (i = 0; i < SHA1HashSize; ++i)
|
|
{
|
|
Message_Digest[i]
|
|
= context->intermediate_hash[i >> 2] >> 8 * (3 - (i & 0x03));
|
|
}
|
|
return SHA1_RESULT_SUCCESS;
|
|
}
|
|
|
|
|
|
static enum SHA1_RESULT
|
|
SHA1Input (struct SHA1Context *context, const unsigned char *message_array,
|
|
unsigned length)
|
|
{
|
|
if (! length)
|
|
{
|
|
return SHA1_RESULT_SUCCESS;
|
|
}
|
|
if (! context || ! message_array)
|
|
{
|
|
return SHA1_RESULT_NULL;
|
|
}
|
|
if (context->computed)
|
|
{
|
|
context->corrupted = SHA1_RESULT_STATE_ERROR;
|
|
return SHA1_RESULT_STATE_ERROR;
|
|
}
|
|
if (context->corrupted)
|
|
{
|
|
return context->corrupted;
|
|
}
|
|
while (length-- && ! context->corrupted)
|
|
{
|
|
context->message_block[context->message_block_index++]
|
|
= (*message_array & 0xFF);
|
|
context->length_low += 8;
|
|
if (context->length_low == 0)
|
|
{
|
|
context->length_high++;
|
|
if (context->length_high == 0)
|
|
{
|
|
context->corrupted = 1;
|
|
}
|
|
}
|
|
if (context->message_block_index == 64)
|
|
{
|
|
SHA1ProcessMessageBlock (context);
|
|
}
|
|
message_array++;
|
|
}
|
|
return SHA1_RESULT_SUCCESS;
|
|
}
|
|
|
|
|
|
/********** end SHA-1 **********/
|
|
|
|
|
|
/********** begin Base64 **********/
|
|
|
|
|
|
ssize_t
|
|
BASE64Encode (const void *in, size_t len, char **output)
|
|
{
|
|
#define FILLCHAR '='
|
|
const char *cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz"
|
|
"0123456789+/";
|
|
const char *data = in;
|
|
char *opt;
|
|
ssize_t ret;
|
|
size_t i;
|
|
char c;
|
|
ret = 0;
|
|
|
|
opt = malloc (2 + (len * 4 / 3) + 8);
|
|
if (NULL == opt)
|
|
{
|
|
return -1;
|
|
}
|
|
for (i = 0; i < len; ++i)
|
|
{
|
|
c = (data[i] >> 2) & 0x3F;
|
|
opt[ret++] = cvt[(int) c];
|
|
c = (data[i] << 4) & 0x3F;
|
|
if (++i < len)
|
|
{
|
|
c |= (data[i] >> 4) & 0x0F;
|
|
}
|
|
opt[ret++] = cvt[(int) c];
|
|
if (i < len)
|
|
{
|
|
c = (data[i] << 2) & 0x3F;
|
|
if (++i < len)
|
|
{
|
|
c |= (data[i] >> 6) & 0x03;
|
|
}
|
|
opt[ret++] = cvt[(int) c];
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
opt[ret++] = FILLCHAR;
|
|
}
|
|
if (i < len)
|
|
{
|
|
c = data[i] & 0x3F;
|
|
opt[ret++] = cvt[(int) c];
|
|
}
|
|
else
|
|
{
|
|
opt[ret++] = FILLCHAR;
|
|
}
|
|
}
|
|
*output = opt;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/********** end Base64 **********/
|
|
|
|
|
|
static enum MHD_Result
|
|
is_websocket_request (struct MHD_Connection *con, const char *upg_header,
|
|
const char *con_header)
|
|
{
|
|
|
|
(void) con; /* Unused. Silent compiler warning. */
|
|
|
|
return (upg_header != NULL) && (con_header != NULL)
|
|
&& (0 == strcmp (upg_header, WS_UPGRADE_VALUE))
|
|
&& (NULL != strstr (con_header, "Upgrade"))
|
|
? MHD_YES
|
|
: MHD_NO;
|
|
}
|
|
|
|
|
|
static enum MHD_Result
|
|
send_chat_page (struct MHD_Connection *con)
|
|
{
|
|
struct MHD_Response *res;
|
|
enum MHD_Result ret;
|
|
|
|
res = MHD_create_response_from_buffer (strlen (CHAT_PAGE), (void *) CHAT_PAGE,
|
|
MHD_RESPMEM_PERSISTENT);
|
|
ret = MHD_queue_response (con, MHD_HTTP_OK, res);
|
|
MHD_destroy_response (res);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static enum MHD_Result
|
|
send_bad_request (struct MHD_Connection *con)
|
|
{
|
|
struct MHD_Response *res;
|
|
enum MHD_Result ret;
|
|
|
|
res = MHD_create_response_from_buffer (strlen (BAD_REQUEST_PAGE),
|
|
(void *) BAD_REQUEST_PAGE,
|
|
MHD_RESPMEM_PERSISTENT);
|
|
ret = MHD_queue_response (con, MHD_HTTP_BAD_REQUEST, res);
|
|
MHD_destroy_response (res);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static enum MHD_Result
|
|
send_upgrade_required (struct MHD_Connection *con)
|
|
{
|
|
struct MHD_Response *res;
|
|
enum MHD_Result ret;
|
|
|
|
res = MHD_create_response_from_buffer (strlen (UPGRADE_REQUIRED_PAGE),
|
|
(void *) UPGRADE_REQUIRED_PAGE,
|
|
MHD_RESPMEM_PERSISTENT);
|
|
MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION,
|
|
WS_SEC_WEBSOCKET_VERSION);
|
|
ret = MHD_queue_response (con, MHD_HTTP_UPGRADE_REQUIRED, res);
|
|
MHD_destroy_response (res);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static enum MHD_Result
|
|
ws_get_accept_value (const char *key, char **val)
|
|
{
|
|
struct SHA1Context ctx;
|
|
unsigned char hash[SHA1HashSize];
|
|
char *str;
|
|
ssize_t len;
|
|
|
|
if ( (NULL == key) || (WS_KEY_LEN != strlen (key)))
|
|
{
|
|
return MHD_NO;
|
|
}
|
|
str = malloc (WS_KEY_LEN + WS_GUID_LEN + 1);
|
|
if (NULL == str)
|
|
{
|
|
return MHD_NO;
|
|
}
|
|
strncpy (str, key, (WS_KEY_LEN + 1));
|
|
strncpy (str + WS_KEY_LEN, WS_GUID, WS_GUID_LEN + 1);
|
|
SHA1Reset (&ctx);
|
|
SHA1Input (&ctx, (const unsigned char *) str, WS_KEY_GUID_LEN);
|
|
if (SHA1_RESULT_SUCCESS != SHA1Result (&ctx, hash))
|
|
{
|
|
free (str);
|
|
return MHD_NO;
|
|
}
|
|
free (str);
|
|
len = BASE64Encode (hash, SHA1HashSize, val);
|
|
if (-1 == len)
|
|
{
|
|
return MHD_NO;
|
|
}
|
|
(*val)[len] = '\0';
|
|
return MHD_YES;
|
|
}
|
|
|
|
|
|
static void
|
|
make_blocking (MHD_socket fd)
|
|
{
|
|
#if defined(MHD_POSIX_SOCKETS)
|
|
int flags;
|
|
|
|
flags = fcntl (fd, F_GETFL);
|
|
if (-1 == flags)
|
|
abort ();
|
|
if ((flags & ~O_NONBLOCK) != flags)
|
|
if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
|
|
abort ();
|
|
#elif defined(MHD_WINSOCK_SOCKETS)
|
|
unsigned long flags = 0;
|
|
|
|
if (0 != ioctlsocket (fd, (int) FIONBIO, &flags))
|
|
abort ();
|
|
#endif /* MHD_WINSOCK_SOCKETS */
|
|
}
|
|
|
|
|
|
static size_t
|
|
send_all (MHD_socket sock, const unsigned char *buf, size_t len)
|
|
{
|
|
ssize_t ret;
|
|
size_t off;
|
|
|
|
for (off = 0; off < len; off += ret)
|
|
{
|
|
ret = send (sock, (const void *) &buf[off], len - off, 0);
|
|
if (0 > ret)
|
|
{
|
|
if (EAGAIN == errno)
|
|
{
|
|
ret = 0;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
if (0 == ret)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return off;
|
|
}
|
|
|
|
|
|
static int
|
|
ws_send_frame (MHD_socket sock, const char *msg, size_t length)
|
|
{
|
|
unsigned char *response;
|
|
unsigned char frame[10];
|
|
unsigned char idx_first_rdata;
|
|
int idx_response;
|
|
int output;
|
|
MHD_socket isock;
|
|
size_t i;
|
|
|
|
frame[0] = (WS_FIN | WS_OPCODE_TEXT_FRAME);
|
|
if (length <= 125)
|
|
{
|
|
frame[1] = length & 0x7F;
|
|
idx_first_rdata = 2;
|
|
}
|
|
#if SIZEOF_SIZE_T > 4
|
|
else if (0xFFFF < length)
|
|
{
|
|
frame[1] = 127;
|
|
frame[2] = (unsigned char) ((length >> 56) & 0xFF);
|
|
frame[3] = (unsigned char) ((length >> 48) & 0xFF);
|
|
frame[4] = (unsigned char) ((length >> 40) & 0xFF);
|
|
frame[5] = (unsigned char) ((length >> 32) & 0xFF);
|
|
frame[6] = (unsigned char) ((length >> 24) & 0xFF);
|
|
frame[7] = (unsigned char) ((length >> 16) & 0xFF);
|
|
frame[8] = (unsigned char) ((length >> 8) & 0xFF);
|
|
frame[9] = (unsigned char) (length & 0xFF);
|
|
idx_first_rdata = 10;
|
|
}
|
|
#endif /* SIZEOF_SIZE_T > 4 */
|
|
else
|
|
{
|
|
frame[1] = 126;
|
|
frame[2] = (length >> 8) & 0xFF;
|
|
frame[3] = length & 0xFF;
|
|
idx_first_rdata = 4;
|
|
}
|
|
idx_response = 0;
|
|
response = malloc (idx_first_rdata + length + 1);
|
|
if (NULL == response)
|
|
{
|
|
return -1;
|
|
}
|
|
for (i = 0; i < idx_first_rdata; i++)
|
|
{
|
|
response[i] = frame[i];
|
|
idx_response++;
|
|
}
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
response[idx_response] = msg[i];
|
|
idx_response++;
|
|
}
|
|
response[idx_response] = '\0';
|
|
output = 0;
|
|
pthread_mutex_lock (&MUTEX);
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
isock = CLIENT_SOCKS[i];
|
|
if ((isock != MHD_INVALID_SOCKET) && (isock != sock))
|
|
{
|
|
output += send_all (isock, response, idx_response);
|
|
}
|
|
}
|
|
pthread_mutex_unlock (&MUTEX);
|
|
free (response);
|
|
return output;
|
|
}
|
|
|
|
|
|
static unsigned char *
|
|
ws_receive_frame (unsigned char *frame, ssize_t *length, int *type)
|
|
{
|
|
unsigned char masks[4];
|
|
unsigned char mask;
|
|
unsigned char *msg;
|
|
unsigned char flength;
|
|
unsigned char idx_first_mask;
|
|
unsigned char idx_first_data;
|
|
ssize_t data_length;
|
|
int i;
|
|
int j;
|
|
|
|
msg = NULL;
|
|
if (frame[0] == (WS_FIN | WS_OPCODE_TEXT_FRAME))
|
|
{
|
|
*type = WS_OPCODE_TEXT_FRAME;
|
|
idx_first_mask = 2;
|
|
mask = frame[1];
|
|
flength = mask & 0x7F;
|
|
if (flength == 126)
|
|
{
|
|
idx_first_mask = 4;
|
|
}
|
|
else if (flength == 127)
|
|
{
|
|
idx_first_mask = 10;
|
|
}
|
|
idx_first_data = idx_first_mask + 4;
|
|
data_length = *length - idx_first_data;
|
|
masks[0] = frame[idx_first_mask + 0];
|
|
masks[1] = frame[idx_first_mask + 1];
|
|
masks[2] = frame[idx_first_mask + 2];
|
|
masks[3] = frame[idx_first_mask + 3];
|
|
msg = malloc (data_length + 1);
|
|
if (NULL != msg)
|
|
{
|
|
for (i = idx_first_data, j = 0; i < *length; i++, j++)
|
|
{
|
|
msg[j] = frame[i] ^ masks[j % 4];
|
|
}
|
|
*length = data_length;
|
|
msg[j] = '\0';
|
|
}
|
|
}
|
|
else if (frame[0] == (WS_FIN | WS_OPCODE_CON_CLOSE_FRAME))
|
|
{
|
|
*type = WS_OPCODE_CON_CLOSE_FRAME;
|
|
}
|
|
else
|
|
{
|
|
*type = frame[0] & 0x0F;
|
|
}
|
|
return msg;
|
|
}
|
|
|
|
|
|
static void *
|
|
run_usock (void *cls)
|
|
{
|
|
struct WsData *ws = cls;
|
|
struct MHD_UpgradeResponseHandle *urh = ws->urh;
|
|
unsigned char buf[2048];
|
|
unsigned char *msg;
|
|
char client[20];
|
|
char *text;
|
|
ssize_t got;
|
|
size_t size;
|
|
int type;
|
|
int sent;
|
|
int i;
|
|
|
|
make_blocking (ws->sock);
|
|
while (1)
|
|
{
|
|
got = recv (ws->sock, (void *) buf, sizeof (buf), 0);
|
|
if (0 >= got)
|
|
{
|
|
break;
|
|
}
|
|
msg = ws_receive_frame (buf, &got, &type);
|
|
if (NULL == msg)
|
|
{
|
|
break;
|
|
}
|
|
if (type == WS_OPCODE_TEXT_FRAME)
|
|
{
|
|
size = sprintf (client, "User#%d: ", (int) ws->sock);
|
|
size += got;
|
|
text = malloc (size);
|
|
if (NULL != text)
|
|
{
|
|
sprintf (text, "%s%s", client, msg);
|
|
sent = ws_send_frame (ws->sock, text, size);
|
|
free (text);
|
|
}
|
|
else
|
|
{
|
|
sent = -1;
|
|
}
|
|
free (msg);
|
|
if (-1 == sent)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (type == WS_OPCODE_CON_CLOSE_FRAME)
|
|
{
|
|
free (msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
pthread_mutex_lock (&MUTEX);
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if (CLIENT_SOCKS[i] == ws->sock)
|
|
{
|
|
CLIENT_SOCKS[i] = MHD_INVALID_SOCKET;
|
|
break;
|
|
}
|
|
}
|
|
pthread_mutex_unlock (&MUTEX);
|
|
free (ws);
|
|
MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
uh_cb (void *cls, struct MHD_Connection *con, void *con_cls,
|
|
const char *extra_in, size_t extra_in_size, MHD_socket sock,
|
|
struct MHD_UpgradeResponseHandle *urh)
|
|
{
|
|
struct WsData *ws;
|
|
pthread_t pt;
|
|
int sock_overflow;
|
|
int i;
|
|
|
|
(void) cls; /* Unused. Silent compiler warning. */
|
|
(void) con; /* Unused. Silent compiler warning. */
|
|
(void) con_cls; /* Unused. Silent compiler warning. */
|
|
(void) extra_in; /* Unused. Silent compiler warning. */
|
|
(void) extra_in_size; /* Unused. Silent compiler warning. */
|
|
|
|
ws = malloc (sizeof (struct WsData));
|
|
if (NULL == ws)
|
|
abort ();
|
|
memset (ws, 0, sizeof (struct WsData));
|
|
ws->sock = sock;
|
|
ws->urh = urh;
|
|
sock_overflow = MHD_YES;
|
|
pthread_mutex_lock (&MUTEX);
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if (MHD_INVALID_SOCKET == CLIENT_SOCKS[i])
|
|
{
|
|
CLIENT_SOCKS[i] = ws->sock;
|
|
sock_overflow = MHD_NO;
|
|
break;
|
|
}
|
|
}
|
|
pthread_mutex_unlock (&MUTEX);
|
|
if (sock_overflow)
|
|
{
|
|
free (ws);
|
|
MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE);
|
|
return;
|
|
}
|
|
if (0 != pthread_create (&pt, NULL, &run_usock, ws))
|
|
abort ();
|
|
/* Note that by detaching like this we make it impossible to ensure
|
|
a clean shutdown, as the we stop the daemon even if a worker thread
|
|
is still running. Alas, this is a simple example... */
|
|
pthread_detach (pt);
|
|
}
|
|
|
|
|
|
static enum MHD_Result
|
|
ahc_cb (void *cls, struct MHD_Connection *con, const char *url,
|
|
const char *method, const char *version, const char *upload_data,
|
|
size_t *upload_data_size, void **ptr)
|
|
{
|
|
struct MHD_Response *res;
|
|
const char *upg_header;
|
|
const char *con_header;
|
|
const char *ws_version_header;
|
|
const char *ws_key_header;
|
|
char *ws_ac_value;
|
|
enum MHD_Result ret;
|
|
size_t key_size;
|
|
|
|
(void) cls; /* Unused. Silent compiler warning. */
|
|
(void) url; /* Unused. Silent compiler warning. */
|
|
(void) upload_data; /* Unused. Silent compiler warning. */
|
|
(void) upload_data_size; /* Unused. Silent compiler warning. */
|
|
|
|
if (NULL == *ptr)
|
|
{
|
|
*ptr = (void *) 1;
|
|
return MHD_YES;
|
|
}
|
|
*ptr = NULL;
|
|
upg_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_UPGRADE);
|
|
con_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_CONNECTION);
|
|
if (MHD_NO == is_websocket_request (con, upg_header, con_header))
|
|
{
|
|
return send_chat_page (con);
|
|
}
|
|
if ((0 != strcmp (method, MHD_HTTP_METHOD_GET))
|
|
|| (0 != strcmp (version, MHD_HTTP_VERSION_1_1)))
|
|
{
|
|
return send_bad_request (con);
|
|
}
|
|
ws_version_header = MHD_lookup_connection_value (
|
|
con, MHD_HEADER_KIND, MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
|
|
if ((NULL == ws_version_header)
|
|
|| (0 != strcmp (ws_version_header, WS_SEC_WEBSOCKET_VERSION)))
|
|
{
|
|
return send_upgrade_required (con);
|
|
}
|
|
ret = MHD_lookup_connection_value_n (
|
|
con, MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY,
|
|
strlen (MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY),
|
|
&ws_key_header, &key_size);
|
|
if ((MHD_NO == ret) || (key_size != WS_KEY_LEN))
|
|
{
|
|
return send_bad_request (con);
|
|
}
|
|
ret = ws_get_accept_value (ws_key_header, &ws_ac_value);
|
|
if (MHD_NO == ret)
|
|
{
|
|
return ret;
|
|
}
|
|
res = MHD_create_response_for_upgrade (&uh_cb, NULL);
|
|
MHD_add_response_header (res, MHD_HTTP_HEADER_UPGRADE, WS_UPGRADE_VALUE);
|
|
MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
|
|
ws_ac_value);
|
|
free (ws_ac_value);
|
|
ret = MHD_queue_response (con, MHD_HTTP_SWITCHING_PROTOCOLS, res);
|
|
MHD_destroy_response (res);
|
|
return ret;
|
|
}
|
|
|
|
|
|
int
|
|
main (int argc, char *const *argv)
|
|
{
|
|
struct MHD_Daemon *d;
|
|
uint16_t port;
|
|
size_t i;
|
|
|
|
if (argc != 2)
|
|
{
|
|
printf ("%s PORT\n", argv[0]);
|
|
return 1;
|
|
}
|
|
port = atoi (argv[1]);
|
|
d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_AUTO_INTERNAL_THREAD
|
|
| MHD_USE_ERROR_LOG,
|
|
port, NULL, NULL, &ahc_cb, &port, MHD_OPTION_END);
|
|
if (NULL == d)
|
|
return 1;
|
|
for (i = 0; i < sizeof(CLIENT_SOCKS) / sizeof(CLIENT_SOCKS[0]); ++i)
|
|
CLIENT_SOCKS[i] = MHD_INVALID_SOCKET;
|
|
(void) getc (stdin);
|
|
MHD_stop_daemon (d);
|
|
return 0;
|
|
}
|