Logo Search packages:      
Sourcecode: tardy version File versions  Download package

gunzip.cc

//
//    tardy - a tar post-processor
//    Copyright (C) 2003, 2004 Peter Miller;
//    All rights reserved.
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
//
// MANIFEST: functions to manipulate gunzips
//

#pragma implementation "tar_input_filter_gunzip"

#include <ac/string.h>
#include <tar/input/filter/gunzip.h>


00029 tar_input_filter_gunzip::~tar_input_filter_gunzip()
{
    delete buffered_data;
    delete buf;
}


00036 tar_input_filter_gunzip::tar_input_filter_gunzip(tar_input *arg) :
    tar_input_filter(arg),
    pass_through(true),
    z_eof(true),
    buf(0),
    buffered_data(0),
    buffered_data_pos(0),
    buffered_data_size(0),
    buffered_data_size_max(0),
    inflated_file_pos(0),
    inflated_file_length(0),
    deflated_file_pos(0),
    deflated_file_length(0)
{
}


void
00054 tar_input_filter_gunzip::zlib_fatal_error(int err)
{
    if (err < 0)
      fatal("gunzip: %s (%s)", z_error(err), stream.msg); 
}



int
00063 tar_input_filter_gunzip::buffered_getc()
{
    unsigned char c;
    int nbytes = buffered_read(&c, 1);
    if (nbytes < 1)
      return -1;
    return c;
}


int
00074 tar_input_filter_gunzip::buffered_read(void *data, int len)
{
    if (buffered_data_pos < buffered_data_size)
    {
      int nbytes = buffered_data_size - buffered_data_pos;
      if (nbytes > len)
          nbytes = len;
      memcpy(data, buffered_data + buffered_data_pos, nbytes);
      buffered_data_pos += nbytes;
      deflated_file_pos += nbytes;
      return nbytes;
    }
    if (deflated_file_pos >= deflated_file_length)
      return 0;
    int nbytes = deflated_file_length - deflated_file_pos;
    if (nbytes > len)
      nbytes = len;
    int nbytes2 = tar_input_filter::read_data(data, nbytes);
    deflated_file_pos += nbytes2;
    return nbytes2;
}


void
00098 tar_input_filter_gunzip::buffered_unread(const void *data, size_t len)
{
    deflated_file_pos -= len;

    /*
     * If we need a bigger buffer, allocate it from the heap
     * and copy the new and old data into it.
     */
    size_t buffered_nbytes = buffered_data_size - buffered_data_pos;
    if (len + buffered_nbytes > buffered_data_size_max)
    {
      int nbytes = len + buffered_nbytes;
      unsigned char *new_buffered_data = new unsigned char[nbytes];
      memcpy(new_buffered_data, data, len);
      memcpy
      (
          new_buffered_data + len,
          buffered_data + buffered_data_pos,
          buffered_nbytes
      );
      delete buffered_data;
      buffered_data = new_buffered_data;
      new_buffered_data = 0;
      buffered_data_pos = 0;
      buffered_data_size = nbytes;
      buffered_data_size_max = nbytes;
      return;
    }

    /*
     * See if we need to stuffle the data up to make it fit into the
     * existing buffer.
     */
    if (len > buffered_data_pos)
    {
      memmove
      (
          buffered_data + buffered_data_pos,
          buffered_data + buffered_data_size_max - buffered_nbytes,
          buffered_nbytes
      );
      buffered_data_size = buffered_data_size_max;
      buffered_data_pos = buffered_data_size_max - buffered_nbytes;
    }

    /*
     * Add the data onto the *front* of the buffer.
     */
    memcpy(buffered_data + buffered_data_pos - len, data, len);
    buffered_data_pos -= len;
}


unsigned long
00152 tar_input_filter_gunzip::get_long()
{
    unsigned char buffer[4];
    unsigned char *bp = buffer;
    size_t len = sizeof(buffer);
    for (;;)
    {
      //
      // weird block boundaries can mess this up.
      //
      size_t nbytes = buffered_read(bp, len);
      if (nbytes == 0)
          fatal("short read");
      len -= nbytes;
      if (!len)
          break;
      bp += nbytes;
    }
    return
      (
          (unsigned long)buffer[0]
      |
          ((unsigned long)buffer[1] << 8)
      |
          ((unsigned long)buffer[2] << 16)
      |
          ((unsigned long)buffer[3] << 24)
      );
}


