RequestCompression.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. /**
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. * SPDX-License-Identifier: Apache-2.0.
  4. */
  5. #include <aws/core/client/RequestCompression.h>
  6. #include <aws/core/utils/logging/LogMacros.h>
  7. #include <aws/core/utils/memory/AWSMemory.h>
  8. #include <algorithm>
  9. #include <aws/core/utils/memory/stl/AWSAllocator.h>
  10. #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION
  11. #include "zlib.h"
  12. #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
  13. #include <fcntl.h>
  14. #include <io.h>
  15. #define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
  16. #else
  17. #define SET_BINARY_MODE(file)
  18. #endif // defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
  19. // Defining zlib chunks to be 256k
  20. static const size_t ZLIB_CHUNK=263144;
  21. static const char AWS_REQUEST_COMPRESSION_ALLOCATION_TAG[] =
  22. "RequestCompressionAlloc";
  23. #endif // ENABLED_ZLIB_REQUEST_COMPRESSION
  24. static const char AWS_REQUEST_COMPRESSION_LOG_TAG[] = "RequestCompression";
  25. Aws::String Aws::Client::GetCompressionAlgorithmId(const CompressionAlgorithm &algorithm)
  26. {
  27. switch (algorithm)
  28. {
  29. case CompressionAlgorithm::GZIP:
  30. return "gzip";
  31. default:
  32. return "";
  33. }
  34. }
  35. #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION
  36. #ifdef USE_AWS_MEMORY_MANAGEMENT
  37. static const char* ZlibMemTag = "zlib";
  38. static const size_t offset = sizeof(size_t); // to make space for size of the array
  39. //Define custom memory allocation for zlib
  40. // if fail to allocate, return Z_NULL
  41. void* aws_zalloc(void * /* opaque */, unsigned items, unsigned size)
  42. {
  43. unsigned sizeToAllocate = items*size;
  44. size_t sizeToAllocateWithOffset = sizeToAllocate + offset;
  45. if ((size != 0 && sizeToAllocate / size != items)
  46. || (sizeToAllocateWithOffset <= sizeToAllocate ))
  47. {
  48. return Z_NULL;
  49. }
  50. char* newMem = reinterpret_cast<char*>(Aws::Malloc(ZlibMemTag, sizeToAllocateWithOffset));
  51. if (newMem != nullptr) {
  52. std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(newMem);
  53. *pointerToSize = size;
  54. return reinterpret_cast<void*>(newMem + offset);
  55. }
  56. else
  57. {
  58. return Z_NULL;
  59. }
  60. }
  61. void aws_zfree(void * /* opaque */, void * ptr)
  62. {
  63. if(ptr)
  64. {
  65. char* shiftedMemory = reinterpret_cast<char*>(ptr);
  66. Aws::Free(shiftedMemory - offset);
  67. }
  68. }
  69. #endif // AWS_CUSTOM_MEMORY_MANAGEMENT
  70. #endif // ENABLED_ZLIB_REQUEST_COMPRESSION
  71. iostream_outcome Aws::Client::RequestCompression::compress(std::shared_ptr<Aws::IOStream> input,
  72. const CompressionAlgorithm &algorithm) const
  73. {
  74. #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION
  75. if (algorithm == CompressionAlgorithm::GZIP)
  76. {
  77. // calculating stream size
  78. input->seekg(0, input->end);
  79. size_t streamSize = input->tellg();
  80. input->seekg(0, input->beg);
  81. AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressing request of " << streamSize << " bytes.");
  82. // Preparing output
  83. std::shared_ptr<Aws::IOStream> output = Aws::MakeShared<Aws::StringStream>(AWS_REQUEST_COMPRESSION_ALLOCATION_TAG);
  84. if(!output)
  85. {
  86. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate output while compressing")
  87. return false;
  88. }
  89. // Prepare ZLIB to compress
  90. int ret = Z_NULL;
  91. int flush = Z_NO_FLUSH;
  92. z_stream strm = {};
  93. auto in = Aws::MakeUniqueArray<unsigned char>(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG);
  94. if(!in)
  95. {
  96. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate in buffer while compressing")
  97. return false;
  98. }
  99. auto out = Aws::MakeUniqueArray<unsigned char>(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG);
  100. if(!out)
  101. {
  102. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate out buffer while compressing")
  103. return false;
  104. }
  105. //Preparing allocators
  106. #ifdef USE_AWS_MEMORY_MANAGEMENT
  107. strm.zalloc = (void *(*)(void *, unsigned, unsigned)) aws_zalloc;
  108. strm.zfree = (void (*)(void *, void *)) aws_zfree;
  109. #else
  110. strm.zalloc = Z_NULL;
  111. strm.zfree = Z_NULL;
  112. #endif
  113. strm.opaque = Z_NULL;
  114. const int MAX_WINDOW_GZIP = 31;
  115. const int DEFAULT_MEM_LEVEL_USAGE = 8;
  116. ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WINDOW_GZIP, DEFAULT_MEM_LEVEL_USAGE, Z_DEFAULT_STRATEGY);
  117. if(ret != Z_OK)
  118. {
  119. return false;
  120. }
  121. //Adding one to the stream size counter to account for the EOF marker.
  122. streamSize++;
  123. size_t toRead;
  124. // Compress
  125. do {
  126. toRead = std::min(streamSize, ZLIB_CHUNK);
  127. // Fill the buffer
  128. if (! input->read(reinterpret_cast<char *>(in.get()), toRead))
  129. {
  130. if (input->eof())
  131. {
  132. //Last read need flush when exit loop
  133. flush = Z_FINISH;
  134. }
  135. else {
  136. AWS_LOGSTREAM_ERROR(
  137. AWS_REQUEST_COMPRESSION_LOG_TAG,
  138. "Uncompress request failed to read from stream");
  139. return false;
  140. }
  141. }
  142. streamSize -= toRead; //left to read
  143. strm.avail_in = (flush == Z_FINISH)?toRead-1:toRead; //skip EOF if included
  144. strm.next_in = in.get();
  145. do
  146. {
  147. // Run deflate on buffers
  148. strm.avail_out = ZLIB_CHUNK;
  149. strm.next_out = out.get();
  150. ret = deflate(&strm, flush);
  151. // writing the output
  152. assert(ZLIB_CHUNK >= strm.avail_out);
  153. unsigned output_size = ZLIB_CHUNK - strm.avail_out;
  154. if(! output->write(reinterpret_cast<char *>(out.get()), output_size))
  155. {
  156. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to write to output stream");
  157. return false;
  158. }
  159. } while (strm.avail_out == 0);
  160. assert(strm.avail_in == 0); // All data was read
  161. } while (flush != Z_FINISH);
  162. assert(ret == Z_STREAM_END); // Completed stream
  163. AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request to: " << strm.total_out << " bytes");
  164. deflateEnd(&strm);
  165. return output;
  166. }
  167. else
  168. {
  169. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm));
  170. return false;
  171. }
  172. #else
  173. // If there is no support to compress, always fail calls to this method.
  174. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm));
  175. AWS_UNREFERENCED_PARAM(input); // silencing warning;
  176. return false;
  177. #endif
  178. }
  179. Aws::Utils::Outcome<std::shared_ptr<Aws::IOStream>, bool>
  180. Aws::Client::RequestCompression::uncompress(std::shared_ptr<Aws::IOStream> input, const CompressionAlgorithm &algorithm) const
  181. {
  182. #ifdef ENABLED_ZLIB_REQUEST_COMPRESSION
  183. if (algorithm == CompressionAlgorithm::GZIP)
  184. {
  185. // calculating stream size
  186. input->seekg(0, input->end);
  187. size_t streamSize = input->tellg();
  188. input->seekg(0, input->beg);
  189. AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompressing request of " << streamSize << " bytes.");
  190. // Preparing output
  191. std::shared_ptr<Aws::IOStream> output = Aws::MakeShared<Aws::StringStream>( AWS_REQUEST_COMPRESSION_ALLOCATION_TAG);
  192. if(!output)
  193. {
  194. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate output while uncompressing")
  195. return false;
  196. }
  197. // Prepare ZLIB to uncompress
  198. int ret = Z_NULL;
  199. z_stream strm = {};
  200. auto in = Aws::MakeUniqueArray<unsigned char>(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG);
  201. if(!in)
  202. {
  203. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate in buffer while uncompressing")
  204. return false;
  205. }
  206. auto out = Aws::MakeUniqueArray<unsigned char>(ZLIB_CHUNK, AWS_REQUEST_COMPRESSION_ALLOCATION_TAG);
  207. if(!out)
  208. {
  209. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to allocate out buffer while uncompressing")
  210. return false;
  211. }
  212. //preparing allocation
  213. #ifdef USE_AWS_MEMORY_MANAGEMENT
  214. strm.zalloc = (void *(*)(void *, unsigned, unsigned)) aws_zalloc;
  215. strm.zfree = (void (*)(void *, void *)) aws_zfree;
  216. #else
  217. strm.zalloc = Z_NULL;
  218. strm.zfree = Z_NULL;
  219. #endif
  220. strm.opaque = Z_NULL;
  221. strm.avail_in = 0;
  222. strm.next_in = Z_NULL;
  223. const int MAX_WINDOW_GZIP = 31;
  224. ret = inflateInit2(&strm, MAX_WINDOW_GZIP);
  225. if (ret != Z_OK)
  226. {
  227. return false;
  228. }
  229. //Adding one to the stream size counter to account for the EOF marker.
  230. streamSize++;
  231. size_t toRead;
  232. // Decompress
  233. do {
  234. toRead = (streamSize < ZLIB_CHUNK)?streamSize:ZLIB_CHUNK;
  235. if (toRead < 1) break; // Nothing left to read
  236. // Fill the buffer
  237. if(! input->read(reinterpret_cast<char *>(in.get()), toRead))
  238. {
  239. if (input->eof())
  240. {
  241. //skip passing the EOF to the buffer
  242. toRead--;
  243. }
  244. else
  245. {
  246. AWS_LOGSTREAM_ERROR(
  247. AWS_REQUEST_COMPRESSION_LOG_TAG,
  248. "Compress request failed to read from stream");
  249. return false;
  250. }
  251. }
  252. // Filling input buffer to decompress
  253. strm.avail_in = toRead;
  254. strm.next_in = in.get();
  255. do
  256. {
  257. // Run inflate on buffers
  258. strm.avail_out = ZLIB_CHUNK;
  259. strm.next_out = out.get();
  260. ret = inflate(&strm, Z_NO_FLUSH);
  261. // Catch errors
  262. switch (ret)
  263. {
  264. case Z_NEED_DICT:
  265. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to inflate with code: Z_NEED_DICT");
  266. return false;
  267. case Z_DATA_ERROR:
  268. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to inflate with code: Z_DATA_ERROR");
  269. return false;
  270. case Z_MEM_ERROR:
  271. (void)inflateEnd(&strm);
  272. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Compressed request failed to inflate with code: Z_MEM_ERROR");
  273. return false;
  274. }
  275. // writing the output
  276. unsigned output_size = ZLIB_CHUNK - strm.avail_out;
  277. if(! output->write(reinterpret_cast<char *>(out.get()), output_size)) {
  278. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompressed request failed to write to output stream");
  279. return false;
  280. }
  281. } while (strm.avail_out == 0);
  282. } while (ret != Z_STREAM_END);
  283. // clean up
  284. (void)inflateEnd(&strm);
  285. if (ret == Z_STREAM_END)
  286. {
  287. AWS_LOGSTREAM_TRACE(AWS_REQUEST_COMPRESSION_LOG_TAG, "Decompressed request to: " << strm.total_out << " bytes");
  288. return output;
  289. }
  290. else
  291. {
  292. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Failed to decompress after read input completely");
  293. return false;
  294. }
  295. }
  296. else
  297. {
  298. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm));
  299. return false;
  300. }
  301. #else
  302. // If there is no support to compress, always fail calls to this method.
  303. AWS_LOGSTREAM_ERROR(AWS_REQUEST_COMPRESSION_LOG_TAG, "Uncompress request requested in runtime without support: " << GetCompressionAlgorithmId(algorithm));
  304. AWS_UNREFERENCED_PARAM(input); // silencing warning;
  305. return false;
  306. #endif
  307. }