// // aegis - project change supervisor // Copyright (C) 1999, 2001-2008 Peter Miller // // 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 3 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, see // . // #include #include #include #include #include #ifndef Z_BUFSIZE # ifdef MAXSEG_64K # define Z_BUFSIZE 4096 // minimize memory usage for 16-bit DOS # else # define Z_BUFSIZE 16384 # endif #endif // gzip flag byte #define ASCII_FLAG 0x01 // bit 0 set: file probably ascii text #define HEAD_CRC 0x02 // bit 1 set: header CRC present #define EXTRA_FIELD 0x04 // bit 2 set: extra field present #define ORIG_NAME 0x08 // bit 3 set: original file name present #define COMMENT 0x10 // bit 4 set: file comment present #define RESERVED 0xE0 // bits 5..7: reserved void input_gunzip::zlib_fatal_error(int err) { if (err >= 0) return; sub_context_ty sc; if (stream.msg) sc.var_set_format("ERRNO", "%s (%s)", z_error(err), stream.msg); else sc.var_set_charstar("ERRNO", z_error(err)); sc.var_override("ERRNO"); sc.var_set_string("File_Name", deeper->name()); sc.fatal_intl(i18n("gunzip $filename: $errno")); } input_gunzip::~input_gunzip() { int err = inflateEnd(&stream); if (err < 0) zlib_fatal_error(err); delete [] buf; buf = 0; } input_gunzip::input_gunzip(input &arg) : deeper(arg), z_eof(false), crc(crc32(0L, Z_NULL, 0)), pos(0), buf(new Byte [Z_BUFSIZE]) { 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; // // 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); // // Now read the file header. // read_header(); } long input_gunzip::getLong() { long result = 0; for (int j = 0; j < 4; ++j) { int c = deeper->getch(); if (c < 0) fatal_error("gunzip: premature end of file"); result += c << (j * 8); } return result; } long input_gunzip::read_inner(void *data, size_t len) { if (z_eof) return 0; Bytef *start = (Bytef *)data; // starting point for crc computation stream.next_out = (Bytef *)data; stream.avail_out = len; while (stream.avail_out > 0) { if (stream.avail_in == 0) { stream.next_in = buf; stream.avail_in = deeper->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) { deeper->fatal_error("gunzip: 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. // (The way we wrote it, there shouldn't be much.) // while (stream.avail_in > 0) { stream.avail_in--; deeper->ungetc(stream.next_in[stream.avail_in]); } // // Fall out of the loop. // break; } } // // Calculate the running CRC // long result = stream.next_out - start; crc = crc32(crc, start, (uInt)result); // // Update the file position. // 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. // if (((unsigned long)getLong() & 0xFFFFFFFF) != (crc & 0xFFFFFFFF)) fatal_error("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 (getLong() != pos) fatal_error("gunzip: length mismatch"); } // // Return success (failure always goes via input_format_error, // or zlib_fatal_error). // return result; } long input_gunzip::ftell_inner() { return pos; } nstring input_gunzip::name() { if (filename.empty()) { nstring s = deeper->name(); if (s.ends_with_nocase(".z")) filename = nstring(s.c_str(), s.size() - 2); else if (s.ends_with_nocase(".gz")) filename = nstring(s.c_str(), s.size() - 3); else if (s.ends_with_nocase(".tgz")) { filename = nstring::format("%.*s.tar", (int)s.size() - 4, s.c_str()); } else filename = s; } return filename; } long input_gunzip::length() { // // We have no idea how long the decompressed stream will be. // return -1; } // // 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 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; // stream.avail_in is zero for the first time, but may be non-zero // for concatenated .gz files. // static int gz_magic[2] = {0x1f, 0x8b}; // gzip magic header bool input_gunzip::candidate(input &ip) { // // Check for the magic number. // If it isn't present, assume transparent mode. // int c = ip->getch(); if (c < 0) return false; if (c != gz_magic[0]) { ip->ungetc(c); return false; } c = ip->getch(); if (c < 0) { ip->ungetc(gz_magic[0]); return false; } if (c != gz_magic[1]) { ip->ungetc(c); ip->ungetc(gz_magic[0]); return false; } ip->ungetc(gz_magic[1]); ip->ungetc(gz_magic[0]); return true; } void input_gunzip::read_header() { // // Check for the magic number. // If it isn't present, assume transparent mode. // int c1 = deeper->getch(); int c2 = deeper->getch(); if (c1 != gz_magic[0] || c2 != gz_magic[1]) deeper->fatal_error("gunzip: wrong magic number"); // // Magic number present, now we require the rest of the header // to be present and correctly formed. // int method = deeper->getch(); if (method != Z_DEFLATED) deeper->fatal_error("gunzip: not deflated encoding"); int flags = deeper->getch(); if (flags < 0 || (flags & RESERVED) != 0) deeper->fatal_error("gunzip: unknown flags"); // Discard time, xflags and OS code: for (uInt len = 0; len < 6; len++) if (deeper->getch() < 0) deeper->fatal_error("gunzip: short file"); if (flags & EXTRA_FIELD) { // skip the extra field int elen = deeper->getch(); if (elen < 0) deeper->fatal_error("gunzip: invalid character value"); int c = deeper->getch(); if (c < 0) deeper->fatal_error("gunzip: short file"); elen += (c << 8); while (elen-- > 0) { if (deeper->getch() < 0) deeper->fatal_error("gunzip: short file"); } } if (flags & ORIG_NAME) { // skip the original file name for (;;) { int c = deeper->getch(); if (c < 0) deeper->fatal_error("gunzip: short file"); if (c == 0) break; } } if (flags & COMMENT) { // skip the .gz file comment for (;;) { int c = deeper->getch(); if (c < 0) deeper->fatal_error("gunzip: short file"); if (c == 0) break; } } if (flags & HEAD_CRC) { // skip the header crc for (int len = 0; len < 2; len++) if (deeper->getch() < 0) deeper->fatal_error("gunzip: short file"); } } void input_gunzip::keepalive() { deeper->keepalive(); } input input_gunzip_open(input &deeper) { if (!input_gunzip::candidate(deeper)) { // // If it is not actually a compressed file, // simply return the deeper file. This will // give much better performance. // return deeper; } return new input_gunzip(deeper); } bool input_gunzip::is_remote() const { return deeper->is_remote(); }