From 4202818be94c537e1f17b76a7c614ac5b260b295 Mon Sep 17 00:00:00 2001 From: Samuel Brian Date: Mon, 7 Nov 2016 15:43:31 +1000 Subject: [PATCH] C websockify: support for binary websocket protocol with HyBi/RFC 6455. The server prefers binary over base64 encoding, given a choice. This is required as noVNC no longer supports base64 encoding. --- other/websocket.c | 68 +++++++++++++++++++++++++++++++++------------- other/websocket.h | 4 +++ other/websockify.c | 4 +-- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/other/websocket.c b/other/websocket.c index cbd9cb8..0911371 100644 --- a/other/websocket.c +++ b/other/websocket.c @@ -335,24 +335,32 @@ int decode_hixie(char *src, size_t srclength, int encode_hybi(u_char const *src, size_t srclength, char *target, size_t targsize, unsigned int opcode) { - unsigned long long b64_sz, len_offset = 1, payload_offset = 2; + unsigned long long payload_offset = 2; int len = 0; - - if ((int)srclength <= 0) - { - return 0; - } - b64_sz = ((srclength - 1) / 3) * 4 + 4; + if (opcode != OPCODE_TEXT && opcode != OPCODE_BINARY) { + handler_emsg("Invalid opcode. Opcode must be 0x01 for text mode, or 0x02 for binary mode.\n"); + return -1; + } target[0] = (char)((opcode & 0x0F) | 0x80); - if (b64_sz <= 125) { - target[1] = (char) b64_sz; + if ((int)srclength <= 0) { + return 0; + } + + if (opcode & OPCODE_TEXT) { + len = ((srclength - 1) / 3) * 4 + 4; + } else { + len = srclength; + } + + if (len <= 125) { + target[1] = (char) len; payload_offset = 2; - } else if ((b64_sz > 125) && (b64_sz < 65536)) { + } else if ((len > 125) && (len < 65536)) { target[1] = (char) 126; - *(u_short*)&(target[2]) = htons(b64_sz); + *(u_short*)&(target[2]) = htons(len); payload_offset = 4; } else { handler_emsg("Sending frames larger than 65535 bytes not supported\n"); @@ -362,8 +370,13 @@ int encode_hybi(u_char const *src, size_t srclength, //payload_offset = 10; } - len = ws_b64_ntop(src, srclength, target+payload_offset, targsize-payload_offset); - + if (opcode & OPCODE_TEXT) { + len = ws_b64_ntop(src, srclength, target+payload_offset, targsize-payload_offset); + } else { + memcpy(target+payload_offset, src, srclength); + len = srclength; + } + if (len < 0) { return len; } @@ -433,7 +446,7 @@ int decode_hybi(unsigned char *src, size_t srclength, //printf(" payload_length: %u, raw remaining: %u\n", payload_length, remaining); payload = frame + hdr_length + 4*masked; - if (*opcode != 1 && *opcode != 2) { + if (*opcode != OPCODE_TEXT && *opcode != OPCODE_BINARY) { handler_msg("Ignoring non-data frame, opcode 0x%x\n", *opcode); continue; } @@ -458,8 +471,13 @@ int decode_hybi(unsigned char *src, size_t srclength, payload[i] ^= mask[i%4]; } - // base64 decode the data - len = ws_b64_pton((const char*)payload, target+target_offset, targsize); + if (*opcode & OPCODE_TEXT) { + // base64 decode the data + len = ws_b64_pton((const char*)payload, target+target_offset, targsize); + } else { + memcpy(target+target_offset, payload, payload_length); + len = payload_length; + } // Restore the first character of the next frame payload[payload_length] = save_char; @@ -640,6 +658,7 @@ ws_ctx_t *do_handshake(int sock) { headers_t *headers; int len, ret, i, offset; ws_ctx_t * ws_ctx; + char *response_protocol; // Peek, but don't read the data len = recv(sock, handshake, 1024, MSG_PEEK); @@ -709,10 +728,21 @@ ws_ctx_t *do_handshake(int sock) { } headers = ws_ctx->headers; + + response_protocol = strtok(headers->protocols, ","); + if (!response_protocol || !strlen(response_protocol)) { + ws_ctx->opcode = OPCODE_BINARY; + response_protocol = "null"; + } else if (!strcmp(response_protocol, "base64")) { + ws_ctx->opcode = OPCODE_TEXT; + } else { + ws_ctx->opcode = OPCODE_BINARY; + } + if (ws_ctx->hybi > 0) { handler_msg("using protocol HyBi/IETF 6455 %d\n", ws_ctx->hybi); gen_sha1(headers, sha1); - sprintf(response, SERVER_HANDSHAKE_HYBI, sha1, "base64"); + snprintf(response, sizeof(response), SERVER_HANDSHAKE_HYBI, sha1, response_protocol); } else { if (ws_ctx->hixie == 76) { handler_msg("using protocol Hixie 76\n"); @@ -723,8 +753,8 @@ ws_ctx_t *do_handshake(int sock) { trailer[0] = '\0'; pre = ""; } - sprintf(response, SERVER_HANDSHAKE_HIXIE, pre, headers->origin, pre, scheme, - headers->host, headers->path, pre, "base64", trailer); + snprintf(response, sizeof(response), SERVER_HANDSHAKE_HIXIE, pre, headers->origin, + pre, scheme, headers->host, headers->path, pre, "base64", trailer); } //handler_msg("response: %s\n", response); diff --git a/other/websocket.h b/other/websocket.h index fecfcf8..7bbd6fb 100644 --- a/other/websocket.h +++ b/other/websocket.h @@ -26,6 +26,9 @@ Sec-WebSocket-Protocol: %s\r\n\ #define POLICY_RESPONSE "\n" +#define OPCODE_TEXT 0x01 +#define OPCODE_BINARY 0x02 + typedef struct { char path[1024+1]; char host[1024+1]; @@ -44,6 +47,7 @@ typedef struct { SSL *ssl; int hixie; int hybi; + int opcode; headers_t *headers; char *cin_buf; char *cout_buf; diff --git a/other/websockify.c b/other/websockify.c index 2e05bf8..e69abe2 100644 --- a/other/websockify.c +++ b/other/websockify.c @@ -158,7 +158,7 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { cout_start = 0; if (ws_ctx->hybi) { cout_end = encode_hybi(ws_ctx->cin_buf, bytes, - ws_ctx->cout_buf, BUFSIZE, 1); + ws_ctx->cout_buf, BUFSIZE, ws_ctx->opcode); } else { cout_end = encode_hixie(ws_ctx->cin_buf, bytes, ws_ctx->cout_buf, BUFSIZE); @@ -205,7 +205,7 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { } if (opcode == 8) { - handler_emsg("client sent orderly close frame\n"); + handler_msg("client sent orderly close frame\n"); break; }