/*************************************************
* Gzip Compressor Source File                    *
* (C) 2001 Peter J Jones (pjones@pmade.org)      *
*     2001-2017 Jack Lloyd                       *
*     2007-2017 Markus Wanner                    *
*                                                *
* Based on the comp_zlib module, modified        *
* by Matt Johnston. This is not a complete       *
* gzip implementation (it only handles basic     *
* headers).                                      *
*************************************************/

/* This could be implemented a lot more cleanly if we rely on zlib >= 1.2
 * being available. the Gzip Compressor would just be a subclass of
 * Zlib Compressor, with window_bits+=16 for deflateInit2(), etc */

#include "base.hh"

#include "botan_glue.hh"
#include <cstring>
#include <map>
#include <zlib.h>

#include "gzip.hh"

namespace Botan {

namespace {

/*************************************************
* Allocation Information for Zlib                *
*************************************************/
class Zlib_Alloc_Info
   {
   public:
      std::map<void*, u32bit> current_allocs;
   };

/*************************************************
* Allocation Function for Zlib                   *
*************************************************/
void* zlib_malloc(void* info_ptr, unsigned int n, unsigned int size)
   {
   Zlib_Alloc_Info* info = static_cast<Zlib_Alloc_Info*>(info_ptr);
   void* ptr = calloc(n, size);
   info->current_allocs[ptr] = n * size;
   return ptr;
   }

/*************************************************
* Allocation Function for Zlib                   *
*************************************************/
void zlib_free(void* info_ptr, void* ptr)
   {
   Zlib_Alloc_Info* info = static_cast<Zlib_Alloc_Info*>(info_ptr);
   std::map<void*, u32bit>::const_iterator i = info->current_allocs.find(ptr);
   if(i == info->current_allocs.end())
      throw Invalid_Argument("zlib_free: Got pointer not allocated by us");
   memset(ptr, 0, i->second);
   free(ptr);
   }
}

/*************************************************
* Wrapper Type for Zlib z_stream                 *
*************************************************/
class Zlib_Stream
   {
   public:
      z_stream stream;

