mirror of
https://github.com/openappsec/attachment.git
synced 2025-06-28 16:41:03 +03:00
549 lines
18 KiB
C
549 lines
18 KiB
C
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/// @file ngx_cp_compression.c
|
|
#include "ngx_cp_compression.h"
|
|
|
|
#include "ngx_cp_utils.h"
|
|
#include "ngx_cp_metric.h"
|
|
|
|
static ngx_int_t is_debug_printing_initialized = 0;
|
|
|
|
ngx_int_t
|
|
is_compression_debug_printing_initialized()
|
|
{
|
|
return is_debug_printing_initialized;
|
|
}
|
|
|
|
///
|
|
/// @brief Writes a debug message at a debug level of Trace.
|
|
/// @param[in] debug_message message to be written.
|
|
///
|
|
static void
|
|
compression_trace_level_debug_printer(const char *debug_message)
|
|
{
|
|
write_dbg(DBG_LEVEL_TRACE, debug_message);
|
|
}
|
|
|
|
///
|
|
/// @brief Writes a debug message at a debug level of Debug.
|
|
/// @param[in] debug_message message to be written.
|
|
///
|
|
static void
|
|
compression_debug_level_debug_printer(const char *debug_message)
|
|
{
|
|
write_dbg(DBG_LEVEL_DEBUG, debug_message);
|
|
}
|
|
|
|
///
|
|
/// @brief Writes a debug message at a debug level of Info.
|
|
/// @param[in] debug_message message to be written.
|
|
///
|
|
static void
|
|
compression_info_level_debug_printer(const char *debug_message)
|
|
{
|
|
write_dbg(DBG_LEVEL_INFO, debug_message);
|
|
}
|
|
|
|
///
|
|
/// @brief Writes a debug message at a debug level of Warning.
|
|
/// @param[in] debug_message message to be written.
|
|
///
|
|
static void
|
|
compression_warning_level_debug_printer(const char *debug_message)
|
|
{
|
|
write_dbg(DBG_LEVEL_WARNING, debug_message);
|
|
}
|
|
|
|
///
|
|
/// @brief Writes a debug message at a debug level of Error.
|
|
/// @param[in] debug_message message to be written.
|
|
///
|
|
static void
|
|
compression_error_level_debug_printer(const char *debug_message)
|
|
{
|
|
write_dbg(DBG_LEVEL_ERROR, debug_message);
|
|
}
|
|
|
|
///
|
|
/// @brief Writes a debug message at a debug level of Assert.
|
|
/// @param[in] debug_message message to be written.
|
|
///
|
|
static void
|
|
compression_assert_level_debug_printer(const char *debug_message)
|
|
{
|
|
write_dbg(DBG_LEVEL_ASSERT, debug_message);
|
|
}
|
|
|
|
void
|
|
initialize_compression_debug_printing()
|
|
{
|
|
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_TRACE, compression_trace_level_debug_printer);
|
|
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_DEBUG, compression_debug_level_debug_printer);
|
|
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_INFO, compression_info_level_debug_printer);
|
|
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_WARNING, compression_warning_level_debug_printer);
|
|
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_ERROR, compression_error_level_debug_printer);
|
|
setCompressionDebugFunction(COMPRESSION_DBG_LEVEL_ASSERTION, compression_assert_level_debug_printer);
|
|
|
|
is_debug_printing_initialized = 1;
|
|
}
|
|
|
|
///
|
|
/// @brief Checks if the compression buffer is valid.
|
|
/// @param[in] should_compress Checks if buffer can be used for compression.
|
|
/// - #0 - Buffer is used for decompression.
|
|
/// - #1 - Buffer is used for compression.
|
|
/// @param[in] buffer message to be written.
|
|
/// @returns ngx_int_t
|
|
/// - #NGX_OK
|
|
/// - #NGX_ERROR
|
|
///
|
|
static ngx_int_t
|
|
is_valid_compression_buffer(const ngx_int_t should_compress, const ngx_buf_t *buffer)
|
|
{
|
|
uint64_t buffer_size = buffer->last - buffer->pos;
|
|
|
|
if (buffer_size == 0 && !should_compress) {
|
|
write_dbg(DBG_LEVEL_WARNING, "Invalid decompression buffer: has size 0");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
///
|
|
/// @brief Gets the NGINX string data from NGINX buffer.
|
|
/// @param[in, out] buffer_data NGINX string, used as a destination.
|
|
/// @param[in] buffer NGINX buffer.
|
|
///
|
|
static void
|
|
get_buffer_data(ngx_str_t *buffer_data, const ngx_buf_t *buffer)
|
|
{
|
|
if (buffer_data == NULL) {
|
|
write_dbg(DBG_LEVEL_WARNING, "Passed a null pointer as destination buffer");
|
|
return;
|
|
}
|
|
|
|
buffer_data->len = buffer->last - buffer->pos;
|
|
buffer_data->data = buffer->pos;
|
|
}
|
|
|
|
///
|
|
/// @brief Sets the buffer from NGINX string to NGINX buffer.
|
|
/// @param[in, out] buffer NGINX buffer, used as a destination.
|
|
/// @param[in] buffer_data NGINX string.
|
|
///
|
|
static void
|
|
set_buffer_data(ngx_buf_t *buffer, const ngx_str_t *data)
|
|
{
|
|
buffer->start = data->data;
|
|
buffer->pos = buffer->start;
|
|
buffer->last = buffer->start + data->len;
|
|
buffer->end = buffer->last;
|
|
}
|
|
|
|
///
|
|
/// @brief Decompresses or compresses the provided data.
|
|
/// @param[in] should_compress Checks if buffer is used for compression or decompression.
|
|
/// - #0 - Function will decompression.
|
|
/// - #1 - Function will compress.
|
|
/// @param[in, out] compression_stream CompressionStream to compress.
|
|
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
|
|
/// @param[in, out] output NGINX string to be used as output.
|
|
/// @param[in] input NGINX string input to be used as input.
|
|
/// @param[in] pool NGINX pool.
|
|
/// @param[in, out] params Holds NGINX compression parameters.
|
|
/// @returns ngx_int_t
|
|
/// - #NGX_OK
|
|
/// - #NGX_ERROR
|
|
///
|
|
static ngx_int_t
|
|
compression_data_filter(
|
|
const ngx_int_t should_compress,
|
|
CompressionStream *compression_stream,
|
|
int *is_last_decompressed_part,
|
|
ngx_str_t *output,
|
|
ngx_str_t *input,
|
|
ngx_pool_t *pool,
|
|
ngx_cp_http_compression_params *params
|
|
)
|
|
{
|
|
CompressionResult compression_result;
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Performing %s on buffer data", should_compress ? "compression" : "decompression");
|
|
|
|
if (should_compress && params == NULL) {
|
|
write_dbg(DBG_LEVEL_ASSERT, "Passed a pointer to null as compression parameters");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (should_compress) {
|
|
// Compressing data.
|
|
compression_result = compressData(
|
|
compression_stream,
|
|
params->compression_type,
|
|
input->len,
|
|
input->data,
|
|
params->is_last_part
|
|
);
|
|
} else {
|
|
// Decompressing data.
|
|
DecompressionResult decompression_result = decompressData(compression_stream, input->len, input->data);
|
|
compression_result.ok = decompression_result.ok;
|
|
compression_result.num_output_bytes = decompression_result.num_output_bytes;
|
|
compression_result.output = decompression_result.output;
|
|
*is_last_decompressed_part = decompression_result.is_last_chunk;
|
|
}
|
|
if (!compression_result.ok) return NGX_ERROR;
|
|
|
|
if (compression_result.output == NULL) {
|
|
output->len = 0;
|
|
output->data = (u_char *)"";
|
|
} else {
|
|
output->len = compression_result.num_output_bytes;
|
|
output->data = ngx_palloc(pool, output->len);
|
|
if (output->data == NULL) {
|
|
// Failed to allocate a new buffer.
|
|
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate a new buffer");
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_memcpy(output->data, compression_result.output, output->len);
|
|
free(compression_result.output);
|
|
}
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Successfully %s buffer data", should_compress ? "compressed" : "decompressed");
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
///
|
|
/// @brief Decompresses or compresses the provided buffer.
|
|
/// @param[in] should_compress Checks if buffer is used for compression or decompression.
|
|
/// - #0 - Function will decompression.
|
|
/// - #1 - Function will compress.
|
|
/// @param[in, out] compression_stream CompressionStream to compress.
|
|
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
|
|
/// @param[in, out] dest NGINX buffer used as destination.
|
|
/// @param[in] src NGINX buffer used as source.
|
|
/// @param[in] pool NGINX pool.
|
|
/// @param[in] params Holds NGINX compression parameters.
|
|
/// @returns ngx_int_t
|
|
/// - #NGX_OK
|
|
/// - #NGX_ERROR
|
|
///
|
|
static ngx_int_t
|
|
compression_buffer_filter(
|
|
const ngx_int_t should_compress,
|
|
CompressionStream *compression_stream,
|
|
int *is_last_decompressed_part,
|
|
ngx_buf_t *dest,
|
|
ngx_buf_t *src,
|
|
ngx_pool_t *pool,
|
|
ngx_cp_http_compression_params *params
|
|
)
|
|
{
|
|
ngx_str_t src_data;
|
|
ngx_str_t dest_data;
|
|
ngx_int_t compression_result;
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Performing %s on buffer", should_compress ? "compression" : "decompression");
|
|
|
|
if (is_valid_compression_buffer(should_compress, src) != NGX_OK) {
|
|
// Invalid buffer provided.
|
|
write_dbg(DBG_LEVEL_WARNING, "Failed to %s: invalid buffer", should_compress ? "compress" : "decompress");
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (should_compress) {
|
|
// Preparing data for compression.
|
|
params->is_last_part = src->last_buf;
|
|
|
|
if (params->is_last_part && src->pos == NULL) {
|
|
src->start = (u_char *)"";
|
|
src->pos = src->start;
|
|
src->last = src->start;
|
|
src->end = src->start;
|
|
}
|
|
}
|
|
|
|
get_buffer_data(&src_data, src);
|
|
// Compresses the data
|
|
compression_result = compression_data_filter(
|
|
should_compress,
|
|
compression_stream,
|
|
is_last_decompressed_part,
|
|
&dest_data,
|
|
&src_data,
|
|
pool,
|
|
params
|
|
);
|
|
if (compression_result != NGX_OK) {
|
|
// Failed to compress or decompress.
|
|
write_dbg(DBG_LEVEL_WARNING, "Failed to %s data", should_compress ? "compress" : "decompress");
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_memcpy(dest, src, sizeof(ngx_buf_t));
|
|
set_buffer_data(dest, &dest_data);
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Successfully %s buffer", should_compress ? "compressed" : "decompressed");
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
///
|
|
/// @brief Compresses the provided chain.
|
|
/// @param[in] should_compress Checks if buffer is used for compression or decompression.
|
|
/// - #0 - Function will decompression.
|
|
/// - #1 - Function will compress.
|
|
/// @param[in, out] compression_stream CompressionStream to compress.
|
|
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
|
|
/// @param[in, out] body NGINX chain used as destination.
|
|
/// @param[in] original_body_contents NGINX chain used as source.
|
|
/// @param[in] pool NGINX pool.
|
|
/// @param[in] params Holds NGINX cp compression parameters.
|
|
/// @returns ngx_int_t
|
|
/// - #NGX_OK
|
|
/// - #NGX_ERROR
|
|
///
|
|
static ngx_int_t
|
|
compression_chain_filter(
|
|
const ngx_int_t should_compress,
|
|
CompressionStream *compression_stream,
|
|
int *is_last_decompressed_part,
|
|
ngx_chain_t **body,
|
|
ngx_chain_t **original_body_contents,
|
|
ngx_pool_t *pool,
|
|
ngx_cp_http_compression_params *params
|
|
)
|
|
{
|
|
ngx_int_t compression_result;
|
|
ngx_buf_t *output_buffer = ngx_calloc_buf(pool);
|
|
ngx_chain_t *curr_input_link = NULL;
|
|
ngx_chain_t *curr_original_contents_link = original_body_contents == NULL ? NULL : *original_body_contents;
|
|
|
|
if (body == NULL) {
|
|
// Null body parameter has been passed.
|
|
write_dbg(
|
|
DBG_LEVEL_WARNING,
|
|
"Failed to %s chain: passed null pointer as output chain",
|
|
should_compress ? "compress" : "decompress"
|
|
);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Performing %s on chain", should_compress ? "compression" : "decompression");
|
|
|
|
for (curr_input_link = *body; curr_input_link != NULL; curr_input_link = curr_input_link->next) {
|
|
// Decompress or compresses buffer
|
|
compression_result = compression_buffer_filter(
|
|
should_compress,
|
|
compression_stream,
|
|
is_last_decompressed_part,
|
|
output_buffer,
|
|
curr_input_link->buf,
|
|
pool,
|
|
params
|
|
);
|
|
if (compression_result != NGX_OK) {
|
|
// Failed to decompress or compress.
|
|
free_chain(pool, *body);
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (curr_original_contents_link != NULL) {
|
|
curr_original_contents_link->buf = ngx_calloc_buf(pool);
|
|
ngx_memcpy(curr_original_contents_link->buf, curr_input_link->buf, sizeof(ngx_buf_t));
|
|
|
|
if (curr_input_link->next != NULL) {
|
|
// Allocates next chain.
|
|
curr_original_contents_link->next = ngx_alloc_chain_link(pool);
|
|
ngx_memset(curr_original_contents_link->next, 0, sizeof(ngx_chain_t));
|
|
curr_original_contents_link = curr_original_contents_link->next;
|
|
}
|
|
}
|
|
|
|
ngx_memcpy(curr_input_link->buf, output_buffer, sizeof(ngx_buf_t));
|
|
curr_input_link->buf->memory = 1;
|
|
}
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Successfully %s chain", should_compress ? "compressed" : "decompressed");
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
///
|
|
/// @brief Sets the ngx_cp_http_compression_params and calls compression_chain_filter with compression flag.
|
|
/// @param[in, out] compression_stream CompressionStream to compress.
|
|
/// @param[in] compression_type Compression type to be used by compressions.
|
|
/// @param[in] is_last_part Flags if the buffer's last part was compressed.
|
|
/// @param[in, out] body NGINX chain used as destination.
|
|
/// @param[in] original_body_contents NGINX chain used as source.
|
|
/// @param[in] pool NGINX pool.
|
|
/// @returns ngx_int_t
|
|
/// - #NGX_OK
|
|
/// - #NGX_ERROR
|
|
///
|
|
static ngx_int_t
|
|
compress_chain(
|
|
CompressionStream *compression_stream,
|
|
const CompressionType compression_type,
|
|
const int is_last_part,
|
|
ngx_chain_t **body,
|
|
ngx_chain_t **original_body_contents,
|
|
ngx_pool_t *pool
|
|
)
|
|
{
|
|
ngx_cp_http_compression_params params;
|
|
params.compression_type = compression_type;
|
|
params.is_last_part = is_last_part;
|
|
|
|
return compression_chain_filter(1, compression_stream, NULL, body, original_body_contents, pool, ¶ms);
|
|
}
|
|
|
|
///
|
|
/// @brief Sets the ngx_cp_http_compression_params and calls compression_chain_filter with decompression flag.
|
|
/// @param[in, out] compression_stream CompressionStream to compress.
|
|
/// @param[in] compression_type Compression type to be used by compressions.
|
|
/// @param[in] is_last_decompressed_part Flags if the buffer's last part was decompressed.
|
|
/// @param[in, out] body NGINX chain used as destination.
|
|
/// @param[in] original_body_contents NGINX chain used as source.
|
|
/// @param[in] pool NGINX pool.
|
|
/// @returns ngx_int_t
|
|
/// - #NGX_OK
|
|
/// - #NGX_ERROR
|
|
///
|
|
static ngx_int_t
|
|
decompress_chain(
|
|
CompressionStream *decompress_stream,
|
|
int *is_last_decompressed_part,
|
|
ngx_chain_t **body,
|
|
ngx_chain_t **original_body,
|
|
ngx_pool_t *pool
|
|
)
|
|
{
|
|
return
|
|
compression_chain_filter(
|
|
0,
|
|
decompress_stream,
|
|
is_last_decompressed_part,
|
|
body,
|
|
original_body,
|
|
pool,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
ngx_int_t
|
|
decompress_body(
|
|
CompressionStream *decompression_stream,
|
|
const ngx_http_chunk_type_e chunk_type,
|
|
int *is_last_decompressed_part,
|
|
ngx_chain_t **body,
|
|
ngx_chain_t **original_body_contents,
|
|
ngx_pool_t *pool
|
|
)
|
|
{
|
|
char *body_type = chunk_type == REQUEST_BODY ? "request" : "response";
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Decompressing %s body", body_type);
|
|
|
|
ngx_int_t decompress_data_res = decompress_chain(
|
|
decompression_stream,
|
|
is_last_decompressed_part,
|
|
body,
|
|
original_body_contents,
|
|
pool
|
|
);
|
|
if (decompress_data_res != NGX_OK) {
|
|
// Failed to decompress the provided data.
|
|
write_dbg(DBG_LEVEL_WARNING, "Failed to decompress %s body", body_type);
|
|
updateMetricField(
|
|
chunk_type == REQUEST_BODY ? REQ_FAILED_DECOMPRESSION_COUNT : RES_FAILED_DECOMPRESSION_COUNT,
|
|
1
|
|
);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Successfully decompressed %s body", body_type);
|
|
updateMetricField(
|
|
chunk_type == REQUEST_BODY ? REQ_SUCCESSFUL_DECOMPRESSION_COUNT : RES_SUCCESSFUL_DECOMPRESSION_COUNT,
|
|
1
|
|
);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
ngx_int_t
|
|
compress_body(
|
|
CompressionStream *compression_stream,
|
|
const CompressionType compression_type,
|
|
const ngx_http_chunk_type_e chunk_type,
|
|
const int is_last_part,
|
|
ngx_chain_t **body,
|
|
ngx_chain_t **original_body_contents,
|
|
ngx_pool_t *pool
|
|
)
|
|
{
|
|
ngx_int_t compress_res;
|
|
char *body_type;
|
|
|
|
if (compression_type == NO_COMPRESSION) {
|
|
// This function should not be called with a NO_COMPRESSION type.
|
|
// This if statement serves a case that somewhere throughout the code the data
|
|
// is set to be compressed but the compression type is wrongly set.
|
|
write_dbg(DBG_LEVEL_WARNING, "Invalid compression type: NO_COMPRESSION");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
body_type = chunk_type == REQUEST_BODY ? "request" : "response";
|
|
write_dbg(
|
|
DBG_LEVEL_TRACE,
|
|
"Compressing plain-text %s body in the format \"%s\"",
|
|
body_type,
|
|
compression_type == GZIP ? "gzip" : "zlib"
|
|
);
|
|
// Checks if the compression was successful.
|
|
compress_res = compress_chain(
|
|
compression_stream,
|
|
compression_type,
|
|
is_last_part,
|
|
body,
|
|
original_body_contents,
|
|
pool
|
|
);
|
|
if (compress_res != NGX_OK) {
|
|
// Failed to compress the body.
|
|
write_dbg(DBG_LEVEL_WARNING, "Failed to compress %s body", body_type);
|
|
updateMetricField(
|
|
chunk_type == REQUEST_BODY ? REQ_FAILED_COMPRESSION_COUNT : RES_FAILED_COMPRESSION_COUNT,
|
|
1
|
|
);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
write_dbg(DBG_LEVEL_TRACE, "Successfully compressed %s body", body_type);
|
|
updateMetricField(
|
|
chunk_type == REQUEST_BODY ? REQ_SUCCESSFUL_COMPRESSION_COUNT : RES_SUCCESSFUL_COMPRESSION_COUNT,
|
|
1
|
|
);
|
|
|
|
return NGX_OK;
|
|
}
|