/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION #include "zlib.h" #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) #include #include #define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) #else #define SET_BINARY_MODE(file) #endif // defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) // Defining zlib chunks to be 256k static const size_t ZLIB_CHUNK=263144; static const char AWS_REQUEST_COMPRESSION_ALLOCATION_TAG[] = "RequestCompressionAlloc"; #endif // ENABLED_ZLIB_REQUEST_COMPRESSION static const char AWS_REQUEST_COMPRESSION_LOG_TAG[] = "RequestCompression"; Aws::String Aws::Client::GetCompressionAlgorithmId(const CompressionAlgorithm &algorithm) { switch (algorithm) { case CompressionAlgorithm::GZIP: return "gzip"; default: return ""; } } #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION #ifdef USE_AWS_MEMORY_MANAGEMENT static const char* ZlibMemTag = "zlib"; static const size_t offset = sizeof(size_t); // to make space for size of the array //Define custom memory allocation for zlib // if fail to allocate, return Z_NULL void* aws_zalloc(void * /* opaque */, unsigned items, unsigned size) { unsigned sizeToAllocate = items*size; size_t sizeToAllocateWithOffset = sizeToAllocate + offset; if ((size != 0 && sizeToAllocate / size != items) || (sizeToAllocateWithOffset <= sizeToAllocate )) { return Z_NULL; } char* newMem = reinterpret_cast(Aws::Malloc(ZlibMemTag, sizeToAllocateWithOffset)); if (newMem != nullptr) { std::size_t* pointerToSize = reinterpret_cast(newMem); *pointerToSize = size; return reinterpret_cast(newMem + offset); } else { return Z_NULL; } } void aws_zfree(void * /* opaque */, void * ptr) { if(ptr) { char* shiftedMemory = reinterpret_cast(ptr); Aws::Free(shiftedMemory - offset); } } #endif // AWS_CUSTOM_MEMORY_MANAGEMENT #endif // ENABLED_ZLIB_REQUEST_COMPRESSION iostream_outcome Aws::Client::RequestCompression::compress(std::shared_ptr input, const CompressionAlgorithm &algorithm) const { #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION if (algorithm == CompressionAlgorithm::GZIP) { // calculating stream size input->seekg(0, input->end); size_t streamSize = input->tellg(); input->seekg(0, input->beg); AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressing request of " << streamSize << " bytes."); // Preparing output std::shared_ptr output = Aws::MakeShared(AWS_REQUEST_COMPRESSION_ALLOCATION_TAG); if(!output) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate output while compressing") return false; } // Prepare ZLIB to compress int ret = Z_NULL; int flush = Z_NO_FLUSH; z_stream strm = {}; auto in = Aws::MakeUniqueArray(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG); if(!in) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate in buffer while compressing") return false; } auto out = Aws::MakeUniqueArray(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG); if(!out) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate out buffer while compressing") return false; } //Preparing allocators #ifdef USE_AWS_MEMORY_MANAGEMENT strm.zalloc = (void *(*)(void *, unsigned, unsigned)) aws_zalloc; strm.zfree = (void (*)(void *, void *)) aws_zfree; #else strm.zalloc = Z_NULL; strm.zfree = Z_NULL; #endif strm.opaque = Z_NULL; const int MAX_WINDOW_GZIP = 31; const int DEFAULT_MEM_LEVEL_USAGE = 8; ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WINDOW_GZIP, DEFAULT_MEM_LEVEL_USAGE, Z_DEFAULT_STRATEGY); if(ret != Z_OK) { return false; } //Adding one to the stream size counter to account for the EOF marker. streamSize++; size_t toRead; // Compress do { toRead = std::min(streamSize, ZLIB_CHUNK); // Fill the buffer if (! input->read(reinterpret_cast(in.get()), toRead)) { if (input->eof()) { //Last read need flush when exit loop flush = Z_FINISH; } else { AWS_LOGSTREAM_ERROR( AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompress request failed to read from stream"); return false; } } streamSize -= toRead; //left to read strm.avail_in = (flush == Z_FINISH)?toRead-1:toRead; //skip EOF if included strm.next_in = in.get(); do { // Run deflate on buffers strm.avail_out = ZLIB_CHUNK; strm.next_out = out.get(); ret = deflate(&strm, flush); // writing the output assert(ZLIB_CHUNK >= strm.avail_out); unsigned output_size = ZLIB_CHUNK - strm.avail_out; if(! output->write(reinterpret_cast(out.get()), output_size)) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to write to output stream"); return false; } } while (strm.avail_out == 0); assert(strm.avail_in == 0); // All data was read } while (flush != Z_FINISH); assert(ret == Z_STREAM_END); // Completed stream AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request to: " << strm.total_out << " bytes"); deflateEnd(&strm); return output; } else { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm)); return false; } #else // If there is no support to compress, always fail calls to this method. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm)); AWS_UNREFERENCED_PARAM(input); // silencing warning; return false; #endif } Aws::Utils::Outcome, bool> Aws::Client::RequestCompression::uncompress(std::shared_ptr input, const CompressionAlgorithm &algorithm) const { #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION if (algorithm == CompressionAlgorithm::GZIP) { // calculating stream size input->seekg(0, input->end); size_t streamSize = input->tellg(); input->seekg(0, input->beg); AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompressing request of " << streamSize << " bytes."); // Preparing output std::shared_ptr output = Aws::MakeShared( AWS_REQUEST_COMPRESSION_ALLOCATION_TAG); if(!output) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate output while uncompressing") return false; } // Prepare ZLIB to uncompress int ret = Z_NULL; z_stream strm = {}; auto in = Aws::MakeUniqueArray(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG); if(!in) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate in buffer while uncompressing") return false; } auto out = Aws::MakeUniqueArray(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG); if(!out) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate out buffer while uncompressing") return false; } //preparing allocation #ifdef USE_AWS_MEMORY_MANAGEMENT strm.zalloc = (void *(*)(void *, unsigned, unsigned)) aws_zalloc; strm.zfree = (void (*)(void *, void *)) aws_zfree; #else strm.zalloc = Z_NULL; strm.zfree = Z_NULL; #endif strm.opaque = Z_NULL; strm.avail_in = 0; strm.next_in = Z_NULL; const int MAX_WINDOW_GZIP = 31; ret = inflateInit2(&strm, MAX_WINDOW_GZIP); if (ret != Z_OK) { return false; } //Adding one to the stream size counter to account for the EOF marker. streamSize++; size_t toRead; // Decompress do { toRead = (streamSize < ZLIB_CHUNK)?streamSize:ZLIB_CHUNK; if (toRead < 1) break; // Nothing left to read // Fill the buffer if(! input->read(reinterpret_cast(in.get()), toRead)) { if (input->eof()) { //skip passing the EOF to the buffer toRead--; } else { AWS_LOGSTREAM_ERROR( AWS_REQUEST_COMPRESSION_LOG_TAG, "Compress request failed to read from stream"); return false; } } // Filling input buffer to decompress strm.avail_in = toRead; strm.next_in = in.get(); do { // Run inflate on buffers strm.avail_out = ZLIB_CHUNK; strm.next_out = out.get(); ret = inflate(&strm, Z_NO_FLUSH); // Catch errors switch (ret) { case Z_NEED_DICT: AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to inflate with code: Z_NEED_DICT"); return false; case Z_DATA_ERROR: AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to inflate with code: Z_DATA_ERROR"); return false; case Z_MEM_ERROR: (void)inflateEnd(&strm); AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to inflate with code: Z_MEM_ERROR"); return false; } // writing the output unsigned output_size = ZLIB_CHUNK - strm.avail_out; if(! output->write(reinterpret_cast(out.get()), output_size)) { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompressed request failed to write to output stream"); return false; } } while (strm.avail_out == 0); } while (ret != Z_STREAM_END); // clean up (void)inflateEnd(&strm); if (ret == Z_STREAM_END) { AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Decompressed request to: " << strm.total_out << " bytes"); return output; } else { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to decompress after read input completely"); return false; } } else { AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm)); return false; } #else // If there is no support to compress, always fail calls to this method. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm)); AWS_UNREFERENCED_PARAM(input); // silencing warning; return false; #endif }