summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSebastien Helleu <flashcode@flashtux.org>2013-02-10 20:22:13 +0100
committerSebastien Helleu <flashcode@flashtux.org>2013-02-10 20:22:13 +0100
commitc2aeb69c46a8f0222aed935afc46e0b27cbc94a0 (patch)
treed217e5bfc2dbfe7e0d50dbcd7a0b24b032681441 /src
parenteb11921f1633db940df4b0c02a43df1360d39b96 (diff)
downloadweechat-c2aeb69c46a8f0222aed935afc46e0b27cbc94a0.zip
relay: add experimental websocket server support (RFC 6455) for irc and weechat protocols, new option relay.network.websocket_allowed_origins
It is a partial implementation of RFC 6455: fragmentation and control frames are not yet supported. Text and binary frames are supported.
Diffstat (limited to 'src')
-rw-r--r--src/plugins/relay/CMakeLists.txt3
-rw-r--r--src/plugins/relay/Makefile.am4
-rw-r--r--src/plugins/relay/irc/relay-irc.c36
-rw-r--r--src/plugins/relay/irc/relay-irc.h2
-rw-r--r--src/plugins/relay/relay-client.c435
-rw-r--r--src/plugins/relay/relay-client.h20
-rw-r--r--src/plugins/relay/relay-config.c48
-rw-r--r--src/plugins/relay/relay-config.h1
-rw-r--r--src/plugins/relay/relay-raw.c128
-rw-r--r--src/plugins/relay/relay-raw.h3
-rw-r--r--src/plugins/relay/relay-server.c2
-rw-r--r--src/plugins/relay/relay-websocket.c379
-rw-r--r--src/plugins/relay/relay-websocket.h38
-rw-r--r--src/plugins/relay/weechat/relay-weechat-msg.c27
-rw-r--r--src/plugins/relay/weechat/relay-weechat-protocol.c10
-rw-r--r--src/plugins/relay/weechat/relay-weechat-protocol.h2
-rw-r--r--src/plugins/relay/weechat/relay-weechat.c45
17 files changed, 1011 insertions, 172 deletions
diff --git a/src/plugins/relay/CMakeLists.txt b/src/plugins/relay/CMakeLists.txt
index d442cc036..fb4f95fef 100644
--- a/src/plugins/relay/CMakeLists.txt
+++ b/src/plugins/relay/CMakeLists.txt
@@ -32,7 +32,8 @@ relay-info.c relay-info.h
relay-network.c relay-network.h
relay-raw.c relay-raw.h
relay-server.c relay-server.h
-relay-upgrade.c relay-upgrade.h)
+relay-upgrade.c relay-upgrade.h
+relay-websocket.c relay-websocket.h)
SET_TARGET_PROPERTIES(relay PROPERTIES PREFIX "")
SET (LINK_LIBS)
diff --git a/src/plugins/relay/Makefile.am b/src/plugins/relay/Makefile.am
index ff06afd8e..a8a25211f 100644
--- a/src/plugins/relay/Makefile.am
+++ b/src/plugins/relay/Makefile.am
@@ -52,7 +52,9 @@ relay_la_SOURCES = relay.c \
relay-server.c \
relay-server.h \
relay-upgrade.c \
- relay-upgrade.h
+ relay-upgrade.h \
+ relay-websocket.c \
+ relay-websocket.h
relay_la_LDFLAGS = -module
relay_la_LIBADD = $(RELAY_LFLAGS) $(ZLIB_LFLAGS) $(GNUTLS_LFLAGS)
diff --git a/src/plugins/relay/irc/relay-irc.c b/src/plugins/relay/irc/relay-irc.c
index 2f7b13ac7..50fd9622d 100644
--- a/src/plugins/relay/irc/relay-irc.c
+++ b/src/plugins/relay/irc/relay-irc.c
@@ -231,13 +231,12 @@ relay_irc_sendf (struct t_relay_client *client, const char *format, ...)
str_message = weechat_hashtable_get (hashtable_out, hash_key);
if (!str_message)
break;
- relay_raw_print (client, RELAY_RAW_FLAG_SEND, "%s", str_message);
length = strlen (str_message) + 16 + 1;
message = malloc (length);
if (message)
{
snprintf (message, length, "%s\r\n", str_message);
- relay_client_send (client, message, strlen (message));
+ relay_client_send (client, message, strlen (message), NULL);
free (message);
}
number++;
@@ -1290,9 +1289,9 @@ relay_irc_recv_command_capab (struct t_relay_client *client,
*/
void
-relay_irc_recv_one_msg (struct t_relay_client *client, char *data)
+relay_irc_recv (struct t_relay_client *client, const char *data)
{
- char *pos, str_time[128], str_signal[128], str_server_channel[256];
+ char str_time[128], str_signal[128], str_server_channel[256];
char str_command[128], *target, **irc_argv;
const char *irc_command, *irc_channel, *irc_args, *irc_args2;
int irc_argc, redirect_msg;
@@ -1304,11 +1303,6 @@ relay_irc_recv_one_msg (struct t_relay_client *client, char *data)
irc_argv = NULL;
irc_argc = 0;
- /* remove \r at the end of message */
- pos = strchr (data, '\r');
- if (pos)
- pos[0] = '\0';
-
/* display debug message */
if (weechat_relay_plugin->debug >= 2)
{
@@ -1320,9 +1314,6 @@ relay_irc_recv_one_msg (struct t_relay_client *client, char *data)
data);
}
- /* display message in raw buffer */
- relay_raw_print (client, RELAY_RAW_FLAG_RECV, "%s", data);
-
/* parse IRC message */
hash_parsed = relay_irc_message_parse (data);
if (!hash_parsed)
@@ -1708,27 +1699,6 @@ end:
}
/*
- * Reads data from a client.
- */
-
-void
-relay_irc_recv (struct t_relay_client *client, const char *data)
-{
- char **items;
- int items_count, i;
-
- items = weechat_string_split (data, "\n", 0, 0, &items_count);
- if (items)
- {
- for (i = 0; i < items_count; i++)
- {
- relay_irc_recv_one_msg (client, items[i]);
- }
- weechat_string_free_split (items);
- }
-}
-
-/*
* Closes connection with client.
*/
diff --git a/src/plugins/relay/irc/relay-irc.h b/src/plugins/relay/irc/relay-irc.h
index ca8d6f1be..3c7e87a0e 100644
--- a/src/plugins/relay/irc/relay-irc.h
+++ b/src/plugins/relay/irc/relay-irc.h
@@ -63,7 +63,7 @@ enum t_relay_irc_server_capab
};
extern void relay_irc_recv (struct t_relay_client *client,
- const char *data);
+ const char *data);
extern void relay_irc_close_connection (struct t_relay_client *client);
extern void relay_irc_alloc (struct t_relay_client *client);
extern void relay_irc_alloc_with_infolist (struct t_relay_client *client,
diff --git a/src/plugins/relay/relay-client.c b/src/plugins/relay/relay-client.c
index 93b05a243..d91b2e4ef 100644
--- a/src/plugins/relay/relay-client.c
+++ b/src/plugins/relay/relay-client.c
@@ -41,7 +41,9 @@
#include "relay-config.h"
#include "relay-buffer.h"
#include "relay-network.h"
+#include "relay-raw.h"
#include "relay-server.h"
+#include "relay-websocket.h"
char *relay_client_status_string[] = /* strings for status */
@@ -49,6 +51,9 @@ char *relay_client_status_string[] = /* strings for status */
N_("connected"), N_("auth failed"), N_("disconnected")
};
+char *relay_client_data_type_string[] = /* strings for data types */
+{ "text", "binary" };
+
struct t_relay_client *relay_clients = NULL;
struct t_relay_client *last_relay_client = NULL;
int relay_client_count = 0; /* number of clients */
@@ -217,6 +222,168 @@ relay_client_handshake_timer_cb (void *data, int remaining_calls)
#endif
/*
+ * Reads text data from a client: splits data on '\n' and keep a partial message
+ * if date does not end with '\n'.
+ */
+
+void
+relay_client_recv_text (struct t_relay_client *client, const char *data)
+{
+ char *new_partial, *raw_msg, **lines, *pos, *tmp, *handshake;
+ int i, num_lines, length, rc;
+
+ if (client->partial_message)
+ {
+ new_partial = realloc (client->partial_message,
+ strlen (client->partial_message) +
+ strlen (data) + 1);
+ if (!new_partial)
+ return;
+ client->partial_message = new_partial;
+ strcat (client->partial_message, data);
+ }
+ else
+ client->partial_message = strdup (data);
+
+ pos = strrchr (client->partial_message, '\n');
+ if (pos)
+ {
+ /* print message in raw buffer */
+ raw_msg = weechat_strndup (client->partial_message,
+ pos - client->partial_message + 1);
+ if (raw_msg)
+ {
+ relay_raw_print (client, RELAY_RAW_FLAG_RECV,
+ raw_msg, strlen (raw_msg) + 1);
+ free (raw_msg);
+ }
+
+ pos[0] = '\0';
+
+ lines = weechat_string_split (client->partial_message, "\n",
+ 0, 0, &num_lines);
+ if (lines)
+ {
+ for (i = 0; i < num_lines; i++)
+ {
+ /* remove final '\r' */
+ length = strlen (lines[i]);
+ if ((length > 0) && (lines[i][length - 1] == '\r'))
+ lines[i][length - 1] = '\0';
+
+ /* if websocket is initializing */
+ if (client->websocket == 1)
+ {
+ if (lines[i][0])
+ {
+ /* web socket is initializing, read HTTP headers */
+ relay_websocket_save_header (client, lines[i]);
+ }
+ else
+ {
+ /*
+ * empty line means that we have received all HTTP
+ * headers: then we check the validity of websocket, and
+ * if it is OK, we'll do the handshake and answer to the
+ * client
+ */
+ rc = relay_websocket_client_handshake_valid (client);
+ if (rc == 0)
+ {
+ /* handshake from client is valid */
+ handshake = relay_websocket_build_handshake (client);
+ if (handshake)
+ {
+ relay_client_send (client, handshake,
+ strlen (handshake), NULL);
+ free (handshake);
+ client->websocket = 2;
+ }
+ }
+ else
+ {
+ switch (rc)
+ {
+ case -1:
+ relay_websocket_send_http (client,
+ "400 Bad Request");
+ if (weechat_relay_plugin->debug >= 1)
+ {
+ weechat_printf_tags (NULL, "relay_client",
+ _("%s%s: invalid websocket "
+ "handshake received for "
+ "client %s%s%s"),
+ weechat_prefix ("error"),
+ RELAY_PLUGIN_NAME,
+ RELAY_COLOR_CHAT_CLIENT,
+ client->desc,
+ RELAY_COLOR_CHAT);
+ }
+ break;
+ case -2:
+ relay_websocket_send_http (client,
+ "403 Forbidden");
+ if (weechat_relay_plugin->debug >= 1)
+ {
+ weechat_printf_tags (NULL, "relay_client",
+ _("%s%s: origin \"%s\" "
+ "not allowed for websocket"),
+ weechat_prefix ("error"),
+ RELAY_PLUGIN_NAME,
+ weechat_hashtable_get (client->http_headers,
+ "Origin"));
+ }
+ break;
+ }
+ relay_client_set_status (client, RELAY_STATUS_DISCONNECTED);
+ }
+
+ /* remove HTTP headers */
+ weechat_hashtable_free (client->http_headers);
+ client->http_headers = NULL;
+
+ /*
+ * discard all received data after the handshake
+ * received from client, and return immediately
+ */
+ free (client->partial_message);
+ client->partial_message = NULL;
+ return;
+ }
+ }
+ else
+ {
+ /* receive text from client */
+ switch (client->protocol)
+ {
+ case RELAY_PROTOCOL_WEECHAT:
+ relay_weechat_recv (client, lines[i]);
+ break;
+ case RELAY_PROTOCOL_IRC:
+ relay_irc_recv (client, lines[i]);
+ break;
+ case RELAY_NUM_PROTOCOLS:
+ break;
+ }
+ }
+ }
+ weechat_string_free_split (lines);
+ }
+ if (pos[1])
+ {
+ tmp = strdup (pos + 1);
+ free (client->partial_message);
+ client->partial_message = tmp;
+ }
+ else
+ {
+ free (client->partial_message);
+ client->partial_message = NULL;
+ }
+ }
+}
+
+/*
* Reads data from a client.
*/
@@ -224,7 +391,8 @@ int
relay_client_recv_cb (void *arg_client, int fd)
{
struct t_relay_client *client;
- static char buffer[4096 + 2];
+ static char buffer[4096], decoded[4096];
+ const char *ptr_buffer;
int num_read;
/* make C compiler happy */
@@ -245,18 +413,63 @@ relay_client_recv_cb (void *arg_client, int fd)
if (num_read > 0)
{
- client->bytes_recv += num_read;
buffer[num_read] = '\0';
- switch (client->protocol)
+ ptr_buffer = buffer;
+
+ /*
+ * if we are receiving the first message from client, check if it looks
+ * like a websocket
+ */
+ if (client->bytes_recv == 0)
{
- case RELAY_PROTOCOL_WEECHAT:
- relay_weechat_recv (client, buffer);
- break;
- case RELAY_PROTOCOL_IRC:
- relay_irc_recv (client, buffer);
- break;
- case RELAY_NUM_PROTOCOLS:
- break;
+ if (relay_websocket_is_http_get_weechat (buffer))
+ {
+ /*
+ * web socket is just initializing for now, it's not accepted
+ * (we will check later with "http_headers" if web socket is
+ * valid or not)
+ */
+ client->websocket = 1;
+ client->http_headers = weechat_hashtable_new (32,
+ WEECHAT_HASHTABLE_STRING,
+ WEECHAT_HASHTABLE_STRING,
+ NULL,
+ NULL);
+ }
+ }
+
+ client->bytes_recv += num_read;
+
+ if (client->websocket == 2)
+ {
+ /* websocket used, decode message */
+ if (!relay_websocket_decode_frame ((unsigned char *)buffer, num_read,
+ (unsigned char *)decoded))
+ {
+ /* error when decoding frame: close connection */
+ weechat_printf_tags (NULL, "relay_client",
+ _("%s%s: error decoding websocket frame "
+ "for client %s%s%s"),
+ weechat_prefix ("error"), RELAY_PLUGIN_NAME,
+ RELAY_COLOR_CHAT_CLIENT,
+ client->desc,
+ RELAY_COLOR_CHAT);
+ relay_client_set_status (client, RELAY_STATUS_DISCONNECTED);
+ return WEECHAT_RC_OK;
+ }
+ ptr_buffer = decoded;
+ }
+
+ if ((client->websocket == 1)
+ || (client->recv_data_type == RELAY_CLIENT_DATA_TEXT))
+ {
+ /* websocket initializing or text data for this client */
+ relay_client_recv_text (client, ptr_buffer);
+ }
+ else
+ {
+ /* receive buffer as-is (binary data) */
+ /* currently, all supported protocols receive only text, no binary */
}
relay_buffer_refresh (NULL);
}
@@ -310,10 +523,13 @@ relay_client_recv_cb (void *arg_client, int fd)
*/
void
-relay_client_outqueue_add (struct t_relay_client *client, const char *data,
- int data_size)
+relay_client_outqueue_add (struct t_relay_client *client,
+ const char *data, int data_size,
+ int raw_flags[2], const char *raw_message[2],
+ int raw_size[2])
{
struct t_relay_client_outqueue *new_outqueue;
+ int i;
if (!client || !data || (data_size <= 0))
return;
@@ -329,6 +545,23 @@ relay_client_outqueue_add (struct t_relay_client *client, const char *data,
}
memcpy (new_outqueue->data, data, data_size);
new_outqueue->data_size = data_size;
+ for (i = 0; i < 2; i++)
+ {
+ new_outqueue->raw_flags[i] = 0;
+ new_outqueue->raw_message[i] = NULL;
+ new_outqueue->raw_size[i] = 0;
+ if (raw_message && raw_message[i] && (raw_size[i] > 0))
+ {
+ new_outqueue->raw_message[i] = malloc (raw_size[i]);
+ if (new_outqueue->raw_message[i])
+ {
+ new_outqueue->raw_flags[i] = raw_flags[i];
+ memcpy (new_outqueue->raw_message[i], raw_message[i],
+ raw_size[i]);
+ new_outqueue->raw_size[i] = raw_size[i];
+ }
+ }
+ }
new_outqueue->prev_outqueue = client->last_outqueue;
new_outqueue->next_outqueue = NULL;
@@ -367,6 +600,10 @@ relay_client_outqueue_free (struct t_relay_client *client,
/* free data */
if (outqueue->data)
free (outqueue->data);
+ if (outqueue->raw_message[0])
+ free (outqueue->raw_message[0]);
+ if (outqueue->raw_message[1])
+ free (outqueue->raw_message[1]);
free (outqueue);
/* set new head */
@@ -389,18 +626,88 @@ relay_client_outqueue_free_all (struct t_relay_client *client)
/*
* Sends data to client (adds in out queue if it's impossible to send now).
*
+ * If "message_raw_buffer" is not NULL, it is used for display in raw buffer
+ * and replaces display of data, which is default.
+ *
* Returns number of bytes sent to client, -1 if error.
*/
int
relay_client_send (struct t_relay_client *client, const char *data,
- int data_size)
+ int data_size, const char *message_raw_buffer)
{
- int num_sent;
+ int num_sent, raw_size[2], raw_flags[2], i;
+ char *websocket_frame;
+ unsigned long long length_frame;
+ const char *ptr_data, *raw_msg[2];
if (client->sock < 0)
return -1;
+ ptr_data = data;
+ websocket_frame = NULL;
+
+ /* set raw messages */
+ for (i = 0; i < 2; i++)
+ {
+ raw_flags[i] = RELAY_RAW_FLAG_SEND;
+ raw_msg[i] = NULL;
+ raw_size[i] = 0;
+ }
+ if (message_raw_buffer)
+ {
+ if (weechat_relay_plugin->debug >= 2)
+ {
+ raw_msg[0] = message_raw_buffer;
+ raw_size[0] = strlen (message_raw_buffer) + 1;
+ raw_msg[1] = data;
+ raw_size[1] = data_size;
+ raw_flags[1] |= RELAY_RAW_FLAG_BINARY;
+ if ((client->websocket == 1)
+ || (client->send_data_type == RELAY_CLIENT_DATA_TEXT))
+ {
+ raw_size[1]--;
+ }
+ }
+ else
+ {
+ raw_msg[0] = message_raw_buffer;
+ raw_size[0] = strlen (message_raw_buffer) + 1;
+ }
+ }
+ else
+ {
+ raw_msg[0] = data;
+ raw_size[0] = data_size;
+ if ((client->websocket != 1)
+ && (client->send_data_type == RELAY_CLIENT_DATA_BINARY))
+ {
+ /*
+ * set binary flag if we send binary to client
+ * (except if websocket == 1, which means that websocket is
+ * initializing, and then we are sending HTTP data, as text)
+ */
+ raw_flags[0] |= RELAY_RAW_FLAG_BINARY;
+ }
+ else
+ {
+ /* count the final '\0' in size */
+ raw_size[0]++;
+ }
+ }
+
+ /* if websocket is initialized, encode data in a websocket frame */
+ if (client->websocket == 2)
+ {
+ websocket_frame = relay_websocket_encode_frame (client, data, data_size,
+ &length_frame);
+ if (websocket_frame)
+ {
+ ptr_data = websocket_frame;
+ data_size = length_frame;
+ }
+ }
+
num_sent = -1;
/*
@@ -409,19 +716,28 @@ relay_client_send (struct t_relay_client *client, const char *data,
*/
if (client->outqueue)
{
- relay_client_outqueue_add (client, data, data_size);
+ relay_client_outqueue_add (client, ptr_data, data_size,
+ raw_flags, raw_msg, raw_size);
}
else
{
#ifdef HAVE_GNUTLS
if (client->ssl)
- num_sent = gnutls_record_send (client->gnutls_sess, data, data_size);
+ num_sent = gnutls_record_send (client->gnutls_sess, ptr_data, data_size);
else
#endif
- num_sent = send (client->sock, data, data_size, 0);
+ num_sent = send (client->sock, ptr_data, data_size, 0);
if (num_sent >= 0)
{
+ for (i = 0; i < 2; i++)
+ {
+ if (raw_msg[i])
+ {
+ relay_raw_print (client,
+ raw_flags[i], raw_msg[i], raw_size[i]);
+ }
+ }
if (num_sent > 0)
{
client->bytes_sent += num_sent;
@@ -430,8 +746,9 @@ relay_client_send (struct t_relay_client *client, const char *data,
if (num_sent < data_size)
{
/* some data was not sent, add it to outqueue */
- relay_client_outqueue_add (client, data + num_sent,
- data_size - num_sent);
+ relay_client_outqueue_add (client, ptr_data + num_sent,
+ data_size - num_sent,
+ NULL, NULL, NULL);
}
}
else if (num_sent < 0)
@@ -443,7 +760,8 @@ relay_client_send (struct t_relay_client *client, const char *data,
|| (num_sent == GNUTLS_E_INTERRUPTED))
{
/* add message to queue (will be sent later) */
- relay_client_outqueue_add (client, data, data_size);
+ relay_client_outqueue_add (client, ptr_data, data_size,
+ raw_flags, raw_msg, raw_size);
}
else
{
@@ -466,7 +784,8 @@ relay_client_send (struct t_relay_client *client, const char *data,
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
{
/* add message to queue (will be sent later) */
- relay_client_outqueue_add (client, data, data_size);
+ relay_client_outqueue_add (client, ptr_data, data_size,
+ raw_flags, raw_msg, raw_size);
}
else
{
@@ -486,6 +805,9 @@ relay_client_send (struct t_relay_client *client, const char *data,
}
}
+ if (websocket_frame)
+ free (websocket_frame);
+
return num_sent;
}
@@ -497,7 +819,7 @@ int
relay_client_timer_cb (void *data, int remaining_calls)
{
struct t_relay_client *ptr_client;
- int num_sent;
+ int num_sent, i;
char *buf;
/* make C compiler happy */
@@ -527,6 +849,26 @@ relay_client_timer_cb (void *data, int remaining_calls)
}
if (num_sent >= 0)
{
+ for (i = 0; i < 2; i++)
+ {
+ if (ptr_client->outqueue->raw_message
+ && ptr_client->outqueue->raw_message[i])
+ {
+ /*
+ * print raw message and remove it from outqueue
+ * (so that it is displayed only one time, even if
+ * message is sent in many chunks)
+ */
+ relay_raw_print (ptr_client,
+ ptr_client->outqueue->raw_flags[i],
+ ptr_client->outqueue->raw_message[i],
+ ptr_client->outqueue->raw_size[i]);
+ ptr_client->outqueue->raw_flags[i] = 0;
+ free (ptr_client->outqueue->raw_message[i]);
+ ptr_client->outqueue->raw_message[i] = NULL;
+ ptr_client->outqueue->raw_size[i] = 0;
+ }
+ }
if (num_sent > 0)
{
ptr_client->bytes_sent += num_sent;
@@ -644,6 +986,8 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
#ifdef HAVE_GNUTLS
new_client->hook_timer_handshake = NULL;
#endif
+ new_client->websocket = 0;
+ new_client->http_headers = NULL;
new_client->address = strdup ((address) ? address : "?");
new_client->status = RELAY_STATUS_CONNECTED;
new_client->protocol = server->protocol;
@@ -656,6 +1000,22 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
new_client->last_activity = new_client->start_time;
new_client->bytes_recv = 0;
new_client->bytes_sent = 0;
+ switch (new_client->protocol)
+ {
+ case RELAY_PROTOCOL_WEECHAT:
+ new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT;
+ new_client->send_data_type = RELAY_CLIENT_DATA_BINARY;
+ break;
+ case RELAY_PROTOCOL_IRC:
+ new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT;
+ new_client->send_data_type = RELAY_CLIENT_DATA_TEXT;
+ break;
+ default:
+ new_client->recv_data_type = RELAY_CLIENT_DATA_TEXT;
+ new_client->send_data_type = RELAY_CLIENT_DATA_TEXT;
+ break;
+ }
+ new_client->partial_message = NULL;
relay_client_set_desc (new_client);
@@ -792,6 +1152,8 @@ relay_client_new_with_infolist (struct t_infolist *infolist)
new_client->gnutls_sess = NULL;
new_client->hook_timer_handshake = NULL;
#endif
+ new_client->websocket = weechat_infolist_integer (infolist, "websocket");
+ new_client->http_headers = NULL;
new_client->address = strdup (weechat_infolist_string (infolist, "address"));
new_client->status = weechat_infolist_integer (infolist, "status");
new_client->protocol = weechat_infolist_integer (infolist, "protocol");
@@ -816,6 +1178,10 @@ relay_client_new_with_infolist (struct t_infolist *infolist)
"%lu", &(new_client->bytes_recv));
sscanf (weechat_infolist_string (infolist, "bytes_sent"),
"%lu", &(new_client->bytes_sent));
+ new_client->recv_data_type = weechat_infolist_integer (infolist, "recv_data_type");
+ new_client->send_data_type = weechat_infolist_integer (infolist, "send_data_type");
+ str = weechat_infolist_string (infolist, "partial_message");
+ new_client->partial_message = (str) ? strdup (str) : NULL;
str = weechat_infolist_string (infolist, "desc");
if (str)
@@ -972,8 +1338,12 @@ relay_client_free (struct t_relay_client *client)
if (client->hook_timer_handshake)
weechat_unhook (client->hook_timer_handshake);
#endif
+ if (client->http_headers)
+ weechat_hashtable_free (client->http_headers);
if (client->hook_fd)
weechat_unhook (client->hook_fd);
+ if (client->partial_message)
+ free (client->partial_message);
if (client->protocol_data)
{
switch (client->protocol)
@@ -1078,6 +1448,8 @@ relay_client_add_to_infolist (struct t_infolist *infolist,
if (!weechat_infolist_new_var_pointer (ptr_item, "hook_timer_handshake", client->hook_timer_handshake))
return 0;
#endif
+ if (!weechat_infolist_new_var_integer (ptr_item, "websocket", client->websocket))
+ return 0;
if (!weechat_infolist_new_var_string (ptr_item, "address", client->address))
return 0;
if (!weechat_infolist_new_var_integer (ptr_item, "status", client->status))
@@ -1108,6 +1480,12 @@ relay_client_add_to_infolist (struct t_infolist *infolist,
snprintf (value, sizeof (value), "%lu", client->bytes_sent);
if (!weechat_infolist_new_var_string (ptr_item, "bytes_sent", value))
return 0;
+ if (!weechat_infolist_new_var_integer (ptr_item, "recv_data_type", client->recv_data_type))
+ return 0;
+ if (!weechat_infolist_new_var_integer (ptr_item, "send_data_type", client->send_data_type))
+ return 0;
+ if (!weechat_infolist_new_var_string (ptr_item, "partial_message", client->partial_message))
+ return 0;
switch (client->protocol)
{
@@ -1146,6 +1524,10 @@ relay_client_print_log ()
weechat_log_printf (" gnutls_sess . . . . . : 0x%lx", ptr_client->gnutls_sess);
weechat_log_printf (" hook_timer_handshake. : 0x%lx", ptr_client->hook_timer_handshake);
#endif
+ weechat_log_printf (" websocket . . . . . . : %d", ptr_client->websocket);
+ weechat_log_printf (" http_headers. . . . . : 0x%lx (hashtable: '%s')",
+ ptr_client->http_headers,
+ weechat_hashtable_get_string (ptr_client->http_headers, "keys_values"));
weechat_log_printf (" address . . . . . . . : '%s'", ptr_client->address);
weechat_log_printf (" status. . . . . . . . : %d (%s)",
ptr_client->status,
@@ -1162,6 +1544,13 @@ relay_client_print_log ()
weechat_log_printf (" last_activity . . . . : %ld", ptr_client->last_activity);
weechat_log_printf (" bytes_recv. . . . . . : %lu", ptr_client->bytes_recv);
weechat_log_printf (" bytes_sent. . . . . . : %lu", ptr_client->bytes_sent);
+ weechat_log_printf (" recv_data_type. . . . : %d (%s)",
+ ptr_client->recv_data_type,
+ relay_client_data_type_string[ptr_client->recv_data_type]);
+ weechat_log_printf (" send_data_type. . . . : %d (%s)",
+ ptr_client->send_data_type,
+ relay_client_data_type_string[ptr_client->send_data_type]);
+ weechat_log_printf (" partial_message . . . : '%s'", ptr_client->partial_message);
weechat_log_printf (" protocol_data . . . . : 0x%lx", ptr_client->protocol_data);
switch (ptr_client->protocol)
{
diff --git a/src/plugins/relay/relay-client.h b/src/plugins/relay/relay-client.h
index 9fde7eadb..27cd00fca 100644
--- a/src/plugins/relay/relay-client.h
+++ b/src/plugins/relay/relay-client.h
@@ -39,6 +39,16 @@ enum t_relay_status
RELAY_NUM_STATUS,
};
+/* type of data exchanged with client */
+
+enum t_relay_client_data_type
+{
+ RELAY_CLIENT_DATA_TEXT = 0, /* text messages */
+ RELAY_CLIENT_DATA_BINARY, /* binary messages */
+ /* number of data types */
+ RELAY_NUM_CLIENT_DATA_TYPES,
+};
+
/* macros for status */
#define RELAY_CLIENT_HAS_ENDED(client) \
@@ -51,6 +61,9 @@ struct t_relay_client_outqueue
{
char *data; /* data to send */
int data_size; /* number of bytes */
+ int raw_flags[2]; /* flags for raw messages */
+ char *raw_message[2]; /* msgs for raw buffer (can be NULL)*/
+ int raw_size[2]; /* size (in bytes) of raw messages */
struct t_relay_client_outqueue *next_outqueue; /* next msg in queue */
struct t_relay_client_outqueue *prev_outqueue; /* prev msg in queue */
};
@@ -67,6 +80,8 @@ struct t_relay_client
gnutls_session_t gnutls_sess; /* gnutls session (only if SSL used) */
struct t_hook *hook_timer_handshake; /* timer for doing gnutls handshake*/
#endif
+ int websocket; /* 0=not a ws, 1=init ws, 2=ws ready */
+ struct t_hashtable *http_headers; /* HTTP headers for websocket */
char *address; /* string with IP address */
enum t_relay_status status; /* status (connecting, active,..) */
enum t_relay_protocol protocol; /* protocol (irc,..) */
@@ -80,6 +95,9 @@ struct t_relay_client
time_t last_activity; /* time of last byte received/sent */
unsigned long bytes_recv; /* bytes received from client */
unsigned long bytes_sent; /* bytes sent to client */
+ enum t_relay_client_data_type recv_data_type; /* type recv from client */
+ enum t_relay_client_data_type send_data_type; /* type sent to client */
+ char *partial_message; /* partial text message received */
void *protocol_data; /* data depending on protocol used */
struct t_relay_client_outqueue *outqueue; /* queue for outgoing msgs */
struct t_relay_client_outqueue *last_outqueue; /* last outgoing msg */
@@ -97,7 +115,7 @@ extern struct t_relay_client *relay_client_search_by_number (int number);
extern struct t_relay_client *relay_client_search_by_id (int id);
extern int relay_client_recv_cb (void *arg_client, int fd);
extern int relay_client_send (struct t_relay_client *client, const char *data,
- int data_size);
+ int data_size, const char *message_raw_buffer);
extern int relay_client_timer_cb (void *data, int remaining_calls);
extern struct t_relay_client *relay_client_new (int sock, const char *address,
struct t_relay_server *server);
diff --git a/src/plugins/relay/relay-config.c b/src/plugins/relay/relay-config.c
index d38c22f5e..d6092d279 100644
--- a/src/plugins/relay/relay-config.c
+++ b/src/plugins/relay/relay-config.c
@@ -57,6 +57,7 @@ struct t_config_option *relay_config_network_ipv6;
struct t_config_option *relay_config_network_max_clients;
struct t_config_option *relay_config_network_password;
struct t_config_option *relay_config_network_ssl_cert_key;
+struct t_config_option *relay_config_network_websocket_allowed_origins;
/* relay config, irc section */
@@ -69,6 +70,7 @@ struct t_config_option *relay_config_irc_backlog_time_format;
/* other */
regex_t *relay_config_regex_allowed_ips = NULL;
+regex_t *relay_config_regex_websocket_allowed_origins = NULL;
struct t_hashtable *relay_config_hashtable_irc_backlog_tags = NULL;
@@ -188,6 +190,44 @@ relay_config_change_network_ssl_cert_key (void *data,
}
/*
+ * Callback for changes on option "relay.network.websocker_allowed_origins".
+ */
+
+void
+relay_config_change_network_websocket_allowed_origins (void *data,
+ struct t_config_option *option)
+{
+ const char *allowed_origins;
+
+ /* make C compiler happy */
+ (void) data;
+ (void) option;
+
+ if (relay_config_regex_websocket_allowed_origins)
+ {
+ regfree (relay_config_regex_websocket_allowed_origins);
+ free (relay_config_regex_websocket_allowed_origins);
+ relay_config_regex_websocket_allowed_origins = NULL;
+ }
+
+ allowed_origins = weechat_config_string (relay_config_network_websocket_allowed_origins);
+ if (allowed_origins && allowed_origins[0])
+ {
+ relay_config_regex_websocket_allowed_origins = malloc (sizeof (*relay_config_regex_websocket_allowed_origins));
+ if (relay_config_regex_websocket_allowed_origins)
+ {
+ if (weechat_string_regcomp (relay_config_regex_websocket_allowed_origins,
+ allowed_origins,
+ REG_EXTENDED | REG_ICASE) != 0)
+ {
+ free (relay_config_regex_websocket_allowed_origins);
+ relay_config_regex_websocket_allowed_origins = NULL;
+ }
+ }
+ }
+}
+
+/*
* Callback for changes on option "relay.irc.backlog_tags".
*/
@@ -606,6 +646,14 @@ relay_config_init ()
"with SSL)"),
NULL, 0, 0, "%h/ssl/relay.pem", NULL, 0, NULL, NULL,
&relay_config_change_network_ssl_cert_key, NULL, NULL, NULL);
+ relay_config_network_websocket_allowed_origins = weechat_config_new_option (
+ relay_config_file, ptr_section,
+ "websocket_allowed_origins", "string",
+ N_("regular expression with origins allowed in websockets (case "
+ "insensitive, use \"(?-i)\" at beginning to make it case sensitive), "
+ "example: \"^http://(www\\.)?example\\.(com|org)\""),
+ NULL, 0, 0, "", NULL, 0, NULL, NULL,
+ &relay_config_change_network_websocket_allowed_origins, NULL, NULL, NULL);
/* section irc */
ptr_section = weechat_config_new_section (relay_config_file, "irc",
diff --git a/src/plugins/relay/relay-config.h b/src/plugins/relay/relay-config.h
index a6340c3f6..b25510179 100644
--- a/src/plugins/relay/relay-config.h
+++ b/src/plugins/relay/relay-config.h
@@ -51,6 +51,7 @@ extern struct t_config_option *relay_config_irc_backlog_tags;
extern struct t_config_option *relay_config_irc_backlog_time_format;
extern regex_t *relay_config_regex_allowed_ips;
+extern regex_t *relay_config_regex_websocket_allowed_origins;
extern struct t_hashtable *relay_config_hashtable_irc_backlog_tags;
extern int relay_config_create_option_port (void *data,
diff --git a/src/plugins/relay/relay-raw.c b/src/plugins/relay/relay-raw.c
index 082e232e1..ef9a1e75f 100644
--- a/src/plugins/relay/relay-raw.c
+++ b/src/plugins/relay/relay-raw.c
@@ -215,46 +215,99 @@ relay_raw_message_add_to_list (time_t date, const char *prefix,
/*
* Adds a new raw message to list.
- *
- * Returns pointer to new raw message, NULL if error.
*/
-struct t_relay_raw_message *
+void
relay_raw_message_add (struct t_relay_client *client, int flags,
- const char *message)
+ const char *data, int data_size)
{
char *buf, *buf2, prefix[256], prefix_arrow[16];
+ char str_hexa[(16 * 3) + 1], str_ascii[(16 * 2) + 1], str_line[256];
const unsigned char *ptr_buf;
const char *hexa = "0123456789ABCDEF";
- int pos_buf, pos_buf2, char_size, i;
+ int pos_buf, pos_buf2, char_size, i, hexa_pos, ascii_pos;
struct t_relay_raw_message *new_raw_message;
- buf = weechat_iconv_to_internal (NULL, message);
- buf2 = malloc ((strlen (buf) * 3) + 1);
- if (buf2)
+ buf = NULL;
+ buf2 = NULL;
+
+ if (flags & RELAY_RAW_FLAG_BINARY)
{
- ptr_buf = (buf) ? (unsigned char *)buf : (unsigned char *)message;
- pos_buf = 0;
- pos_buf2 = 0;
- while (ptr_buf[pos_buf])
+ /* binary message */
+ buf = malloc ((data_size * 6) + 128 + 1);
+ if (buf)
{
- if (ptr_buf[pos_buf] < 32)
+ buf[0] = '\0';
+ hexa_pos = 0;
+ ascii_pos = 0;
+ for (i = 0; i < data_size; i++)
{
- buf2[pos_buf2++] = '\\';
- buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] / 16];
- buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] % 16];
- pos_buf++;
+ snprintf (str_hexa + hexa_pos, 4,
+ "%02X ", (unsigned char)(data[i]));
+ hexa_pos += 3;
+ snprintf (str_ascii + ascii_pos, 3, "%c ",
+ ((((unsigned char)data[i]) < 32)
+ || (((unsigned char)data[i]) > 127)) ?
+ '.' : (unsigned char)(data[i]));
+ ascii_pos += 2;
+ if (ascii_pos == 32)
+ {
+ if (buf[0])
+ strcat (buf, "\n");
+ snprintf (str_line, sizeof (str_line),
+ "%-48s %s", str_hexa, str_ascii);
+ strcat (buf, str_line);
+ hexa_pos = 0;
+ ascii_pos = 0;
+ }
}
- else
+ if (ascii_pos > 0)
{
- char_size = weechat_utf8_char_size ((const char *)(ptr_buf + pos_buf));
- for (i = 0; i < char_size; i++)
+ if (buf[0])
+ strcat (buf, "\n");
+ snprintf (str_line, sizeof (str_line),
+ "%-48s %s", str_hexa, str_ascii);
+ strcat (buf, str_line);
+ }
+ }
+ }
+ else
+ {
+ /* text message */
+ buf = weechat_iconv_to_internal (NULL, data);
+ buf2 = weechat_string_replace (buf, "\r", "");
+ if (buf2)
+ {
+ free (buf);
+ buf = buf2;
+ buf2 = NULL;
+ }
+ buf2 = malloc ((strlen (buf) * 3) + 1);
+ if (buf2)
+ {
+ ptr_buf = (buf) ? (unsigned char *)buf : (unsigned char *)data;
+ pos_buf = 0;
+ pos_buf2 = 0;
+ while (ptr_buf[pos_buf])
+ {
+ if ((ptr_buf[pos_buf] < 32) && (ptr_buf[pos_buf] != '\n'))
{
- buf2[pos_buf2++] = ptr_buf[pos_buf++];
+ buf2[pos_buf2++] = '\\';
+ buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] / 16];
+ buf2[pos_buf2++] = hexa[ptr_buf[pos_buf] % 16];
+ pos_buf++;
+ }
+ else
+ {
+ char_size = weechat_utf8_char_size ((const char *)(ptr_buf + pos_buf));
+ for (i = 0; i < char_size; i++)
+ {
+ buf2[pos_buf2++] = ptr_buf[pos_buf++];
+ }
}
}
+ buf2[pos_buf2] = '\0';
}
- buf2[pos_buf2] = '\0';
}
/* build prefix with arrow */
@@ -302,14 +355,20 @@ relay_raw_message_add (struct t_relay_client *client, int flags,
new_raw_message = relay_raw_message_add_to_list (time (NULL),
prefix,
- (buf2) ? buf2 : ((buf) ? buf : message));
+ (buf2) ? buf2 : ((buf) ? buf : data));
+
+ if (new_raw_message)
+ {
+ if (relay_raw_buffer)
+ relay_raw_message_print (new_raw_message);
+ if (weechat_config_integer (relay_config_look_raw_messages) == 0)
+ relay_raw_message_free (new_raw_message);
+ }
if (buf)
free (buf);
if (buf2)
free (buf2);
-
- return new_raw_message;
}
/*
@@ -318,28 +377,13 @@ relay_raw_message_add (struct t_relay_client *client, int flags,
void
relay_raw_print (struct t_relay_client *client, int flags,
- const char *format, ...)
+ const char *data, int data_size)
{
- struct t_relay_raw_message *new_raw_message;
-
- weechat_va_format (format);
- if (!vbuffer)
- return;
-
/* auto-open Relay raw buffer if debug for irc plugin is >= 1 */
if (!relay_raw_buffer && (weechat_relay_plugin->debug >= 1))
relay_raw_open (0);
- new_raw_message = relay_raw_message_add (client, flags, vbuffer);
- if (new_raw_message)
- {
- if (relay_raw_buffer)
- relay_raw_message_print (new_raw_message);
- if (weechat_config_integer (relay_config_look_raw_messages) == 0)
- relay_raw_message_free (new_raw_message);
- }
-
- free (vbuffer);
+ relay_raw_message_add (client, flags, data, data_size);
}
/*
diff --git a/src/plugins/relay/relay-raw.h b/src/plugins/relay/relay-raw.h
index f2b0261f9..4d6f8fae6 100644
--- a/src/plugins/relay/relay-raw.h
+++ b/src/plugins/relay/relay-raw.h
@@ -26,6 +26,7 @@
#define RELAY_RAW_FLAG_RECV 1
#define RELAY_RAW_FLAG_SEND 2
+#define RELAY_RAW_FLAG_BINARY 4
struct t_relay_raw_message
{
@@ -47,7 +48,7 @@ extern struct t_relay_raw_message *relay_raw_message_add_to_list (time_t date,
const char *prefix,
const char *message);
extern void relay_raw_print (struct t_relay_client *client, int flags,
- const char *format, ...);
+ const char *data, int data_size);
extern void relay_raw_message_free_all ();
extern int relay_raw_add_to_infolist (struct t_infolist *infolist,
struct t_relay_raw_message *raw_message);
diff --git a/src/plugins/relay/relay-server.c b/src/plugins/relay/relay-server.c
index baf54df0b..d71ef6316 100644
--- a/src/plugins/relay/relay-server.c
+++ b/src/plugins/relay/relay-server.c
@@ -286,7 +286,7 @@ relay_server_sock_cb (void *data, int fd)
if (relay_config_regex_allowed_ips
&& (regexec (relay_config_regex_allowed_ips, ptr_ip_address, 0, NULL, 0) != 0))
{
- if (weechat_relay_plugin->debug >= 2)
+ if (weechat_relay_plugin->debug >= 1)
{
weechat_printf (NULL,
_("%s%s: IP address \"%s\" not allowed for relay"),
diff --git a/src/plugins/relay/relay-websocket.c b/src/plugins/relay/relay-websocket.c
new file mode 100644
index 000000000..f0632db8c
--- /dev/null
+++ b/src/plugins/relay/relay-websocket.c
@@ -0,0 +1,379 @@
+/*
+ * relay-websocket.c - websocket server functions for relay plugin (RFC 6455)
+ *
+ * Copyright (C) 2013 Sebastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * WeeChat 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with WeeChat. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <gcrypt.h>
+
+#include "../weechat-plugin.h"
+#include "relay.h"
+#include "relay-client.h"
+#include "relay-config.h"
+
+
+/*
+ * globally unique identifier that is concatenated to HTTP header
+ * "Sec-WebSocket-Key"
+ */
+#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+
+/*
+ * Checks if a message is a HTTP GET with resource "/weechat".
+ *
+ * Returns:
+ * 1: message is a HTTP GET with resource "/weechat"
+ * 0: message is NOT a HTTP GET with resource "/weechat"
+ */
+
+int
+relay_websocket_is_http_get_weechat (const char *message)
+{
+ /* the message must start with "GET /weechat" */
+ if (strncmp (message, "GET /weechat", 12) != 0)
+ return 0;
+
+ /* after "GET /weechat", only a new line or " HTTP" is allowed */
+ if ((message[12] != '\r') && (message[12] != '\n')
+ && (strncmp (message + 12, " HTTP", 5) != 0))
+ {
+ return 0;
+ }
+
+ /* valid HTTP GET for resource "/weechat" */
+ return 1;
+}
+
+/*
+ * Saves a HTTP header in hashtable "http_header" of client.
+ */
+
+void
+relay_websocket_save_header (struct t_relay_client *client,
+ const char *message)
+{
+ char *pos, *name;
+ const char *ptr_value;
+
+ /* ignore the "GET" request */
+ if (strncmp (message, "GET ", 4) == 0)
+ return;
+
+ pos = strchr (message, ':');
+
+ /* not a valid header */
+ if (!pos || (pos == message))
+ return;
+
+ /* get header name */
+ name = weechat_strndup (message, pos - message);
+ if (!name)
+ return;
+
+ /* get pointer on header value */
+ ptr_value = pos + 1;
+ while (ptr_value[0] == ' ')
+ {
+ ptr_value++;
+ }
+
+ /* add header in the hashtable */
+ weechat_hashtable_set (client->http_headers, name, ptr_value);
+
+ free (name);
+}
+
+/*
+ * Checks if a client handshake is valid.
+ *
+ * A websocket query looks like:
+ * GET /weechat HTTP/1.1
+ * Upgrade: websocket
+ * Connection: Upgrade
+ * Host: myhost:5000
+ * Origin: http://example.org.org
+ * Pragma: no-cache
+ * Cache-Control: no-cache
+ * Sec-WebSocket-Key: fo1J9uHSsrfDP3BkwUylzQ==
+ * Sec-WebSocket-Version: 13
+ * Sec-WebSocket-Extensions: x-webkit-deflate-frame
+ * Cookie: csrftoken=acb65377798f32dc377ebb50316a12b5
+ *
+ * Expected HTTP headers with values are:
+ *
+ * header | value
+ * --------------------+----------------
+ * "Upgrade" | "websocket"
+ * "Sec-WebSocket-Key" | non-empty value
+ *
+ * If option relay.network.websocket_allowed_origins is set, the HTTP header
+ * "Origin" is checked against this regex. If header "Origin" is not set or does
+ * not match regex, the handshake is considered as invalid.
+ *
+ * Returns:
+ * 0: handshake is valid
+ * -1: handshake is invalid (headers missing or with bad value)
+ * -2: origin is not allowed (option relay.network.websocket_allowed_origins)
+ */
+
+int
+relay_websocket_client_handshake_valid (struct t_relay_client *client)
+{
+ const char *value;
+
+ /* check if we have header "Upgrade" with value "websocket" */
+ value = weechat_hashtable_get (client->http_headers, "Upgrade");
+ if (!value)
+ return -1;
+ if (strcmp (value, "websocket") != 0)
+ return -1;
+
+ /* check if we have header "Sec-WebSocket-Key" with non-empty value */
+ value = weechat_hashtable_get (client->http_headers, "Sec-WebSocket-Key");
+ if (!value || !value[0])
+ return -1;
+
+ if (relay_config_regex_websocket_allowed_origins)
+ {
+ value = weechat_hashtable_get (client->http_headers, "Origin");
+ if (!value || !value[0])
+ return -2;
+ if (regexec (relay_config_regex_websocket_allowed_origins, value, 0,
+ NULL, 0) != 0)
+ {
+ return -2;
+ }
+ }
+
+ /* client handshake is valid */
+ return 0;
+}
+
+/*
+ * Builds the handshake that will be returned to client, to initialize and use
+ * the websocket.
+ *
+ * Returns a string with content of handshake to send to client, it looks like:
+ * HTTP/1.1 101 Switching Protocols
+ * Upgrade: websocket
+ * Connection: Upgrade
+ * Sec-WebSocket-Accept: 73OzoF/IyV9znm7Tsb4EtlEEmn4=
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+relay_websocket_build_handshake (struct t_relay_client *client)
+{
+ const char *sec_websocket_key;
+ char *key, sec_websocket_accept[128], handshake[1024];
+ unsigned char *result;
+ gcry_md_hd_t hd;
+ int length;
+
+ sec_websocket_key = weechat_hashtable_get (client->http_headers,
+ "Sec-WebSocket-Key");
+ if (!sec_websocket_key || !sec_websocket_key[0])
+ return NULL;
+
+ length = strlen (sec_websocket_key) + strlen (WEBSOCKET_GUID) + 1;
+ key = malloc (length);
+ if (!key)
+ return NULL;
+
+ /*
+ * concatenate header "Sec-WebSocket-Key" with the GUID
+ * (globally unique identifier)
+ */
+ snprintf (key, length, "%s%s", sec_websocket_key, WEBSOCKET_GUID);
+
+ /* compute 160-bit SHA1 on the key and encode it with base64 */
+ gcry_md_open (&hd, GCRY_MD_SHA1, 0);
+ length = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
+ gcry_md_write (hd, key, strlen (key));
+ result = gcry_md_read (hd, GCRY_MD_SHA1);
+ weechat_string_encode_base64 ((char *)result, length, sec_websocket_accept);
+ gcry_md_close (hd);
+
+ /* build the handshake (it will be sent as-is to client) */
+ snprintf (handshake, sizeof (handshake),
+ "HTTP/1.1 101 Switching Protocols\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ //"Sec-WebSocket-Protocol: chat\r\n"
+ "Sec-WebSocket-Accept: %s\r\n"
+ "\r\n",
+ sec_websocket_accept);
+
+ return strdup (handshake);
+}
+
+/*
+ * Sends a HTTP message to client.
+ *
+ * Argument "http" is a HTTP code + message, for example:
+ * "403 Forbidden".
+ */
+
+void
+relay_websocket_send_http (struct t_relay_client *client,
+ const char *http)
+{
+ char *message;
+ int length;
+
+ length = 32 + strlen (http) + 1;
+ message = malloc (length);
+ if (message)
+ {
+ snprintf (message, length, "HTTP/1.1 %s\r\n\r\n", http);
+ relay_client_send (client, message, strlen (message), NULL);
+ free (message);
+ }
+}
+
+/*
+ * Decodes a websocket frame.
+ *
+ * Returns:
+ * 1: frame decoded successfully
+ * 0: error decoding frame (connection must be closed if it happens)
+ */
+
+int
+relay_websocket_decode_frame (const unsigned char *buffer,
+ unsigned long long length,
+ unsigned char *decoded)
+{
+ unsigned long long i, index, length_frame_size, length_frame;
+
+ if (length < 2)
+ return 0;
+
+ /*
+ * check if frame is masked: client MUST send a masked frame; if frame is
+ * not masked, we MUST reject it and close the connection (see RFC 6455)
+ */
+ if (!(buffer[1] & 128))
+ return 0;
+
+ /* decode frame */
+ index = 2;
+ length_frame_size = 1;
+ length_frame = buffer[1] & 127;
+ if ((length_frame == 126) || (length_frame == 127))
+ {
+ length_frame_size = (length_frame == 126) ? 2 : 8;
+ if (length < 1 + length_frame_size)
+ return 0;
+ length_frame = 0;
+ for (i = 0; i < length_frame_size; i++)
+ {
+ length_frame += (unsigned long long)buffer[index + i] << ((length_frame_size - i - 1) * 8);
+ }
+ index += length_frame_size;
+ }
+
+ if (length < 1 + length_frame_size + 4 + length_frame)
+ return 0;
+
+ /* read masks (4 bytes) */
+ int masks[4];
+ for (i = 0; i < 4; i++)
+ {
+ masks[i] = (int)((unsigned char)buffer[index + i]);
+ }
+ index += 4;
+
+ /* decode data using masks */
+ for (i = 0; i < length_frame; i++)
+ {
+ decoded[i] = (int)((unsigned char)buffer[index + i]) ^ masks[i % 4];
+ }
+ decoded[length_frame] = '\0';
+
+ return 1;
+}
+
+/*
+ * Encodes data in a websocket frame.
+ *
+ * Returns websocket frame, NULL if error.
+ * Argument "length_frame" is set with the length of frame built.
+ *
+ * Note: result must be freed after use.
+ */
+
+char *
+relay_websocket_encode_frame (struct t_relay_client *client,
+ const char *buffer,
+ unsigned long long length,
+ unsigned long long *length_frame)
+{
+ unsigned char *frame;
+ unsigned long long index;
+
+ *length_frame = 0;
+
+ frame = malloc (length + 10);
+ if (!frame)
+ return NULL;
+
+ frame[0] = (client->send_data_type == RELAY_CLIENT_DATA_TEXT) ? 0x81 : 0x82;
+
+ if (length <= 125)
+ {
+ /* length on one byte */
+ frame[1] = length;
+ index = 2;
+ }
+ else if ((length >= 126) && (length <= 65535))
+ {
+ /* length on 2 bytes */
+ frame[1] = 126;
+ frame[2] = (length >> 8) & 0xFF;
+ frame[3] = length & 0xFF;
+ index = 4;
+ }
+ else
+ {
+ /* length on 8 bytes */
+ frame[1] = 127;
+ frame[2] = (length >> 56) & 0xFF;
+ frame[3] = (length >> 48) & 0xFF;
+ frame[4] = (length >> 40) & 0xFF;
+ frame[5] = (length >> 32) & 0xFF;
+ frame[6] = (length >> 24) & 0xFF;
+ frame[7] = (length >> 16) & 0xFF;
+ frame[8] = (length >> 8) & 0xFF;
+ frame[9] = length & 0xFF;
+ index = 10;
+ }
+
+ /* copy buffer after length */
+ memcpy (frame + index, buffer, length);
+
+ *length_frame = index + length;
+
+ return (char *)frame;
+}
diff --git a/src/plugins/relay/relay-websocket.h b/src/plugins/relay/relay-websocket.h
new file mode 100644
index 000000000..8f813200d
--- /dev/null
+++ b/src/plugins/relay/relay-websocket.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 Sebastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * WeeChat 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with WeeChat. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __WEECHAT_RELAY_WEBSOCKET_H
+#define __WEECHAT_RELAY_WEBSOCKET_H 1
+
+extern int relay_websocket_is_http_get_weechat (const char *message);
+extern void relay_websocket_save_header (struct t_relay_client *client,
+ const char *message);
+extern int relay_websocket_client_handshake_valid (struct t_relay_client *client);
+extern char *relay_websocket_build_handshake (struct t_relay_client *client);
+extern void relay_websocket_send_http (struct t_relay_client *client,
+ const char *http);
+extern int relay_websocket_decode_frame (const unsigned char *buffer,
+ int length,
+ unsigned char *decoded);
+extern char *relay_websocket_encode_frame (struct t_relay_client *client,
+ const char *buffer,
+ unsigned long long length,
+ unsigned long long *length_frame);
+
+#endif /* __WEECHAT_RELAY_WEBSOCKET_H */
diff --git a/src/plugins/relay/weechat/relay-weechat-msg.c b/src/plugins/relay/weechat/relay-weechat-msg.c
index 7c9cd42cd..0430c746f 100644
--- a/src/plugins/relay/weechat/relay-weechat-msg.c
+++ b/src/plugins/relay/weechat/relay-weechat-msg.c
@@ -958,7 +958,7 @@ relay_weechat_msg_send (struct t_relay_client *client,
struct t_relay_weechat_msg *msg)
{
uint32_t size32;
- char compression;
+ char compression, raw_message[1024];
int rc;
Bytef *dest;
uLongf dest_size;
@@ -986,16 +986,17 @@ relay_weechat_msg_send (struct t_relay_client *client,
dest[4] = 1;
/* display message in raw buffer */
- relay_raw_print (client, RELAY_RAW_FLAG_SEND,
- "obj: %d/%d bytes (%d%%, %ldms), id: %s",
- (int)dest_size + 5,
- msg->data_size,
- 100 - ((((int)dest_size + 5) * 100) / msg->data_size),
- time_diff,
- msg->id);
+ snprintf (raw_message, sizeof (raw_message),
+ "obj: %d/%d bytes (%d%%, %ldms), id: %s",
+ (int)dest_size + 5,
+ msg->data_size,
+ 100 - ((((int)dest_size + 5) * 100) / msg->data_size),
+ time_diff,
+ msg->id);
/* send compressed data */
- relay_client_send (client, (const char *)dest, dest_size + 5);
+ relay_client_send (client, (const char *)dest, dest_size + 5,
+ raw_message);
free (dest);
return;
@@ -1012,12 +1013,10 @@ relay_weechat_msg_send (struct t_relay_client *client,
compression = 0;
relay_weechat_msg_set_bytes (msg, 4, &compression, 1);
- /* display message in raw buffer */
- relay_raw_print (client, RELAY_RAW_FLAG_SEND,
- "obj: %d bytes", msg->data_size);
-
/* send uncompressed data */
- relay_client_send (client, msg->data, msg->data_size);
+ snprintf (raw_message, sizeof (raw_message),
+ "obj: %d bytes", msg->data_size);
+ relay_client_send (client, msg->data, msg->data_size, raw_message);
}
/*
diff --git a/src/plugins/relay/weechat/relay-weechat-protocol.c b/src/plugins/relay/weechat/relay-weechat-protocol.c
index 489e972bf..9313d4bdd 100644
--- a/src/plugins/relay/weechat/relay-weechat-protocol.c
+++ b/src/plugins/relay/weechat/relay-weechat-protocol.c
@@ -1053,7 +1053,7 @@ RELAY_WEECHAT_PROTOCOL_CALLBACK(quit)
*/
void
-relay_weechat_protocol_recv (struct t_relay_client *client, char *data)
+relay_weechat_protocol_recv (struct t_relay_client *client, const char *data)
{
char *pos, *id, *command, **argv, **argv_eol;
int i, argc, return_code;
@@ -1074,11 +1074,6 @@ relay_weechat_protocol_recv (struct t_relay_client *client, char *data)
if (!data || !data[0] || RELAY_CLIENT_HAS_ENDED(client))
return;
- /* remove \r at the end of message */
- pos = strchr (data, '\r');
- if (pos)
- pos[0] = '\0';
-
/* display debug message */
if (weechat_relay_plugin->debug >= 2)
{
@@ -1090,9 +1085,6 @@ relay_weechat_protocol_recv (struct t_relay_client *client, char *data)
data);
}
- /* display message in raw buffer */
- relay_raw_print (client, RELAY_RAW_FLAG_RECV, "cmd: %s", data);
-
/* extract id */
id = NULL;
if (data[0] == '(')
diff --git a/src/plugins/relay/weechat/relay-weechat-protocol.h b/src/plugins/relay/weechat/relay-weechat-protocol.h
index c4e66d8e3..0324da968 100644
--- a/src/plugins/relay/weechat/relay-weechat-protocol.h
+++ b/src/plugins/relay/weechat/relay-weechat-protocol.h
@@ -96,6 +96,6 @@ extern int relay_weechat_protocol_signal_upgrade_cb (void *data,
extern int relay_weechat_protocol_timer_nicklist_cb (void *data,
int remaining_calls);
extern void relay_weechat_protocol_recv (struct t_relay_client *client,
- char *data);
+ const char *data);
#endif /* __WEECHAT_RELAY_WEECHAT_PROTOCOL_H */
diff --git a/src/plugins/relay/weechat/relay-weechat.c b/src/plugins/relay/weechat/relay-weechat.c
index dfb62f06b..8b8dc2636 100644
--- a/src/plugins/relay/weechat/relay-weechat.c
+++ b/src/plugins/relay/weechat/relay-weechat.c
@@ -42,8 +42,6 @@
char *relay_weechat_compression_string[] = /* strings for compressions */
{ "off", "gzip" };
-char *relay_weechat_partial_message = NULL;
-
/*
* Searches for a compression.
@@ -132,48 +130,7 @@ relay_weechat_hook_timer_nicklist (struct t_relay_client *client)
void
relay_weechat_recv (struct t_relay_client *client, const char *data)
{
- char *new_partial, *pos, *tmp, **commands;
- int num_commands, i;
-
- if (relay_weechat_partial_message)
- {
- new_partial = realloc (relay_weechat_partial_message,
- strlen (relay_weechat_partial_message) +
- strlen (data) + 1);
- if (!new_partial)
- return;
- relay_weechat_partial_message = new_partial;
- strcat (relay_weechat_partial_message, data);
- }
- else
- relay_weechat_partial_message = strdup (data);
-
- pos = strrchr (relay_weechat_partial_message, '\n');
- if (pos)
- {
- pos[0] = '\0';
- commands = weechat_string_split (relay_weechat_partial_message, "\n",
- 0, 0, &num_commands);
- if (commands)
- {
- for (i = 0; i < num_commands; i++)
- {
- relay_weechat_protocol_recv (client, commands[i]);
- }
- weechat_string_free_split (commands);
- }
- if (pos[1])
- {
- tmp = strdup (pos + 1);
- free (relay_weechat_partial_message);
- relay_weechat_partial_message = tmp;
- }
- else
- {
- free (relay_weechat_partial_message);
- relay_weechat_partial_message = NULL;
- }
- }
+ relay_weechat_protocol_recv (client, data);
}
/*