/* * SPDX-FileCopyrightText: 2013-2026 Sébastien Helleu * * SPDX-License-Identifier: GPL-3.0-or-later * * 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 . */ /* Websocket server functions for relay plugin (RFC 6455) */ #include #include #include #include #include #include #include "../weechat-plugin.h" #include "relay.h" #include "relay-client.h" #include "relay-config.h" #include "relay-http.h" #include "relay-websocket.h" /* * Allocate a t_relay_websocket_deflate structure. */ struct t_relay_websocket_deflate * relay_websocket_deflate_alloc (void) { struct t_relay_websocket_deflate *new_ws_deflate; new_ws_deflate = (struct t_relay_websocket_deflate *)malloc (sizeof (*new_ws_deflate)); if (!new_ws_deflate) return NULL; new_ws_deflate->enabled = 0; new_ws_deflate->server_context_takeover = 0; new_ws_deflate->client_context_takeover = 0; new_ws_deflate->window_bits_deflate = 0; new_ws_deflate->window_bits_inflate = 0; new_ws_deflate->server_max_window_bits_recv = 0; new_ws_deflate->client_max_window_bits_recv = 0; new_ws_deflate->strm_deflate = NULL; new_ws_deflate->strm_inflate = NULL; return new_ws_deflate; } /* * Initialize stream for deflate (compression). * * Return: * 1: OK * 0: error */ int relay_websocket_deflate_init_stream_deflate (struct t_relay_websocket_deflate *ws_deflate) { int rc, compression, compression_level; compression = weechat_config_integer (relay_config_network_compression); /* convert % to zlib compression level (1-9) */ compression_level = (((compression - 1) * 9) / 100) + 1; rc = deflateInit2 ( ws_deflate->strm_deflate, compression_level, Z_DEFLATED, /* method */ -1 * ws_deflate->window_bits_deflate, 8, /* memLevel */ Z_DEFAULT_STRATEGY); /* strategy */ return (rc == Z_OK) ? 1 : 0; } /* * Free a deflate stream in a deflate structure. */ void relay_websocket_deflate_free_stream_deflate (struct t_relay_websocket_deflate *ws_deflate) { if (ws_deflate->strm_deflate) { deflateEnd (ws_deflate->strm_deflate); free (ws_deflate->strm_deflate); ws_deflate->strm_deflate = NULL; } } /* * Initialize stream for inflate (decompression). * * Return: * 1: OK * 0: error */ int relay_websocket_deflate_init_stream_inflate (struct t_relay_websocket_deflate *ws_deflate) { int rc; rc = inflateInit2 (ws_deflate->strm_inflate, -1 * ws_deflate->window_bits_inflate); return (rc == Z_OK) ? 1 : 0; } /* * Free an inflate stream in a deflate structure. */ void relay_websocket_deflate_free_stream_inflate (struct t_relay_websocket_deflate *ws_deflate) { if (ws_deflate->strm_inflate) { inflateEnd (ws_deflate->strm_inflate); free (ws_deflate->strm_inflate); ws_deflate->strm_inflate = NULL; } } /* * Reinitialize a t_relay_websocket_deflate structure. */ void relay_websocket_deflate_reinit (struct t_relay_websocket_deflate *ws_deflate) { ws_deflate->enabled = 0; ws_deflate->server_context_takeover = 0; ws_deflate->client_context_takeover = 0; ws_deflate->window_bits_deflate = 0; ws_deflate->window_bits_inflate = 0; ws_deflate->server_max_window_bits_recv = 0; ws_deflate->client_max_window_bits_recv = 0; relay_websocket_deflate_free_stream_deflate (ws_deflate); relay_websocket_deflate_free_stream_inflate (ws_deflate); } /* * Free a websocket deflate structure. */ void relay_websocket_deflate_free (struct t_relay_websocket_deflate *ws_deflate) { if (ws_deflate) { relay_websocket_deflate_free_stream_deflate (ws_deflate); relay_websocket_deflate_free_stream_inflate (ws_deflate); free (ws_deflate); } } /* * Check if a message is a HTTP GET with resource "/weechat" (for weechat * protocol) or "/api" (for api protocol). * * Return: * 1: message is a HTTP GET with appropriate resource * 0: message is NOT a HTTP GET with appropriate resource */ int relay_websocket_is_valid_http_get (enum t_relay_protocol protocol, const char *message) { char string[128]; int length; if (!message) return 0; /* the message must start with "GET /weechat" or "GET /api" */ snprintf (string, sizeof (string), "GET /%s", relay_protocol_string[protocol]); length = strlen (string); if (strncmp (message, string, length) != 0) return 0; /* after "GET /weechat" or "GET /api", only a new line or " HTTP" is allowed */ if ((message[length] != '\r') && (message[length] != '\n') && (strncmp (message + length, " HTTP", 5) != 0)) { return 0; } /* valid HTTP GET */ return 1; } /* * Check if a client handshake is valid. * * A websocket query looks like (weechat protocol): * GET /weechat HTTP/1.1 * Upgrade: websocket * Connection: Upgrade * Host: myhost:5000 * Origin: https://example.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 * * A websocket query looks like (api protocol): * GET /api HTTP/1.1 * Upgrade: websocket * Connection: Upgrade * Host: myhost:5000 * Origin: https://example.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. * * Return: * 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_http_request *request) { const char *value; if (!request || !request->headers) return -1; /* check if we have header "Upgrade" with value "websocket" */ value = weechat_hashtable_get (request->headers, "upgrade"); if (!value) return -1; if (weechat_strcasecmp (value, "websocket") != 0) return -1; /* check if we have header "Sec-WebSocket-Key" with non-empty value */ value = weechat_hashtable_get (request->headers, "sec-websocket-key"); if (!value || !value[0]) return -1; if (relay_config_regex_websocket_allowed_origins) { value = weechat_hashtable_get (request->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; } /* * Parse websocket extensions (header "Sec-WebSocket-Extensions"). * * Header is for example: * Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits */ void relay_websocket_parse_extensions (const char *extensions, struct t_relay_websocket_deflate *ws_deflate, int ws_deflate_allowed) { char **exts, **params, **items; int i, j, number, num_exts, num_params, num_items; if (!extensions || !ws_deflate) return; exts = weechat_string_split (extensions, ",", " ", 0, 0, &num_exts); if (!exts) return; for (i = 0; i < num_exts; i++) { params = weechat_string_split (exts[i], ";", " ", 0, 0, &num_params); if (params && (num_params >= 1) && (strcmp (params[0], "permessage-deflate") == 0) && ws_deflate_allowed && (weechat_config_boolean (relay_config_network_websocket_permessage_deflate))) { ws_deflate->enabled = 1; ws_deflate->server_context_takeover = 1; ws_deflate->client_context_takeover = 1; ws_deflate->window_bits_deflate = 15; ws_deflate->window_bits_inflate = 15; ws_deflate->server_max_window_bits_recv = 0; ws_deflate->client_max_window_bits_recv = 0; for (j = 1; j < num_params; j++) { items = weechat_string_split (params[j], "=", " ", 0, 0, &num_items); if (items && (num_items >= 1)) { if (strcmp (items[0], "server_no_context_takeover") == 0) { ws_deflate->server_context_takeover = 0; } else if (strcmp (items[0], "client_no_context_takeover") == 0) { ws_deflate->client_context_takeover = 0; } else if ((strcmp (items[0], "server_max_window_bits") == 0) || (strcmp (items[0], "client_max_window_bits") == 0)) { number = 15; if (num_items >= 2) { if (weechat_util_parse_int (items[1], 10, &number)) { if (number < 8) number = 8; else if (number > 15) number = 15; } else { number = 15; } } if (strcmp (items[0], "server_max_window_bits") == 0) { ws_deflate->server_max_window_bits_recv = 1; ws_deflate->window_bits_deflate = number; } else { ws_deflate->client_max_window_bits_recv = 1; ws_deflate->window_bits_inflate = number; } } } weechat_string_free_split (items); } } weechat_string_free_split (params); } weechat_string_free_split (exts); } /* * Build the handshake that will be returned to client, to initialize and use * the websocket. * * Return 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_http_request *request) { const char *sec_websocket_key, *sec_websocket_protocol_request; char *key, sec_websocket_accept[128], handshake[4096], hash[160 / 8]; char **extensions, **protocol_array, str_window_bits[128]; char sec_websocket_extensions[1024]; char sec_websocket_protocol[1024]; int i, hash_size, protocol_count; if (!request) return NULL; sec_websocket_key = weechat_hashtable_get (request->headers, "sec-websocket-key"); if (!sec_websocket_key || !sec_websocket_key[0]) return NULL; /* * concatenate header "Sec-WebSocket-Key" with the GUID * (globally unique identifier) */ if (weechat_asprintf (&key, "%s%s", sec_websocket_key, WEBSOCKET_GUID) < 0) return NULL; /* compute 160-bit SHA1 on the key and encode it with base64 */ if (!weechat_crypto_hash (key, strlen (key), "sha1", hash, &hash_size)) { free (key); return NULL; } if (weechat_string_base_encode ("64", hash, hash_size, sec_websocket_accept) < 0) { sec_websocket_accept[0] = '\0'; } free (key); if (request->ws_deflate->enabled) { extensions = weechat_string_dyn_alloc (128); if (!extensions) return NULL; weechat_string_dyn_concat (extensions, "permessage-deflate", -1); if (!request->ws_deflate->server_context_takeover) { weechat_string_dyn_concat (extensions, "; ", -1); weechat_string_dyn_concat (extensions, "server_no_context_takeover", -1); } if (!request->ws_deflate->client_context_takeover) { weechat_string_dyn_concat (extensions, "; ", -1); weechat_string_dyn_concat (extensions, "client_no_context_takeover", -1); } if (request->ws_deflate->server_max_window_bits_recv) { weechat_string_dyn_concat (extensions, "; ", -1); snprintf (str_window_bits, sizeof (str_window_bits), "server_max_window_bits=%d", request->ws_deflate->window_bits_deflate); weechat_string_dyn_concat (extensions, str_window_bits, -1); } if (request->ws_deflate->client_max_window_bits_recv) { weechat_string_dyn_concat (extensions, "; ", -1); snprintf (str_window_bits, sizeof (str_window_bits), "client_max_window_bits=%d", request->ws_deflate->window_bits_inflate); weechat_string_dyn_concat (extensions, str_window_bits, -1); } snprintf ( sec_websocket_extensions, sizeof (sec_websocket_extensions), "Sec-WebSocket-Extensions: %s\r\n", *extensions); weechat_string_dyn_free (extensions, 1); } else { sec_websocket_extensions[0] = '\0'; } sec_websocket_protocol_request = weechat_hashtable_get ( request->headers, "sec-websocket-protocol"); protocol_array = weechat_string_split (sec_websocket_protocol_request, ",", " ", 0, 0, &protocol_count); sec_websocket_protocol[0] = '\0'; for (i = 0; i < protocol_count; i++) { if (strcmp (protocol_array[i], WEBSOCKET_SUB_PROTOCOL_API_WEECHAT) == 0) { snprintf ( sec_websocket_protocol, sizeof (sec_websocket_protocol), "Sec-WebSocket-Protocol: %s\r\n", WEBSOCKET_SUB_PROTOCOL_API_WEECHAT); break; } } /* 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-Accept: %s\r\n" "%s" "%s" "\r\n", sec_websocket_accept, sec_websocket_extensions, sec_websocket_protocol); return strdup (handshake); } /* * Decompress a decoded and compressed websocket frame compressed with * "deflate" (when websocket extension "permessage-deflate" is enabled). * * A final '\0' is added after the decompressed data (the size_decompressed * does not count this final '\0'). * * Return pointer to decompressed data, NULL if error. */ char * relay_websocket_inflate (const void *data, size_t size, z_stream *strm, size_t *size_decompressed) { int rc; unsigned char append_bytes[4] = { 0x00, 0x00, 0xFF, 0xFF }; Bytef *data2, *dest, *dest2; uLongf size2, dest_size, new_size; if (!data || (size == 0) || !strm || !size_decompressed) return NULL; dest = NULL; *size_decompressed = 0; /* append "0x00 0x00 0xFF 0xFF" to data */ size2 = size + sizeof (append_bytes); data2 = malloc (size2); if (!data2) goto error; memcpy (data2, data, size); memcpy (data2 + size, append_bytes, sizeof (append_bytes)); /* * estimate the decompressed size, by default 10 * size, capped to the * maximum allowed size (also prevents an integer overflow on the * multiplication) */ dest_size = (size2 > WEBSOCKET_INFLATE_MAX_SIZE / 10) ? WEBSOCKET_INFLATE_MAX_SIZE : 10 * size2; dest = malloc (dest_size); if (!dest) goto error; strm->avail_in = (uInt)size2; strm->next_in = (Bytef *)data2; strm->total_in = 0; strm->avail_out = (uInt)dest_size; strm->next_out = (Bytef *)dest; strm->total_out = 0; /* loop until we manage to decompress whole data in dest */ while (1) { rc = inflate (strm, Z_SYNC_FLUSH); if (((rc == Z_STREAM_END) || (rc == Z_OK)) && (strm->avail_in == 0)) { /* data successfully decompressed */ *size_decompressed = strm->total_out; break; } if ((rc == Z_BUF_ERROR) || (((rc == Z_STREAM_END) || (rc == Z_OK)) && (strm->avail_in > 0))) { /* output buffer is not large enough */ if (dest_size >= WEBSOCKET_INFLATE_MAX_SIZE) { /* * decompressed data is too large: reject the frame * (protection against a "deflate bomb") */ goto error; } /* double the buffer, capped to the maximum allowed size */ new_size = (dest_size > WEBSOCKET_INFLATE_MAX_SIZE / 2) ? WEBSOCKET_INFLATE_MAX_SIZE : dest_size * 2; strm->avail_out += (uInt)(new_size - dest_size); dest_size = new_size; dest2 = realloc (dest, dest_size); if (!dest2) goto error; dest = dest2; strm->next_out = dest + strm->total_out; } else { /* any other error is fatal */ goto error; } } dest2 = realloc (dest, *size_decompressed + 1); if (!dest2) goto error; dest = dest2; dest[*size_decompressed] = '\0'; free (data2); return (char *)dest; error: free (data2); free (dest); return NULL; } /* * Decode a websocket frame and return a list of frames in "*frames" (each * frame is first decompressed if "permessage-deflate" websocket extension * is used). * * Return an error if argument "expect_masked_frame" is 1 and a frame is not * masked. * * Return: * 1: frame(s) 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 buffer_length, int expect_masked_frame, struct t_relay_websocket_deflate *ws_deflate, struct t_relay_websocket_frame **frames, int *num_frames, char **partial_ws_frame, int *partial_ws_frame_size) { unsigned long long i, index_buffer, index_buffer_start_frame; unsigned long long length_frame_size, length_frame; unsigned char opcode; size_t size_decompressed; char *payload_decompressed; struct t_relay_websocket_frame *frames2, *ptr_frame; int size, masked_frame, mask[4]; if (!buffer || !frames || !num_frames) return 0; *frames = NULL; *num_frames = 0; index_buffer = 0; index_buffer_start_frame = 0; /* loop to decode all frames in message */ while (index_buffer < buffer_length) { index_buffer_start_frame = index_buffer; if (index_buffer + 1 >= buffer_length) goto missing_data; opcode = buffer[index_buffer] & 15; /* check if frame is masked */ masked_frame = (buffer[index_buffer + 1] & 128) ? 1 : 0; /* * error if the frame is not masked and we expect it to be masked, * in this case we must reject it and close the connection * (see RFC 6455) */ if (!masked_frame && expect_masked_frame) return 0; /* decode frame length */ length_frame = buffer[index_buffer + 1] & 127; index_buffer += 2; if (index_buffer >= buffer_length) goto missing_data; if ((length_frame == 126) || (length_frame == 127)) { length_frame_size = (length_frame == 126) ? 2 : 8; if (index_buffer + length_frame_size > buffer_length) goto missing_data; length_frame = 0; for (i = 0; i < length_frame_size; i++) { length_frame += (unsigned long long)buffer[index_buffer + i] << ((length_frame_size - i - 1) * 8); } index_buffer += length_frame_size; } /* * reject the frame if its announced length is too big: this prevents * a client from forcing an unbounded allocation (and unbounded * accumulation of partial frames) by announcing a huge frame */ if (length_frame > WEBSOCKET_FRAME_MAX_LENGTH) return 0; if (masked_frame) { /* read mask (4 bytes) */ if (index_buffer + 4 > buffer_length) goto missing_data; for (i = 0; i < 4; i++) { mask[i] = (int)((unsigned char)buffer[index_buffer + i]); } index_buffer += 4; } /* check if we have enough data */ if ((length_frame > buffer_length) || (index_buffer + length_frame > buffer_length)) { goto missing_data; } /* add a new frame in array */ (*num_frames)++; frames2 = realloc (*frames, sizeof (**frames) * (*num_frames)); if (!frames2) return 0; *frames = frames2; ptr_frame = &((*frames)[*num_frames - 1]); ptr_frame->opcode = 0; ptr_frame->payload_size = 0; ptr_frame->payload = NULL; /* save opcode */ switch (opcode) { case WEBSOCKET_FRAME_OPCODE_PING: ptr_frame->opcode = RELAY_MSG_PING; break; case WEBSOCKET_FRAME_OPCODE_CLOSE: ptr_frame->opcode = RELAY_MSG_CLOSE; break; default: ptr_frame->opcode = RELAY_MSG_STANDARD; break; } /* allocate payload */ ptr_frame->payload = malloc (length_frame + 1); if (!ptr_frame->payload) return 0; ptr_frame->payload_size = length_frame; /* fill payload */ if (masked_frame) { for (i = 0; i < length_frame; i++) { ptr_frame->payload[i] = (int)((unsigned char)buffer[index_buffer + i]) ^ mask[i % 4]; } } else { memcpy (ptr_frame->payload, buffer + index_buffer, length_frame); } ptr_frame->payload[length_frame] = '\0'; /* * decompress data if frame is not empty and if "permessage-deflate" * is enabled */ if ((length_frame > 0) && ws_deflate && ws_deflate->enabled) { if (!ws_deflate->strm_inflate) { ws_deflate->strm_inflate = calloc ( 1, sizeof (*ws_deflate->strm_inflate)); if (!ws_deflate->strm_inflate) return 0; if (!relay_websocket_deflate_init_stream_inflate (ws_deflate)) return 0; } payload_decompressed = relay_websocket_inflate ( ptr_frame->payload, ptr_frame->payload_size, ws_deflate->strm_inflate, &size_decompressed); if (!payload_decompressed) return 0; free (ptr_frame->payload); ptr_frame->payload = payload_decompressed; ptr_frame->payload_size = size_decompressed; if (!ws_deflate->client_context_takeover) relay_websocket_deflate_free_stream_inflate (ws_deflate); } index_buffer += length_frame; } if (*partial_ws_frame) { free (*partial_ws_frame); *partial_ws_frame = NULL; *partial_ws_frame_size = 0; } return 1; missing_data: if (*partial_ws_frame) { free (*partial_ws_frame); *partial_ws_frame = NULL; *partial_ws_frame_size = 0; } size = buffer_length - index_buffer_start_frame; if (size >= 0) { *partial_ws_frame = malloc (size); if (!*partial_ws_frame) return 0; memcpy (*partial_ws_frame, buffer + index_buffer_start_frame, size); *partial_ws_frame_size = size; } return 1; } /* * Compress data to send in a websocket frame (when websocket extension * "permessage-deflate" is enabled). * * Return pointer to compressed data, NULL if error. */ char * relay_websocket_deflate (const void *data, size_t size, z_stream *strm, size_t *size_compressed) { int rc; uLongf dest_size; Bytef *dest; if (!data || (size == 0) || !strm || !size_compressed) return NULL; *size_compressed = 0; dest_size = compressBound (size); dest = malloc (dest_size); if (!dest) return NULL; strm->avail_in = (uInt)size; strm->next_in = (Bytef *)data; strm->total_in = 0; strm->avail_out = (uInt)dest_size; strm->next_out = (Bytef *)dest; strm->total_out = 0; rc = deflate (strm, Z_SYNC_FLUSH); if ((rc == Z_STREAM_END) || (rc == Z_OK)) { *size_compressed = strm->total_out; return (char *)dest; } free (dest); return NULL; } /* * Encode data in a websocket frame. * * Return websocket frame, NULL if error. * Argument "length_frame" is set with the length of frame built. * * Argument "mask_frame" must be 1 when sending to server (remote) and 0 when * sending to a client. * * Note: result must be freed after use. */ char * relay_websocket_encode_frame (struct t_relay_websocket_deflate *ws_deflate, int opcode, int mask_frame, const char *payload, unsigned long long payload_size, unsigned long long *length_frame) { const char *ptr_data; char *payload_compressed; size_t size_compressed; unsigned char *frame, *ptr_mask; unsigned long long i, index, data_size; *length_frame = 0; ptr_data = payload; data_size = payload_size; payload_compressed = NULL; size_compressed = 0; /* * compress data if payload is not empty and if "permessage-deflate" * is enabled */ if (((opcode == WEBSOCKET_FRAME_OPCODE_TEXT) || (opcode == WEBSOCKET_FRAME_OPCODE_BINARY)) && (payload_size > 0) && ws_deflate && ws_deflate->enabled) { if (!ws_deflate->strm_deflate) { ws_deflate->strm_deflate = calloc ( 1, sizeof (*ws_deflate->strm_deflate)); if (!ws_deflate->strm_deflate) return NULL; if (!relay_websocket_deflate_init_stream_deflate (ws_deflate)) return NULL; } payload_compressed = relay_websocket_deflate ( payload, payload_size, ws_deflate->strm_deflate, &size_compressed); if (!payload_compressed) return NULL; ptr_data = payload_compressed; data_size = size_compressed; if ((data_size > 4) && ((unsigned char)ptr_data[data_size - 4] == 0x00) && ((unsigned char)ptr_data[data_size - 3] == 0x00) && ((unsigned char)ptr_data[data_size - 2] == 0xFF) && ((unsigned char)ptr_data[data_size - 1] == 0xFF)) { data_size -= 4; } if (!ws_deflate->server_context_takeover) relay_websocket_deflate_free_stream_deflate (ws_deflate); /* set bit RSV1: indicate permessage-deflate compressed data */ opcode |= 0x40; } frame = malloc (data_size + 14); if (!frame) { free (payload_compressed); return NULL; } frame[0] = 0x80; frame[0] |= opcode; if (data_size <= 125) { /* length on one byte */ frame[1] = data_size; index = 2; } else if (data_size <= 65535) { /* length on 2 bytes */ frame[1] = 126; frame[2] = (data_size >> 8) & 0xFF; frame[3] = data_size & 0xFF; index = 4; } else { /* length on 8 bytes */ frame[1] = 127; frame[2] = (data_size >> 56) & 0xFF; frame[3] = (data_size >> 48) & 0xFF; frame[4] = (data_size >> 40) & 0xFF; frame[5] = (data_size >> 32) & 0xFF; frame[6] = (data_size >> 24) & 0xFF; frame[7] = (data_size >> 16) & 0xFF; frame[8] = (data_size >> 8) & 0xFF; frame[9] = data_size & 0xFF; index = 10; } if (mask_frame) { frame[1] |= 128; ptr_mask = frame + index; gcry_create_nonce (ptr_mask, 4); index += 4; } /* copy buffer after data_size */ memcpy (frame + index, ptr_data, data_size); /* mask frame */ if (mask_frame) { for (i = 0; i < data_size; i++) { frame[index + i] = frame[index + i] ^ ptr_mask[i % 4]; } } *length_frame = index + data_size; free (payload_compressed); return (char *)frame; } /* * Print websocket deflate data in WeeChat log file (usually for crash dump). */ void relay_websocket_deflate_print_log (struct t_relay_websocket_deflate *ws_deflate, const char *prefix) { weechat_log_printf ("%s ws_deflate:", prefix); weechat_log_printf ("%s enabled. . . . . . . . . . : %d", prefix, ws_deflate->enabled); weechat_log_printf ("%s server_context_takeover. . : %d", prefix, ws_deflate->server_context_takeover); weechat_log_printf ("%s client_context_takeover. . : %d", prefix, ws_deflate->client_context_takeover); weechat_log_printf ("%s window_bits_deflate. . . . : %d", prefix, ws_deflate->window_bits_deflate); weechat_log_printf ("%s window_bits_inflate. . . . : %d", prefix, ws_deflate->window_bits_inflate); weechat_log_printf ("%s server_max_window_bits_recv: %d", prefix, ws_deflate->server_max_window_bits_recv); weechat_log_printf ("%s client_max_window_bits_recv: %d", prefix, ws_deflate->client_max_window_bits_recv); weechat_log_printf ("%s strm_deflate . . . . . . . : %p", prefix, ws_deflate->strm_deflate); weechat_log_printf ("%s strm_inflate . . . . . . . : %p", prefix, ws_deflate->strm_inflate); }