/** * Generate the fixed huffman trees as described in the rfc * * @param huffmanCode - pointer to where the code shall be inserted * @param huffmanCodeLength * @param distHuffCode * @param distHuffCodeLength */ public static void genFixedTree( int[] huffmanCode, byte[] huffmanCodeLength, int[] distHuffCode, byte[] distHuffCodeLength) { int i; // huffmanCodes for (i = 0; i <= 143; i++) { huffmanCode[i] = 48 + i; // 48==00110000 huffmanCodeLength[i] = 8; } for (i = 144; i <= 255; i++) { huffmanCode[i] = 400 + i - 144; // 400==110010000 huffmanCodeLength[i] = 9; } for (i = 256; i <= 279; i++) { huffmanCode[i] = i - 256; // 0==0 huffmanCodeLength[i] = 7; } for (i = 280; i < 286; i++) { huffmanCode[i] = 192 + i - 280; // 192==11000000 huffmanCodeLength[i] = 8; } // reverse all: ZipHelper.revHuffTree(huffmanCode, huffmanCodeLength); // distHuffCode for non fixed DISTANCE tree for (int j = 0; j < distHuffCode.length; j++) { distHuffCode[j] = j; distHuffCodeLength[j] = 5; } // reverse all: ZipHelper.revHuffTree(distHuffCode, distHuffCodeLength); }
/** * Creates an input stream capable of GZIP and Deflate. * * @param inputStream the stream that contains the compressed data. * @param size the size of the internally used buffer * @param compressionType TYPE_GZIP or TYPE_DEFLATE * @param hash set true for data checking, set false for speed reading * @throws IOException when the header of a GZIP stream cannot be skipped * @see #TYPE_DEFLATE * @see #TYPE_GZIP */ public GZipInputStream(InputStream inputStream, int size, int compressionType, boolean hash) throws IOException { // System.out.println("creating GZipInputStream with hash=" + hash); this.inStream = inputStream; this.inStreamEnded = false; this.status = GZipInputStream.EXPECTING_HEADER; this.hash = hash; this.type = compressionType; this.smallCodeBuffer = new long[2]; this.huffmanTree = new short[288 * 4]; this.distHuffTree = new short[32 * 4]; this.buffsize = size; this.outBuff = new byte[size + 300]; // #debug System.out.println("creating outbuff, size=" + size + ", actual lenth=" + this.outBuff.length); if (this.type == GZipInputStream.TYPE_GZIP) { ZipHelper.skipheader(inputStream); } this.crc32 = 0; }
private void processHeader() throws IOException { // #debug System.out.println("processHeader()"); int val; int HLIT; // number of miniHuff fragments int HDIST; // number of distance codes (should somehow lead to the same) int HCLEN; // number of length codes int[] distHuffCode = new int[30]; int[] distHuffData = new int[30]; byte[] distHuffCodeLength = new byte[30]; int[] huffmanCode = new int[286]; // this contains the codes according to the huffman tree/mapping int[] huffmanData = new int[286]; // this contains the data referring to the code. byte[] huffmanCodeLength = new byte[286]; this.BFINAL = (popSmallBuffer(1) == 1); this.BTYPE = popSmallBuffer(2); if (this.BTYPE == 3) { throw new IllegalArgumentException(); } else if (this.BTYPE == 1) { // System.out.println(this.allPocessed + ": fixed tree"); ZipHelper.genFixedTree(huffmanCode, huffmanCodeLength, distHuffCode, distHuffCodeLength); for (int i = 0; i < 286; i++) { huffmanData[i] = i; } for (int i = 0; i < 30; i++) { distHuffData[i] = i; } // convert literal table to tree ZipHelper.convertTable2Tree(huffmanCode, huffmanCodeLength, huffmanData, this.huffmanTree); // convert distance table to tree ZipHelper.convertTable2Tree( distHuffCode, distHuffCodeLength, distHuffData, this.distHuffTree); } else if (this.BTYPE == 2) { // System.out.println(this.allPocessed + ": dynamic tree"); // read/parse the length codes HLIT = popSmallBuffer(5); HDIST = popSmallBuffer(5); HCLEN = popSmallBuffer(4); // miniTree int[] miniHuffData = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; int[] seq = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; byte[] miniHuffCodeLength = new byte[19]; int[] miniHuffCode = new int[19]; // read the miniHuffCodeLength for (int i = 0; i < HCLEN + 4; i++) { miniHuffCodeLength[miniHuffData[i]] = (byte) popSmallBuffer(3); } ZipHelper.genHuffTree(miniHuffCode, miniHuffCodeLength); ZipHelper.revHuffTree(miniHuffCode, miniHuffCodeLength); short[] miniTree = new short[19 * 4]; ZipHelper.convertTable2Tree(miniHuffCode, miniHuffCodeLength, seq, miniTree); // parse the length code for the normal Tree and the distance Tree using the miniTree for (int i = 0; i < huffmanCodeLength.length; i++) { huffmanCodeLength[i] = 0; } for (int i = 0; i < distHuffCodeLength.length; i++) { distHuffCodeLength[i] = 0; } byte lastVal = 0; for (int j = 0; j < HLIT + 257 + HDIST + 1; ) { if (this.smallCodeBuffer[1] < 15) { refillSmallCodeBuffer(); } val = ZipHelper.deHuffNext(this.smallCodeBuffer, miniTree); // data if (val < 16) { lastVal = (byte) val; val = 1; } else { // repeat code if (val == 16) { val = popSmallBuffer(2) + 3; } else if (val == 17) { lastVal = 0; val = popSmallBuffer(3) + 3; } else if (val == 18) { lastVal = 0; val = popSmallBuffer(7) + 11; } } // fill the value in for (int k = 0; k < val; k++, j++) { if (j < HLIT + 257) { huffmanCodeLength[j] = lastVal; } else { distHuffCodeLength[j - (HLIT + 257)] = lastVal; } } } // final tree: fill this.huffmanCode this.huffmanData ZipHelper.genHuffTree(huffmanCode, huffmanCodeLength); for (int i = 0; i < huffmanData.length; i++) { huffmanData[i] = i; } // converting literal table to tree ZipHelper.revHuffTree(huffmanCode, huffmanCodeLength); ZipHelper.convertTable2Tree(huffmanCode, huffmanCodeLength, huffmanData, this.huffmanTree); // Distance Tree // distHuffData for non fixed distance tree // this.distHuffCodeLength is read together with this.huffmanCodeLength for (int j = 0; j < distHuffCode.length; j++) { distHuffData[j] = j; } ZipHelper.genHuffTree(distHuffCode, distHuffCodeLength); ZipHelper.revHuffTree(distHuffCode, distHuffCodeLength); ZipHelper.convertTable2Tree( distHuffCode, distHuffCodeLength, distHuffData, this.distHuffTree); } else { // just skip bits up to the next boundary popSmallBuffer(this.smallCodeBuffer[1] & 7); // &7 == %8 // read and check the header this.B0len = popSmallBuffer(8) | popSmallBuffer(8) << 8; if (this.smallCodeBuffer[1] < 15) { refillSmallCodeBuffer(); } if (this.B0len + (popSmallBuffer(8) | popSmallBuffer(8) << 8) != 0xffff) { // Error: the header for the uncompressed is wrong; throw new IOException("3"); } // clear the buffer while (this.smallCodeBuffer[1] != 0 && this.B0len > 0) { val = popSmallBuffer(8); this.window[this.pProcessed] = (byte) val; this.pProcessed = (this.pProcessed + 1) & 32767; // == % (1<<15); this.outBuff[this.outEnd] = (byte) val; this.outEnd++; this.B0len--; } } this.status = GZipInputStream.EXPECTING_DATA; distHuffCode = null; distHuffData = null; distHuffCodeLength = null; huffmanCodeLength = null; huffmanCode = null; huffmanData = null; }
/** * This function fills the internal outputbuffer reading data form the this.inputStream and * inflating it. */ private void inflate() throws IOException { // #debug System.out.println("inflate - outbuff.length=" + this.outBuff.length); int val = 0; int rep; int rem; int cLen; int cPos; int aPos; int copyBytes; byte[] myWindow = this.window; byte[] myOutBuff = this.outBuff; // shift outputbuffer to the beginning System.arraycopy(myOutBuff, this.outStart, myOutBuff, 0, this.outEnd - this.outStart); this.outEnd -= this.outStart; this.outStart = 0; this.lastEnd = this.outEnd; if (this.B0len == 0) { if (this.smallCodeBuffer[1] < 15) { refillSmallCodeBuffer(); } } // and fill it by parsing the input-stream while ((myOutBuff.length - this.outEnd > 300 && (this.smallCodeBuffer[1] > 0 || this.B0len > 0)) && this.status != GZipInputStream.FINISHED) { // parse block header if (this.status == GZipInputStream.EXPECTING_HEADER) { processHeader(); } // deal with the data if (this.status == GZipInputStream.EXPECTING_DATA) { // just copy data if (this.BTYPE == 0) { if (this.B0len > 0) { // copy directly copyBytes = (myOutBuff.length - this.outEnd) > this.B0len ? this.B0len : myOutBuff.length - this.outEnd; // at most myOutBuff.length-this.outEnd copyBytes = this.inStream.read(myOutBuff, this.outEnd, copyBytes); copyIntoWindow(this.pProcessed, copyBytes, myOutBuff, this.outEnd); this.outEnd += copyBytes; this.pProcessed = (this.pProcessed + copyBytes) & 32767; // % (1<<15); this.B0len -= copyBytes; } else { if (this.BFINAL) { this.status = GZipInputStream.EXPECTING_CHECK; } else { this.status = GZipInputStream.EXPECTING_HEADER; } if (this.smallCodeBuffer[1] < 15) { refillSmallCodeBuffer(); } } } // inflate else { if (this.smallCodeBuffer[1] < 15) { refillSmallCodeBuffer(); } val = ZipHelper.deHuffNext(this.smallCodeBuffer, this.huffmanTree); // normal single byte if (val < 256) { // this.window[this.pProcessed]=(byte)val; myWindow[this.pProcessed] = (byte) val; this.pProcessed = (this.pProcessed + 1) & 32767; // % (1<<15); myOutBuff[this.outEnd] = (byte) val; this.outEnd++; } // copy: pointer + len else if (val != 256) { if (val > 285) { // ERROR: data > 285 was decoded. This is invalid data.; throw new IOException("1"); } // parse the pointer // cLen // read some bits cLen = popSmallBuffer(ZipHelper.LENGTH_CODE[(val - 257) << 1]); // add the offset cLen += ZipHelper.LENGTH_CODE[((val - 257) << 1) + 1]; // cPos // resolve the index if (this.smallCodeBuffer[1] < 15) { refillSmallCodeBuffer(); } // DISTANCE val = ZipHelper.deHuffNext(this.smallCodeBuffer, this.distHuffTree); // resolve the value cPos = popSmallBuffer(ZipHelper.DISTANCE_CODE[val << 1]); cPos += ZipHelper.DISTANCE_CODE[(val << 1) + 1]; // process the pointer (the data does always fit) // the absolute starting position for copying data aPos = this.pProcessed - cPos; aPos += aPos < 0 ? myWindow.length : 0; // how often will the data be copied? rep = cLen / cPos; rem = cLen - cPos * rep; for (int j = 0; j < rep; j++) { // cPos < cLen copyFromWindow(aPos, cPos, myOutBuff, this.outEnd); copyIntoWindow(this.pProcessed, cPos, myOutBuff, this.outEnd); this.outEnd += cPos; this.pProcessed = (this.pProcessed + cPos) & 32767; // % (1<<15); } // cPos > cLen OR remainder copyFromWindow( aPos, rem, myOutBuff, this.outEnd); // from window into buffer, and again into window copyIntoWindow(this.pProcessed, rem, myOutBuff, this.outEnd); this.outEnd += rem; this.pProcessed = (this.pProcessed + rem) & 32767; // % (1<<15); } // val=256 else { if (this.BFINAL) { this.status = GZipInputStream.EXPECTING_CHECK; } else { this.status = GZipInputStream.EXPECTING_HEADER; } } if (this.smallCodeBuffer[1] < 15) { refillSmallCodeBuffer(); } } } if (this.status == GZipInputStream.EXPECTING_CHECK) { // System.out.println(this.allPocessed + " data check"); this.status = GZipInputStream.FINISHED; this.allPocessed = (this.allPocessed + this.outEnd - this.lastEnd) & 4294967295L; if (this.hash) { // lastEnd -> End in CRC32 einbeziehen this.crc32 = ZipHelper.crc32( this.crc32Table, this.crc32, myOutBuff, this.lastEnd, this.outEnd - this.lastEnd); } // skip till next byte boundary, read CRC , isize popSmallBuffer(this.smallCodeBuffer[1] & 7); int cCrc = popSmallBuffer(8) | (popSmallBuffer(8) << 8) | (popSmallBuffer(8) << 16) | (popSmallBuffer(8) << 24); int iSize = popSmallBuffer(8) | (popSmallBuffer(8) << 8) | (popSmallBuffer(8) << 16) | (popSmallBuffer(8) << 24); this.validData = (iSize == this.allPocessed); if (this.hash) { this.validData &= (this.crc32 == cCrc); } if (!this.validData) { // ERROR: the data check (size & hash) are wrong throw new IOException("2"); } } } // refresh the checksum at once if (this.status != GZipInputStream.FINISHED) { this.allPocessed = (this.allPocessed + this.outEnd - this.lastEnd) & 4294967295L; if (this.hash) { // lastEnd -> End in CRC32 einbeziehen this.crc32 = ZipHelper.crc32( this.crc32Table, this.crc32, myOutBuff, this.lastEnd, this.outEnd - this.lastEnd); } } }