/*
 * Check the gzip header of a gz_stream opened for reading. Set the
 * stream mode to transparent if the gzip magic header is not present;
 * set this->err to Z_DATA_ERROR if the magic header is present but the
 * rest of the header is incorrect.
 *
 * IN assertion: the stream this has already been created sucessfully;
 * this->stream.avail_in is zero for the first time, but may be non-zero
 * for concatenated .gz files.
 */

int
00195 tar_input_filter_gunzip::read_header(tar_header &arg)
{
    if (0 == tar_input_filter::read_header(arg))
      return 0;
    pass_through = (arg.type != tar_header::type_normal_gzipped);
    tar_input_filter::read_header_padding();
    if (pass_through)
      return 1;
    arg.type = tar_header::type_normal;

    /*
     * Check for the magic number.
     * If it isn't present, assume transparent mode.
     */
    deflated_file_pos = 0;
    deflated_file_length = arg.size;
    static unsigned char gz_magic[] = { 0x1F, 0x8B };
    unsigned char magic[sizeof(gz_magic)];
    if
    (
      buffered_read(magic, sizeof(magic)) != sizeof(magic)
    ||
      0 != memcmp(magic, gz_magic, sizeof(magic))
    )
      fatal("gunzip: bad magic number (%02X %02X)", magic[0], magic[1]);

    /*
     * Magic number present, now we require the rest of the header
     * to be present and correctly formed.
     */
    int method = buffered_getc();
    if (method != Z_DEFLATED)
      fatal("gunzip: not deflated encoding");
    int flags = buffered_getc();
    if (flags < 0 || (flags & Z_FLAG_RESERVED) != 0)
      fatal("gunzip: unknown flags");

    /* Discard time, xflags and OS code: */
    for (int oslen = 0; oslen < 6; ++oslen)
      if (buffered_getc() < 0)
          fatal("gunzip: short file");

    if (flags & Z_FLAG_EXTRA_FIELD)
    {
      /* low byte of len must be 4 */
      int len = buffered_getc();
      if (len < 0)
          goto bad_extra;
      if (len != 4)
          goto bad_extra;

      /* high byte of len must be zero */
      int c = buffered_getc();
      if (c < 0)
          goto bad_extra;
      if (c != 0)
          goto bad_extra;

      inflated_file_length = get_long();
    }
    else
    {
      bad_extra:
      fatal("gunzip: extra field required");
    }

    if (flags & Z_FLAG_ORIG_NAME)
    {
      /* skip the original file name */
      for (;;)
      {
          int c = buffered_getc();
          if (c < 0)
                  fatal("gunzip: short file");
          if (c == 0)
                  break;
      }
    }
    if (flags & Z_FLAG_COMMENT)
    {
      /* skip the .gz file comment */
      for (;;)
      {
          int c = buffered_getc();
          if (c < 0)
                  fatal("gunzip: short file");
          if (c == 0)
                  break;
      }
    }
    if (flags & Z_FLAG_HEAD_CRC)
    {
      /* skip the header crc */
      for (int crclen = 0; crclen < 2; ++crclen)
          if (buffered_getc() < 0)
                  fatal("gunzip: short file");
    }

    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;
    stream.opaque = (voidpf)0;
    stream.next_in = Z_NULL;
    stream.avail_in = 0;
    stream.next_out = Z_NULL;
    stream.avail_out = 0;
    if (!buf)
      buf = new Byte[Z_BUFSIZE];
    crc = crc32(0L, Z_NULL, 0);
    inflated_file_pos = 0;
    z_eof = false;
    buffered_data_pos = 0;
    buffered_data_size = 0;

    /*
     * windowBits is passed < 0 to tell that there is no zlib header.
     * Note that in this case inflate *requires* an extra "dummy" byte
     * after the compressed stream in order to complete decompression
     * and return Z_STREAM_END. Here the gzip CRC32 ensures that 4
     * bytes are present after the compressed stream.
     */
    int err = inflateInit2(&stream, -MAX_WBITS);
    if (err < 0)
      zlib_fatal_error(err);

    /*
     * Re-write the header to reflect the decompressed file data.
     */
    arg.size = inflated_file_length;

    /*
     * Report success.
     */
    return 1;
}


void
00332 tar_input_filter_gunzip::read_header_padding()
{
    // already done.
}


