summaryrefslogtreecommitdiff
path: root/src/plugins/relay/relay-websocket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/relay/relay-websocket.c')
-rw-r--r--src/plugins/relay/relay-websocket.c588
1 files changed, 544 insertions, 44 deletions
diff --git a/src/plugins/relay/relay-websocket.c b/src/plugins/relay/relay-websocket.c
index 4379119d8..4ea3208a5 100644
--- a/src/plugins/relay/relay-websocket.c
+++ b/src/plugins/relay/relay-websocket.c
@@ -23,6 +23,7 @@
#include <unistd.h>
#include <stdio.h>
#include <string.h>
+#include <zlib.h>
#include "../weechat-plugin.h"
#include "relay.h"
@@ -40,6 +41,120 @@
/*
+ * Allocates a t_relay_websocket_deflate structure.
+ */
+
+struct t_relay_websocket_deflate *
+relay_websocket_deflate_alloc ()
+{
+ 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->server_context_takeover = 0;
+ new_ws_deflate->window_bits_deflate = 0;
+ new_ws_deflate->window_bits_inflate = 0;
+ new_ws_deflate->strm_deflate = NULL;
+ new_ws_deflate->strm_inflate = NULL;
+
+ return new_ws_deflate;
+}
+
+/*
+ * Initializes stream for deflate (compression).
+ *
+ * Returns:
+ * 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;
+}
+
+/*
+ * Frees 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;
+ }
+}
+
+/*
+ * Initializes stream for inflate (decompression).
+ *
+ * Returns:
+ * 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;
+}
+
+/*
+ * Frees 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;
+ }
+}
+
+/*
+ * Frees a websocket deflate structure.
+ */
+
+void
+relay_websocket_deflate_free (struct t_relay_websocket_deflate *ws_deflate)
+{
+ relay_websocket_deflate_free_stream_deflate (ws_deflate);
+ relay_websocket_deflate_free_stream_inflate (ws_deflate);
+ free (ws_deflate);
+}
+
+/*
* Checks if a message is a HTTP GET with resource "/weechat" (for weechat
* protocol) or "/api" (for api protocol).
*
@@ -49,15 +164,18 @@
*/
int
-relay_websocket_is_valid_http_get (struct t_relay_client *client,
+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[client->protocol]);
+ "GET /%s", relay_protocol_string[protocol]);
length = strlen (string);
if (strncmp (message, string, length) != 0)
@@ -122,25 +240,28 @@ relay_websocket_is_valid_http_get (struct t_relay_client *client,
*/
int
-relay_websocket_client_handshake_valid (struct t_relay_client *client)
+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 (client->http_req->headers, "upgrade");
+ 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 (client->http_req->headers, "sec-websocket-key");
+ 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 (client->http_req->headers, "origin");
+ value = weechat_hashtable_get (request->headers, "origin");
if (!value || !value[0])
return -2;
if (regexec (relay_config_regex_websocket_allowed_origins, value, 0,
@@ -155,6 +276,89 @@ relay_websocket_client_handshake_valid (struct t_relay_client *client)
}
/*
+ * Parses 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)
+{
+ char **exts, **params, **items, *error;
+ int i, j, num_exts, num_params, num_items;
+ long number;
+
+ 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->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;
+ 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)
+ {
+ error = NULL;
+ number = strtol (items[1], &error, 10);
+ if (error && !error[0])
+ {
+ 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->window_bits_deflate = (int)number;
+ else
+ ws_deflate->window_bits_inflate = (int)number;
+ }
+ }
+ if (items)
+ weechat_string_free_split (items);
+ }
+ }
+ if (params)
+ weechat_string_free_split (params);
+ }
+
+ weechat_string_free_split (exts);
+}
+
+/*
* Builds the handshake that will be returned to client, to initialize and use
* the websocket.
*
@@ -168,13 +372,17 @@ relay_websocket_client_handshake_valid (struct t_relay_client *client)
*/
char *
-relay_websocket_build_handshake (struct t_relay_client *client)
+relay_websocket_build_handshake (struct t_relay_http_request *request)
{
const char *sec_websocket_key;
- char *key, sec_websocket_accept[128], handshake[1024], hash[160 / 8];
+ char *key, sec_websocket_accept[128], handshake[4096], hash[160 / 8];
+ char sec_websocket_extensions[512];
int length, hash_size;
- sec_websocket_key = weechat_hashtable_get (client->http_req->headers,
+ 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;
@@ -204,20 +412,135 @@ relay_websocket_build_handshake (struct t_relay_client *client)
free (key);
+ if (request->ws_deflate->enabled)
+ {
+ snprintf (
+ sec_websocket_extensions, sizeof (sec_websocket_extensions),
+ "Sec-WebSocket-Extensions: permessage-deflate; "
+ "%s"
+ "%s"
+ "server_max_window_bits=%d; "
+ "client_max_window_bits=%d\r\n",
+ (!request->ws_deflate->server_context_takeover) ? "server_no_context_takeover; " : "",
+ (!request->ws_deflate->client_context_takeover) ? "client_no_context_takeover; " : "",
+ request->ws_deflate->window_bits_deflate,
+ request->ws_deflate->window_bits_inflate);
+ }
+ else
+ {
+ sec_websocket_extensions[0] = '\0';
+ }
+
/* 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"
"\r\n",
- sec_websocket_accept);
+ sec_websocket_accept,
+ sec_websocket_extensions);
return strdup (handshake);
}
/*
- * Decodes a websocket frame.
+ * Decompresses 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').
+ *
+ * Returns 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;
+
+ 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 */
+ dest_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))
+ {
+ /* data successfully decompressed */
+ *size_decompressed = strm->total_out;
+ break;
+ }
+ else if (rc == Z_BUF_ERROR)
+ {
+ strm->avail_out += dest_size;
+ dest_size *= 2;
+ 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';
+ if (data2)
+ free (data2);
+ return (char *)dest;
+
+error:
+ if (data2)
+ free (data2);
+ if (dest)
+ free (dest);
+ return NULL;
+}
+
+/*
+ * Decodes a websocket frame and return a list of frames in "*frames" (each
+ * frame is first decompressed if "permessage-deflate" websocket extension
+ * is used).
*
* Returns:
* 1: frame decoded successfully
@@ -225,20 +548,42 @@ relay_websocket_build_handshake (struct t_relay_client *client)
*/
int
-relay_websocket_decode_frame (const unsigned char *buffer,
+relay_websocket_decode_frame (struct t_relay_client *client,
+ const unsigned char *buffer,
unsigned long long buffer_length,
- unsigned char *decoded,
- unsigned long long *decoded_length)
+ struct t_relay_websocket_frame **frames,
+ int *num_frames)
{
unsigned long long i, index_buffer, length_frame_size, length_frame;
unsigned char opcode;
+ size_t size_decompressed;
+ char *payload_decompressed;
+ struct t_relay_websocket_frame *frames2, *ptr_frame;
+
+ if (!buffer || !frames || !num_frames)
+ return 0;
+
+ *frames = NULL;
+ *num_frames = 0;
- *decoded_length = 0;
index_buffer = 0;
/* loop to decode all frames in message */
while (index_buffer + 1 < buffer_length)
{
+ (*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;
+
opcode = buffer[index_buffer] & 15;
/*
@@ -276,20 +621,19 @@ relay_websocket_decode_frame (const unsigned char *buffer,
}
index_buffer += 4;
- /* copy opcode in decoded data */
+ /* save opcode */
switch (opcode)
{
case WEBSOCKET_FRAME_OPCODE_PING:
- decoded[*decoded_length] = RELAY_CLIENT_MSG_PING;
+ ptr_frame->opcode = RELAY_CLIENT_MSG_PING;
break;
case WEBSOCKET_FRAME_OPCODE_CLOSE:
- decoded[*decoded_length] = RELAY_CLIENT_MSG_CLOSE;
+ ptr_frame->opcode = RELAY_CLIENT_MSG_CLOSE;
break;
default:
- decoded[*decoded_length] = RELAY_CLIENT_MSG_STANDARD;
+ ptr_frame->opcode = RELAY_CLIENT_MSG_STANDARD;
break;
}
- *decoded_length += 1;
/* decode data using masks */
if ((length_frame > buffer_length)
@@ -297,12 +641,48 @@ relay_websocket_decode_frame (const unsigned char *buffer,
{
return 0;
}
+
+ ptr_frame->payload = malloc (length_frame + 1);
+ if (!ptr_frame->payload)
+ return 0;
+ ptr_frame->payload_size = length_frame;
+
+ /* fill payload */
for (i = 0; i < length_frame; i++)
{
- decoded[*decoded_length + i] = (int)((unsigned char)buffer[index_buffer + i]) ^ masks[i % 4];
+ ptr_frame->payload[i] = (int)((unsigned char)buffer[index_buffer + i]) ^ masks[i % 4];
}
- decoded[*decoded_length + length_frame] = '\0';
- *decoded_length += length_frame + 1;
+ ptr_frame->payload[length_frame] = '\0';
+
+ /*
+ * decompress data if frame is not empty and if "permessage-deflate"
+ * is enabled
+ */
+ if ((length_frame > 0) && client->ws_deflate->enabled)
+ {
+ if (!client->ws_deflate->strm_inflate)
+ {
+ client->ws_deflate->strm_inflate = calloc (
+ 1, sizeof (*client->ws_deflate->strm_inflate));
+ if (!client->ws_deflate->strm_inflate)
+ return 0;
+ if (!relay_websocket_deflate_init_stream_inflate (client->ws_deflate))
+ return 0;
+ }
+ payload_decompressed = relay_websocket_inflate (
+ ptr_frame->payload,
+ ptr_frame->payload_size,
+ client->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 (!client->ws_deflate->client_context_takeover)
+ relay_websocket_deflate_free_stream_inflate (client->ws_deflate);
+ }
+
index_buffer += length_frame;
}
@@ -310,6 +690,50 @@ relay_websocket_decode_frame (const unsigned char *buffer,
}
/*
+ * Compresses data to send in a websocket frame (when websocket extension
+ * "permessage-deflate" is enabled).
+ *
+ * Returns 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;
+}
+
+/*
* Encodes data in a websocket frame.
*
* Returns websocket frame, NULL if error.
@@ -319,56 +743,132 @@ relay_websocket_decode_frame (const unsigned char *buffer,
*/
char *
-relay_websocket_encode_frame (int opcode,
- const char *buffer,
- unsigned long long length,
+relay_websocket_encode_frame (struct t_relay_client *client,
+ int opcode,
+ 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;
- unsigned long long index;
+ unsigned long long index, data_size;
*length_frame = 0;
- frame = malloc (length + 10);
+ 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)
+ && client->ws_deflate->enabled)
+ {
+ if (!client->ws_deflate->strm_deflate)
+ {
+ client->ws_deflate->strm_deflate = calloc (
+ 1, sizeof (*client->ws_deflate->strm_deflate));
+ if (!client->ws_deflate->strm_deflate)
+ return NULL;
+ if (!relay_websocket_deflate_init_stream_deflate (client->ws_deflate))
+ return NULL;
+ }
+ payload_compressed = relay_websocket_deflate (
+ payload,
+ payload_size,
+ client->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 (!client->ws_deflate->server_context_takeover)
+ relay_websocket_deflate_free_stream_deflate (client->ws_deflate);
+ /* set bit RSV1: indicate permessage-deflate compressed data */
+ opcode |= 0x40;
+ }
+
+ frame = malloc (data_size + 10);
if (!frame)
+ {
+ if (payload_compressed)
+ free (payload_compressed);
return NULL;
+ }
frame[0] = 0x80;
frame[0] |= opcode;
- if (length <= 125)
+ if (data_size <= 125)
{
/* length on one byte */
- frame[1] = length;
+ frame[1] = data_size;
index = 2;
}
- else if (length <= 65535)
+ else if (data_size <= 65535)
{
/* length on 2 bytes */
frame[1] = 126;
- frame[2] = (length >> 8) & 0xFF;
- frame[3] = length & 0xFF;
+ frame[2] = (data_size >> 8) & 0xFF;
+ frame[3] = data_size & 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;
+ 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;
}
- /* copy buffer after length */
- memcpy (frame + index, buffer, length);
+ /* copy buffer after data_size */
+ memcpy (frame + index, ptr_data, data_size);
- *length_frame = index + length;
+ *length_frame = index + data_size;
+
+ if (payload_compressed)
+ free (payload_compressed);
return (char *)frame;
}
+
+/*
+ * Prints 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 strm_deflate. . . . . . : 0x%lx", prefix, ws_deflate->strm_deflate);
+ weechat_log_printf ("%s strm_inflate. . . . . . : 0x%lx", prefix, ws_deflate->strm_inflate);
+}