      Zlib_Stream()
         {
         std::memset(&stream, 0, sizeof(z_stream));
         stream.zalloc = zlib_malloc;
         stream.zfree = zlib_free;
         stream.opaque = new Zlib_Alloc_Info;
         }
      ~Zlib_Stream()
         {
         Zlib_Alloc_Info* info = static_cast<Zlib_Alloc_Info*>(stream.opaque);
         delete info;
         std::memset(&stream, 0, sizeof(z_stream));
         }
   };

/*************************************************
* Gzip_Compression Constructor                   *
*************************************************/
Gzip_Compression::Gzip_Compression(u32bit l) :
   level((l >= 9) ? 9 : l), buffer(DEFAULT_BUFFERSIZE),
   pipe(new Hash_Filter("CRC32")), count( 0 )
   {

   zlib = new Zlib_Stream;
   // window_bits == -15 relies on an undocumented feature of zlib, which
   // supresses the zlib header on the message. We need that since gzip doesn't
   // use this header.  The feature been confirmed to exist in 1.1.4, which
   // everyone should be using due to security fixes. In later versions this
   // feature is documented, along with the ability to do proper gzip output
   // (that would be a nicer way to do things, but will have to wait until 1.2
   // becomes more widespread).
   // The other settings are the defaults that deflateInit() gives
   if(deflateInit2(&(zlib->stream), level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK)
      {
      delete zlib; zlib = 0;
      throw std::bad_alloc();
      }
   }

/*************************************************
* Gzip_Compression Destructor                    *
*************************************************/
Gzip_Compression::~Gzip_Compression()
   {
   deflateEnd(&(zlib->stream));
   delete zlib; zlib = 0;
   }

/*************************************************
* Start Compressing with Gzip                    *
*************************************************/
void Gzip_Compression::start_msg()
   {
   clear();
   put_header();
   pipe.start_msg();
   count = 0;
   }

/*************************************************
* Compress Input with Gzip                       *
*************************************************/
void Gzip_Compression::write(const byte input[], filter_length_t length)
   {

   count += length;
   pipe.write(input, length);

   zlib->stream.next_in = (Bytef*)input;
   zlib->stream.avail_in = length;

   while(zlib->stream.avail_in != 0)
      {
      zlib->stream.next_out = (Bytef*)&buffer[0];
      zlib->stream.avail_out = buffer.size();
      int rc = deflate(&(zlib->stream), Z_NO_FLUSH);
      if (rc != Z_OK && rc != Z_STREAM_END)
         throw Invalid_State("Internal error in Gzip_Compression deflate.");
      send(&buffer[0], buffer.size() - zlib->stream.avail_out);
      }
   }

/*************************************************
* Finish Compressing with Gzip                   *
*************************************************/
void Gzip_Compression::end_msg()
   {
   zlib->stream.next_in = 0;
   zlib->stream.avail_in = 0;

   int rc = Z_OK;
   while(rc != Z_STREAM_END)
      {
      zlib->stream.next_out = (Bytef*)&buffer[0];
      zlib->stream.avail_out = buffer.size();
      rc = deflate(&(zlib->stream), Z_FINISH);
      if (rc != Z_OK && rc != Z_STREAM_END)
         throw Invalid_State("Internal error in Gzip_Compression finishing deflate.");
      send(&buffer[0], buffer.size() - zlib->stream.avail_out);
      }

   pipe.end_msg();
   put_footer();
   clear();
   }

/*************************************************
* Clean up Compression Context                   *
*************************************************/
void Gzip_Compression::clear()
   {
   deflateReset(&(zlib->stream));
   }

/*************************************************
* Put a basic gzip header at the beginning       *
*************************************************/
void Gzip_Compression::put_header()
   {
   send(GZIP::GZIP_HEADER, sizeof(GZIP::GZIP_HEADER));
   }

/*************************************************
* Put a gzip footer at the end                   *
*************************************************/
void Gzip_Compression::put_footer()
   {
   // 4 byte CRC32, and 4 byte length field
   secure_byte_vector buf(4);
   secure_byte_vector tmpbuf(4);

   size_t bytes_read = pipe.read(&tmpbuf[0], tmpbuf.size(), Pipe::LAST_MESSAGE);
   if (bytes_read != tmpbuf.size())
      throw Encoding_Error("Gzip_Compression: Failed reading from pipe");

   // CRC32 is the reverse order to what gzip expects.
   for (int i = 0; i < 4; i++)
      buf[3-i] = tmpbuf[i];

   send(&buf[0], buf.size());

   // Length - LSB first
   for (int i = 0; i < 4; i++)
      buf[3-i] = get_byte(i, count);

   send(&buf[0], buf.size());
   }

/*************************************************
* Gzip_Decompression Constructor                 *
*************************************************/
Gzip_Decompression::Gzip_Decompression() : buffer(DEFAULT_BUFFERSIZE),
   no_writes(true), pipe(new Hash_Filter("CRC32")), footer(0)
   {
   if (DEFAULT_BUFFERSIZE < sizeof(GZIP::GZIP_HEADER))
      throw Decoding_Error("DEFAULT_BUFFERSIZE is too small");

   zlib = new Zlib_Stream;

   // window_bits == -15 is raw zlib (no header) - see comment
   // above about deflateInit2
   if(inflateInit2(&(zlib->stream), -15) != Z_OK)
      {
      delete zlib; zlib = 0;
      throw std::bad_alloc();
      }
   }

/*************************************************
* Gzip_Decompression Destructor                  *
*************************************************/
Gzip_Decompression::~Gzip_Decompression()
   {
      inflateEnd(&(zlib->stream));
      delete zlib; zlib = 0;
   }

/*************************************************
* Start Decompressing with Gzip                  *
*************************************************/
void Gzip_Decompression::start_msg()
   {
   if (!no_writes)
      throw Decoding_Error("Gzip_Decompression: start_msg after already writing");

   pipe.start_msg();
   datacount = 0;
   pos = 0;
   in_footer = false;
   }

/*************************************************
* Decompress Input with Gzip                     *
*************************************************/
void Gzip_Decompression::write(const byte input[], filter_length_t length)
   {
   if(length) no_writes = false;

   // If we're in the footer, take what we need, then go to the next block
   if (in_footer)
      {
         u32bit eat_len = eat_footer(input, length);
         input += eat_len;
         length -= eat_len;
      }

   if (length == 0)
     return;

   // Check the gzip header
   if (pos < sizeof(GZIP::GZIP_HEADER))
      {
      filter_length_t eatlen = std::min(length,
         (filter_length_t) sizeof(GZIP::GZIP_HEADER) - pos);

      // Check the magic header, compression method and flags.
      if (pos < GZIP::HEADER_POS_MTIME)
         {
         filter_length_t cmplen = std::min(length,
            (filter_length_t) GZIP::HEADER_POS_MTIME - pos);
         if (std::memcmp(input, &GZIP::GZIP_HEADER[pos], cmplen) != 0)
            throw Decoding_Error(
               "Gzip_Decompression: Data integrity error in header");
         }

      // Monotone ignores the modification time (only since version 1.2
      // though, it resulted in the above data integrity error, before).

      // Check the extra flag, if in the range.
      if (pos <= GZIP::HEADER_POS_EXTRA_FLAGS
             && pos + eatlen > GZIP::HEADER_POS_EXTRA_FLAGS
             && input[GZIP::HEADER_POS_EXTRA_FLAGS - pos] != 0)
          throw Decoding_Error(
             "Gzip_Decompression: Extra flags not supported");

      // The last byte is the OS flag - we don't care about that, either.

      input += eatlen;
      length -= eatlen;
      pos += eatlen;
      }

   pos += length;

   zlib->stream.next_in = (Bytef*)input;
   zlib->stream.avail_in = length;

   while(zlib->stream.avail_in != 0)
      {
      zlib->stream.next_out = (Bytef*)&buffer[0];
      zlib->stream.avail_out = buffer.size();

      int rc = inflate(&(zlib->stream), Z_SYNC_FLUSH);
      if(rc != Z_OK && rc != Z_STREAM_END)
         {
         if(rc == Z_DATA_ERROR)
            throw Decoding_Error("Gzip_Decompression: Data integrity error");
         if(rc == Z_NEED_DICT)
            throw Decoding_Error("Gzip_Decompression: Need preset dictionary");
         if(rc == Z_MEM_ERROR)
            throw std::bad_alloc();
         throw Decoding_Error("Gzip_Decompression: Unknown decompress error");
         }
      send(&buffer[0], buffer.size() - zlib->stream.avail_out);
      pipe.write(&buffer[0], buffer.size() - zlib->stream.avail_out);
      datacount += buffer.size() - zlib->stream.avail_out;

      // Reached the end - we now need to check the footer
      if(rc == Z_STREAM_END)
         {
         u32bit read_from_block = length - zlib->stream.avail_in;
         u32bit eat_len = eat_footer((Bytef*)input + read_from_block, zlib->stream.avail_in);
         read_from_block += eat_len;
         input += read_from_block;
         length -= read_from_block;
         zlib->stream.next_in = (Bytef*)input;
         zlib->stream.avail_in = length;
         }
      }
   }

/*************************************************
* Store the footer bytes                         *
*************************************************/
u32bit Gzip_Decompression::eat_footer(const byte input[], u32bit length)
   {
      if (footer.size() >= GZIP::FOOTER_LENGTH)
         throw Decoding_Error("Gzip_Decompression: Data integrity error in footer");

#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,11)
      size_t eat_len = std::min(GZIP::FOOTER_LENGTH-footer.size(),
                                static_cast<size_t>(length));
      footer += std::make_pair(input, eat_len);
#else
      u32bit eat_len = std::min(GZIP::FOOTER_LENGTH-footer.size(), length);
      footer.append(input, eat_len);
#endif

      if (footer.size() == GZIP::FOOTER_LENGTH)
         {
         check_footer();
         clear();
         }

         return eat_len;
   }

/*************************************************
* Check the gzip footer                          *
*************************************************/
void Gzip_Decompression::check_footer()
   {
   if (footer.size() != GZIP::FOOTER_LENGTH)
      throw Decoding_Error("Gzip_Decompression: Error finalizing decompression");

   pipe.end_msg();

   // 4 byte CRC32, and 4 byte length field
   secure_byte_vector buf(4);
   secure_byte_vector tmpbuf(4);
   size_t bytes_read = pipe.read(&tmpbuf[0], tmpbuf.size(), Pipe::LAST_MESSAGE);
   if (bytes_read != tmpbuf.size())
      throw Decoding_Error("Gzip_Decompression: Failed reading from pipe");

  // CRC32 is the reverse order to what gzip expects.
  for (int i = 0; i < 4; i++)
     buf[3-i] = tmpbuf[i];

#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,0)
  tmpbuf.assign(footer.begin(), footer.begin() + 4);
#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,11)
  tmpbuf.resize(4);
  tmpbuf.copy(footer.begin(), 4);
#else
  tmpbuf.set(footer.begin(), 4);
#endif
  if (buf != tmpbuf)
      throw Decoding_Error("Gzip_Decompression: Data integrity error - CRC32 error");

   // Check the length matches - it is encoded LSB-first
   for (int i = 0; i < 4; i++)
      {
      if (footer.begin()[GZIP::FOOTER_LENGTH-1-i] != get_byte(i, datacount))
         throw Decoding_Error("Gzip_Decompression: Data integrity error - incorrect length");
      }

   }

/*************************************************
* Finish Decompressing with Gzip                 *
*************************************************/
void Gzip_Decompression::end_msg()
   {

   // All messages should end with a footer, and when a footer is successfully
   // read, clear() will reset no_writes
   if(no_writes) return;

   throw Decoding_Error("Gzip_Decompression: didn't find footer");

   }

/*************************************************
* Clean up Decompression Context                 *
*************************************************/
void Gzip_Decompression::clear()
   {
   no_writes = true;
   inflateReset(&(zlib->stream));

#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,11)
   footer.clear();
#else
   footer.destroy();
#endif
   pos = 0;
   datacount = 0;
   }

}

// Local Variables:
// mode: C++
// fill-column: 76
// c-file-style: "gnu"
// indent-tabs-mode: nil
// End:
// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