int
00339 tar_input_filter_gunzip::read_data(void *data, int len)
{
    if (pass_through)
      return tar_input_filter::read_data(data, len);

    if (z_eof)
      return 0;

    stream.next_out = (Bytef *)data;
    stream.avail_out = len;

    while (stream.avail_out > 0)
    {
      if (stream.avail_in == 0)
      {
          // assert(buf);
          stream.next_in = buf;
          stream.avail_in = buffered_read(buf, Z_BUFSIZE);

          /*
           * There should always be something left on the
           * input, because we have the CRC and Length
           * to follow.  Fatal error if not.
           */
          if (stream.avail_in <= 0)
            fatal("premature end of file");
      }
      int err = inflate(&stream, Z_PARTIAL_FLUSH);
      if (err < 0)
          zlib_fatal_error(err);
      if (err == Z_STREAM_END)
      {
          z_eof = true;

          /*
           * Push back the unused portion of the input stream.
           * (There shouldn't be much.)
           */
          if (stream.avail_in > 0)
            buffered_unread(stream.next_in, stream.avail_in);

          /*
           * Fall out of the loop.
           */
          break;
      }
    }

    /*
     * Calculate the running CRC
     */
    long result = stream.next_out - (Bytef *)data;
    crc = crc32(crc, (Bytef *)data, (uInt)result);

    /*
     * Update the file position.
     */
    inflated_file_pos += result;

    /*
     * At end-of-file we need to do some checking.
     */
    if (z_eof)
    {
      /*
       * Check CRC
       *
       * Watch out for 64-bit machines.  This is what
       * those aparrently redundant 0xFFFFFFFF are for.
       */
      unsigned long icrc = get_long();
      if ((icrc & 0xFFFFFFFF) != (crc & 0xFFFFFFFF))
          fatal("gunzip: checksum mismatch (%lX != %lX)", icrc, crc);

      /*
       * The uncompressed length here may be different
       * from pos in case of concatenated .gz
       * files.  But we don't write them that way,
       * so give an error if it happens.
       *
       * We shouldn't have 64-bit problems in this case.
       */
      unsigned long ilen = get_long();
      if (ilen != inflated_file_pos)
          fatal("gunzip: length mismatch (%ld != %ld)", ilen, (long)inflated_file_pos);
      if (inflated_file_pos != inflated_file_length)
          fatal("inflated length mismatch");
      if (deflated_file_pos != deflated_file_length)
          fatal("deflated length mismatch");

      /*
       * Finished with this stream.
       */
      int err = inflateEnd(&stream);
      if (err < 0)
          zlib_fatal_error(err);
    }

    /*
     * Return success (failure always goes via input_format_error,
     * or zlib_fatal_error).
     */
    return result;
}


void
00446 tar_input_filter_gunzip::read_data_padding()
{
    if (pass_through || z_eof)
    {
      tar_input_filter::read_data_padding();
      return;
    }

    /*
     * We should be exactly at the end of input.
     * So we need to read the length and CRC fields to check.
     * To do this, we need a dummy buffer for decompressed data.
     */
    char data[4];
    stream.next_out = (Bytef *)data;
    stream.avail_out = sizeof(data);

    if (stream.avail_in == 0)
    {
      // assert(buf);
      stream.next_in = buf;
      stream.avail_in = buffered_read(buf, Z_BUFSIZE);

      /*
       * There should always be something left on the
       * input, because we have the CRC and Length
       * to follow.  Fatal error if not.
       */
      if (stream.avail_in <= 0)
          fatal("premature end of file");
    }
    int err = inflate(&stream, Z_PARTIAL_FLUSH);
    if (err < 0)
      zlib_fatal_error(err);
    if (err != Z_STREAM_END || stream.next_out != (Bytef *)data)
      fatal("gunzip: too much deflated file");

    /*
     * Check CRC
     *
     * Watch out for 64-bit machines.  This is what
     * those aparrently redundant 0xFFFFFFFF are for.
     */
    if ((get_long() & 0xFFFFFFFF) != (crc & 0xFFFFFFFF))
      fatal("gunzip: checksum mismatch");

    /*
     * The uncompressed length here may be different
     * from pos in case of concatenated .gz
     * files.  But we don't write them that way,
     * so give an error if it happens.
     *
     * We shouldn't have 64-bit problems in this case.
     */
    if (get_long() != inflated_file_pos)
      fatal("gunzip: length mismatch");
    if (inflated_file_pos != inflated_file_length)
      fatal("inflated length mismatch");
    if (deflated_file_pos != deflated_file_length)
      fatal("deflated length mismatch");

    /*
     * Finished with this stream.
     */
    err = inflateEnd(&stream);
    if (err < 0)
      zlib_fatal_error(err);

    /*
     * Now read the deeper padding
     */
    tar_input_filter::read_data_padding();
}

Generated by  Doxygen 1.6.0   Back to index