diff options
author | Sebastien Helleu <flashcode@flashtux.org> | 2013-02-10 20:22:13 +0100 |
---|---|---|
committer | Sebastien Helleu <flashcode@flashtux.org> | 2013-02-10 20:22:13 +0100 |
commit | c2aeb69c46a8f0222aed935afc46e0b27cbc94a0 (patch) | |
tree | d217e5bfc2dbfe7e0d50dbcd7a0b24b032681441 /src | |
parent | eb11921f1633db940df4b0c02a43df1360d39b96 (diff) | |
download | weechat-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.txt | 3 | ||||
-rw-r--r-- | src/plugins/relay/Makefile.am | 4 | ||||
-rw-r--r-- | src/plugins/relay/irc/relay-irc.c | 36 | ||||
-rw-r--r-- | src/plugins/relay/irc/relay-irc.h | 2 | ||||
-rw-r--r-- | src/plugins/relay/relay-client.c | 435 | ||||
-rw-r--r-- | src/plugins/relay/relay-client.h | 20 | ||||
-rw-r--r-- | src/plugins/relay/relay-config.c | 48 | ||||
-rw-r--r-- | src/plugins/relay/relay-config.h | 1 | ||||
-rw-r--r-- | src/plugins/relay/relay-raw.c | 128 | ||||
-rw-r--r-- | src/plugins/relay/relay-raw.h | 3 | ||||
-rw-r--r-- | src/plugins/relay/relay-server.c | 2 | ||||
-rw-r--r-- | src/plugins/relay/relay-websocket.c | 379 | ||||
-rw-r--r-- | src/plugins/relay/relay-websocket.h | 38 | ||||
-rw-r--r-- | src/plugins/relay/weechat/relay-weechat-msg.c | 27 | ||||
-rw-r--r-- | src/plugins/relay/weechat/relay-weechat-protocol.c | 10 | ||||
-rw-r--r-- | src/plugins/relay/weechat/relay-weechat-protocol.h | 2 | ||||
-rw-r--r-- | src/plugins/relay/weechat/relay-weechat.c | 45 |
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); } /* |