Merge pull request #1754 from ag2s20150909/epublazy

实现Epub懒加载
pull/1758/head
kunfei 3 years ago committed by GitHub
commit 3aed980f5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      app/src/main/assets/epub/chapter.html
  2. 4
      app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt
  3. 13
      app/src/main/java/io/legado/app/model/localBook/EpubFile.kt
  4. 24
      epublib/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java
  5. 3
      epublib/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java
  6. 4
      epublib/src/main/java/me/ag2s/epublib/epub/EpubReader.java
  7. 6
      epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java
  8. 4
      epublib/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java
  9. 10
      epublib/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java
  10. 4
      epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java
  11. 197
      epublib/src/main/java/me/ag2s/epublib/zip/Adler32.java
  12. 368
      epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java
  13. 134
      epublib/src/main/java/me/ag2s/epublib/zip/CRC32.java
  14. 128
      epublib/src/main/java/me/ag2s/epublib/zip/CheckedInputStream.java
  15. 95
      epublib/src/main/java/me/ag2s/epublib/zip/CheckedOutputStream.java
  16. 84
      epublib/src/main/java/me/ag2s/epublib/zip/Checksum.java
  17. 68
      epublib/src/main/java/me/ag2s/epublib/zip/DataFormatException.java
  18. 512
      epublib/src/main/java/me/ag2s/epublib/zip/Deflater.java
  19. 77
      epublib/src/main/java/me/ag2s/epublib/zip/DeflaterConstants.java
  20. 653
      epublib/src/main/java/me/ag2s/epublib/zip/DeflaterEngine.java
  21. 704
      epublib/src/main/java/me/ag2s/epublib/zip/DeflaterHuffman.java
  22. 190
      epublib/src/main/java/me/ag2s/epublib/zip/DeflaterOutputStream.java
  23. 52
      epublib/src/main/java/me/ag2s/epublib/zip/DeflaterPending.java
  24. 338
      epublib/src/main/java/me/ag2s/epublib/zip/GZIPInputStream.java
  25. 146
      epublib/src/main/java/me/ag2s/epublib/zip/GZIPOutputStream.java
  26. 681
      epublib/src/main/java/me/ag2s/epublib/zip/Inflater.java
  27. 187
      epublib/src/main/java/me/ag2s/epublib/zip/InflaterDynHeader.java
  28. 187
      epublib/src/main/java/me/ag2s/epublib/zip/InflaterHuffmanTree.java
  29. 230
      epublib/src/main/java/me/ag2s/epublib/zip/InflaterInputStream.java
  30. 161
      epublib/src/main/java/me/ag2s/epublib/zip/OutputWindow.java
  31. 181
      epublib/src/main/java/me/ag2s/epublib/zip/PendingBuffer.java
  32. 206
      epublib/src/main/java/me/ag2s/epublib/zip/StreamManipulator.java
  33. 96
      epublib/src/main/java/me/ag2s/epublib/zip/ZipConstants.java
  34. 389
      epublib/src/main/java/me/ag2s/epublib/zip/ZipEntry.java
  35. 69
      epublib/src/main/java/me/ag2s/epublib/zip/ZipException.java
  36. 526
      epublib/src/main/java/me/ag2s/epublib/zip/ZipFile.java
  37. 344
      epublib/src/main/java/me/ag2s/epublib/zip/ZipInputStream.java
  38. 383
      epublib/src/main/java/me/ag2s/epublib/zip/ZipOutputStream.java

@ -7,11 +7,6 @@
<link href="../Styles/main.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<!--<div class="logo">
<img alt="" class="logo" src="../Images/logo.png"/>
</div>-->
<br/>
<br/>
<h2 class="head">{title}</h2>
{content}
</body>

@ -60,11 +60,10 @@ abstract class AbsCallBack(
}
//UrlResponseInfo可能为null
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
this.mResponse = responseFromResponse(this.mResponse, info)
//打印协议,用于调试
DebugLog.i(javaClass.name, info.negotiatedProtocol)
DebugLog.i(javaClass.simpleName, "start[${info.negotiatedProtocol}]${info.url}")
if (eventListener != null) {
eventListener.responseHeadersEnd(mCall, this.mResponse)
eventListener.responseBodyStart(mCall)
@ -108,6 +107,7 @@ abstract class AbsCallBack(
buffer.asResponseBody(contentType)
val newRequest = originalRequest.newBuilder().url(info.url).build()
this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build()
DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
eventListener?.callEnd(mCall)
if (responseCallback != null) {

@ -2,6 +2,7 @@ package io.legado.app.model.localBook
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.text.TextUtils
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
@ -10,6 +11,7 @@ import me.ag2s.epublib.domain.EpubBook
import me.ag2s.epublib.domain.Resource
import me.ag2s.epublib.domain.TOCReference
import me.ag2s.epublib.epub.EpubReader
import me.ag2s.epublib.zip.ZipFile
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
@ -101,9 +103,16 @@ class EpubFile(var book: Book) {
/*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/
private fun readEpub(): EpubBook? {
try {
val bis = LocalBook.getBookInputStream(book)
val uri = Uri.parse(book.bookUrl)
return if (uri.isContentScheme()) {
//通过懒加载读取epub
return EpubReader().readEpub(bis, "utf-8")
EpubReader().readEpubLazy(ZipFile(appCtx, uri), "utf-8")
} else {
val bis = LocalBook.getBookInputStream(book)
EpubReader().readEpub(bis, "utf-8")
}
} catch (e: Exception) {
e.printOnDebug()
}

@ -1,32 +1,40 @@
package me.ag2s.epublib.domain;
import android.content.Context;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import me.ag2s.epublib.zip.ZipEntry;
import me.ag2s.epublib.zip.ZipFile;
/**
* @author jake
*/
public class EpubResourceProvider implements LazyResourceProvider {
private final String epubFilename;
private final Context context;
private final Uri uri;
/**
* @param epubFilename the file name for the epub we're created from.
* @param context
* @param uri
*/
public EpubResourceProvider(String epubFilename) {
this.epubFilename = epubFilename;
public EpubResourceProvider(Context context, Uri uri) {
this.context = context;
this.uri = uri;
}
@Override
public InputStream getResourceStream(String href) throws IOException {
ZipFile zipFile = new ZipFile(epubFilename);
ZipFile zipFile = new ZipFile(context, uri);
ZipEntry zipEntry = zipFile.getEntry(href);
if (zipEntry == null) {
zipFile.close();
throw new IllegalStateException(
"Cannot find entry " + href + " in epub file " + epubFilename);
"Cannot find entry " + href + " in epub file " + uri.toString());
}
return new ResourceInputStream(zipFile.getInputStream(zipEntry), zipFile);
}

@ -3,7 +3,8 @@ package me.ag2s.epublib.domain;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipFile;
import me.ag2s.epublib.zip.ZipFile;
/**
* A wrapper class for closing a ZipFile object when the InputStream derived

@ -9,8 +9,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.EpubBook;
@ -20,6 +18,8 @@ import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.Resources;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.StringUtil;
import me.ag2s.epublib.zip.ZipFile;
import me.ag2s.epublib.zip.ZipInputStream;
/**
* Reads an epub file.

@ -9,14 +9,14 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import me.ag2s.epublib.domain.EpubBook;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.util.IOUtil;
import me.ag2s.epublib.zip.CRC32;
import me.ag2s.epublib.zip.ZipEntry;
import me.ag2s.epublib.zip.ZipOutputStream;
/**
* Generates an epub file. Not thread-safe, single use object.

@ -14,8 +14,6 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import me.ag2s.epublib.Constants;
import me.ag2s.epublib.domain.Author;
@ -27,6 +25,8 @@ import me.ag2s.epublib.domain.TOCReference;
import me.ag2s.epublib.domain.TableOfContents;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.util.StringUtil;
import me.ag2s.epublib.zip.ZipEntry;
import me.ag2s.epublib.zip.ZipOutputStream;
/**
* Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/

@ -7,10 +7,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import me.ag2s.epublib.domain.EpubResourceProvider;
import me.ag2s.epublib.domain.LazyResource;
@ -21,6 +17,10 @@ import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.domain.Resources;
import me.ag2s.epublib.util.CollectionUtil;
import me.ag2s.epublib.util.ResourceUtil;
import me.ag2s.epublib.zip.ZipEntry;
import me.ag2s.epublib.zip.ZipException;
import me.ag2s.epublib.zip.ZipFile;
import me.ag2s.epublib.zip.ZipInputStream;
/**
@ -51,7 +51,7 @@ public class ResourcesLoader {
List<MediaType> lazyLoadedTypes) throws IOException {
LazyResourceProvider resourceProvider =
new EpubResourceProvider(zipFile.getName());
new EpubResourceProvider(zipFile.getContext(), zipFile.getUri());
Resources result = new Resources();
Enumeration<? extends ZipEntry> entries = zipFile.entries();

@ -10,8 +10,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilder;
@ -20,6 +18,8 @@ import me.ag2s.epublib.domain.MediaType;
import me.ag2s.epublib.domain.MediaTypes;
import me.ag2s.epublib.domain.Resource;
import me.ag2s.epublib.epub.EpubProcessorSupport;
import me.ag2s.epublib.zip.ZipEntry;
import me.ag2s.epublib.zip.ZipInputStream;
/**
* Various resource utility methods

@ -0,0 +1,197 @@
/* Adler32.java - Computes Adler32 data checksum of a data stream
Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/*
* Written using on-line Java Platform 1.2 API Specification, as well
* as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
* The actual Adler32 algorithm is taken from RFC 1950.
* Status: Believed complete and correct.
*/
/**
* Computes Adler32 checksum for a stream of data. An Adler32
* checksum is not as reliable as a CRC32 checksum, but a lot faster to
* compute.
* <p>
* The specification for Adler32 may be found in RFC 1950.
* (ZLIB Compressed Data Format Specification version 3.3)
* <p>
* <p>
* From that document:
* <p>
* "ADLER32 (Adler-32 checksum)
* This contains a checksum value of the uncompressed data
* (excluding any dictionary data) computed according to Adler-32
* algorithm. This algorithm is a 32-bit extension and improvement
* of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073
* standard.
* <p>
* Adler-32 is composed of two sums accumulated per byte: s1 is
* the sum of all bytes, s2 is the sum of all s1 values. Both sums
* are done modulo 65521. s1 is initialized to 1, s2 to zero. The
* Adler-32 checksum is stored as s2*65536 + s1 in most-
* significant-byte first (network) order."
* <p>
* "8.2. The Adler-32 algorithm
* <p>
* The Adler-32 algorithm is much faster than the CRC32 algorithm yet
* still provides an extremely low probability of undetected errors.
* <p>
* The modulo on unsigned long accumulators can be delayed for 5552
* bytes, so the modulo operation time is negligible. If the bytes
* are a, b, c, the second sum is 3a + 2b + c + 3, and so is position
* and order sensitive, unlike the first sum, which is just a
* checksum. That 65521 is prime is important to avoid a possible
* large class of two-byte errors that leave the check unchanged.
* (The Fletcher checksum uses 255, which is not prime and which also
* makes the Fletcher check insensitive to single byte changes 0 <->
* 255.)
* <p>
* The sum s1 is initialized to 1 instead of zero to make the length
* of the sequence part of s2, so that the length does not have to be
* checked separately. (Any sequence of zeroes has a Fletcher
* checksum of zero.)"
*
* @author John Leuner, Per Bothner
* @see InflaterInputStream
* @see DeflaterOutputStream
* @since JDK 1.1
*/
public class Adler32 implements Checksum {
/**
* largest prime smaller than 65536
*/
private static final int BASE = 65521;
private int checksum; //we do all in int.
//Note that java doesn't have unsigned integers,
//so we have to be careful with what arithmetic
//we do. We return the checksum as a long to
//avoid sign confusion.
/**
* Creates a new instance of the <code>Adler32</code> class.
* The checksum starts off with a value of 1.
*/
public Adler32() {
reset();
}
/**
* Resets the Adler32 checksum to the initial value.
*/
public void reset() {
checksum = 1; //Initialize to 1
}
/**
* Updates the checksum with the byte b.
*
* @param bval the data value to add. The high byte of the int is ignored.
*/
public void update(int bval) {
//We could make a length 1 byte array and call update again, but I
//would rather not have that overhead
int s1 = checksum & 0xffff;
int s2 = checksum >>> 16;
s1 = (s1 + (bval & 0xFF)) % BASE;
s2 = (s1 + s2) % BASE;
checksum = (s2 << 16) + s1;
}
/**
* Updates the checksum with the bytes taken from the array.
*
* @param buffer an array of bytes
*/
public void update(byte[] buffer) {
update(buffer, 0, buffer.length);
}
/**
* Updates the checksum with the bytes taken from the array.
*
* @param buf an array of bytes
* @param off the start of the data used for this update
* @param len the number of bytes to use for this update
*/
public void update(byte[] buf, int off, int len) {
//(By Per Bothner)
int s1 = checksum & 0xffff;
int s2 = checksum >>> 16;
while (len > 0) {
// We can defer the modulo operation:
// s1 maximally grows from 65521 to 65521 + 255 * 3800
// s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31
int n = 3800;
if (n > len)
n = len;
len -= n;
while (--n >= 0) {
s1 = s1 + (buf[off++] & 0xFF);
s2 = s2 + s1;
}
s1 %= BASE;
s2 %= BASE;
}
/*Old implementation, borrowed from somewhere:
int n;
while (len-- > 0) {
s1 = (s1 + (bs[offset++] & 0xff)) % BASE;
s2 = (s2 + s1) % BASE;
}*/
checksum = (s2 << 16) | s1;
}
/**
* Returns the Adler32 data checksum computed so far.
*/
public long getValue() {
return (long) checksum & 0xffffffffL;
}
}

@ -0,0 +1,368 @@
package me.ag2s.epublib.zip;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
import androidx.annotation.NonNull;
import java.io.Closeable;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
public class AndroidRandomReadableFile implements DataInput, Closeable {
private ParcelFileDescriptor pfd;
private FileInputStream fis;
private DataInputStream dis;
public long pos = 0;
public AndroidRandomReadableFile(@NonNull Context context, @NonNull Uri treeUri) throws FileNotFoundException {
try {
pfd = context.getContentResolver().openFileDescriptor(treeUri, "r");
fis = new FileInputStream(pfd.getFileDescriptor());
dis = new DataInputStream(fis);
} catch (FileNotFoundException e) {
throw e;
}
}
public final FileDescriptor getFD() {
return pfd.getFileDescriptor();
}
public final FileChannel getChannel() {
if (fis == null || pos != getPos()) {
fis = new FileInputStream(pfd.getFileDescriptor());
}
return fis.getChannel();
}
public final FileInputStream getFileInputStream() {
if (fis == null || pos != getPos()) {
fis = new FileInputStream(pfd.getFileDescriptor());
}
return fis;
}
public int read() throws IOException {
byte[] b = new byte[1];
return (read(b, 0, 1) != -1) ? b[0] & 0xff : -1;
}
public int read(byte[] b) {
try {
return read(b, 0, b.length);
} catch (IOException e) {
e.printStackTrace();
return -1;
}
}
public int read(byte[] b, int off, int len) throws IOException {
try {
return android.system.Os.read(pfd.getFileDescriptor(), b, off, len);
} catch (Exception e) {
return -1;
}
}
private void syncInputStream() {
fis = new FileInputStream(pfd.getFileDescriptor());
dis = new DataInputStream(fis);
}
public void seek(long pos) throws IOException {
try {
android.system.Os.lseek(pfd.getFileDescriptor(), pos, OsConstants.SEEK_SET);
} catch (Exception e) {
throw new IOException(e);
}
}
public long length() throws IOException {
try {
return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_END);
} catch (Exception e) {
throw new IOException(e);
}
}
public long getPos() {
try {
return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR);
} catch (Exception e) {
return -1;
}
}
@Override
public void readFully(byte[] b) throws IOException {
try {
android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length);
} catch (ErrnoException e) {
e.printStackTrace();
}
}
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
try {
android.system.Os.read(pfd.getFileDescriptor(), b, off, len);
//syncInputStream();
} catch (ErrnoException e) {
throw new IOException(e);
}
}
@Override
public int skipBytes(int n) throws IOException {
try {
byte[] b = new byte[n];
return android.system.Os.read(pfd.getFileDescriptor(), b, 0, b.length);
} catch (Exception e) {
return -1;
}
}
@Override
public boolean readBoolean() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
@Override
public byte readByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return (byte) (ch);
}
@Override
public int readUnsignedByte() throws IOException {
int ch = this.read();
if (ch < 0)
throw new EOFException();
return ch;
}
private final byte[] readBuffer = new byte[8];
@Override
public short readShort() throws IOException {
readFully(readBuffer, 0, 2);
return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get();
}
@Override
public int readUnsignedShort() throws IOException {
readFully(readBuffer, 0, 2);
return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get() & 0xffff;
}
@Override
public char readChar() throws IOException {
readFully(readBuffer, 0, 2);
return (char) ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get();
}
@Override
public int readInt() throws IOException {
readFully(readBuffer, 0, 4);
return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asIntBuffer().get();
}
@Override
public long readLong() throws IOException {
readFully(readBuffer, 0, 8);
return (((long) readBuffer[0] << 56) +
((long) (readBuffer[1] & 255) << 48) +
((long) (readBuffer[2] & 255) << 40) +
((long) (readBuffer[3] & 255) << 32) +
((long) (readBuffer[4] & 255) << 24) +
((readBuffer[5] & 255) << 16) +
((readBuffer[6] & 255) << 8) +
((readBuffer[7] & 255) << 0));
}
@Override
public float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
@Override
public double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
private char[] lineBuffer;
@Deprecated
@Override
public String readLine() throws IOException {
char[] buf = lineBuffer;
if (buf == null) {
buf = lineBuffer = new char[128];
}
int room = buf.length;
int offset = 0;
int c;
loop:
while (true) {
switch (c = this.read()) {
case -1:
case '\n':
break loop;
case '\r':
int c2 = this.read();
break loop;
default:
if (--room < 0) {
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
lineBuffer = buf;
}
buf[offset++] = (char) c;
break;
}
}
if ((c == -1) && (offset == 0)) {
return null;
}
return String.copyValueOf(buf, 0, offset);
}
@Override
public String readUTF() throws IOException {
if (pos != getPos()) {
syncInputStream();
}
return dis.readUTF();
}
// public final static String readUTF(DataInput in) throws IOException {
// int utflen = in.readUnsignedShort();
// byte[] bytearr = null;
// char[] chararr = null;
// if (in instanceof DataInputStream) {
// DataInputStream dis = (DataInputStream)in;
// if (dis.bytearr.length < utflen){
// dis.bytearr = new byte[utflen*2];
// dis.chararr = new char[utflen*2];
// }
// chararr = dis.chararr;
// bytearr = dis.bytearr;
// } else {
// bytearr = new byte[utflen];
// chararr = new char[utflen];
// }
//
// int c, char2, char3;
// int count = 0;
// int chararr_count=0;
//
// in.readFully(bytearr, 0, utflen);
//
// while (count < utflen) {
// c = (int) bytearr[count] & 0xff;
// if (c > 127) break;
// count++;
// chararr[chararr_count++]=(char)c;
// }
//
// while (count < utflen) {
// c = (int) bytearr[count] & 0xff;
// switch (c >> 4) {
// case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// /* 0xxxxxxx*/
// count++;
// chararr[chararr_count++]=(char)c;
// break;
// case 12: case 13:
// /* 110x xxxx 10xx xxxx*/
// count += 2;
// if (count > utflen)
// throw new UTFDataFormatException(
// "malformed input: partial character at end");
// char2 = (int) bytearr[count-1];
// if ((char2 & 0xC0) != 0x80)
// throw new UTFDataFormatException(
// "malformed input around byte " + count);
// chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
// (char2 & 0x3F));
// break;
// case 14:
// /* 1110 xxxx 10xx xxxx 10xx xxxx */
// count += 3;
// if (count > utflen)
// throw new UTFDataFormatException(
// "malformed input: partial character at end");
// char2 = (int) bytearr[count-2];
// char3 = (int) bytearr[count-1];
// if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
// throw new UTFDataFormatException(
// "malformed input around byte " + (count-1));
// chararr[chararr_count++]=(char)(((c & 0x0F) << 12) |
// ((char2 & 0x3F) << 6) |
// ((char3 & 0x3F) << 0));
// break;
// default:
// /* 10xx xxxx, 1111 xxxx */
// throw new UTFDataFormatException(
// "malformed input around byte " + count);
// }
// }
// // The number of chars produced may be less than utflen
// return new String(chararr, 0, chararr_count);
// }
@Override
public void close() throws IOException {
try {
dis.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
pfd.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,134 @@
/* CRC32.java - Computes CRC32 data checksum of a data stream
Copyright (C) 1999. 2000, 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/*
* Written using on-line Java Platform 1.2 API Specification, as well
* as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
* The actual CRC32 algorithm is taken from RFC 1952.
* Status: Believed complete and correct.
*/
/**
* Computes CRC32 data checksum of a data stream.
* The actual CRC32 algorithm is described in RFC 1952
* (GZIP file format specification version 4.3).
* Can be used to get the CRC32 over a stream if used with checked input/output
* streams.
*
* @author Per Bothner
* @date April 1, 1999.
* @see InflaterInputStream
* @see DeflaterOutputStream
*/
public class CRC32 implements Checksum {
/**
* The crc data checksum so far.
*/
private int crc = 0;
/**
* The fast CRC table. Computed once when the CRC32 class is loaded.
*/
private static final int[] crc_table = make_crc_table();
/**
* Make the table for a fast CRC.
*/
private static int[] make_crc_table() {
int[] crc_table = new int[256];
for (int n = 0; n < 256; n++) {
int c = n;
for (int k = 8; --k >= 0; ) {
if ((c & 1) != 0)
c = 0xedb88320 ^ (c >>> 1);
else
c = c >>> 1;
}
crc_table[n] = c;
}
return crc_table;
}
/**
* Returns the CRC32 data checksum computed so far.
*/
public long getValue() {
return (long) crc & 0xffffffffL;
}
/**
* Resets the CRC32 data checksum as if no update was ever called.
*/
public void reset() {
crc = 0;
}
/**
* Updates the checksum with the int bval.
*
* @param bval (the byte is taken as the lower 8 bits of bval)
*/
public void update(int bval) {
int c = ~crc;
c = crc_table[(c ^ bval) & 0xff] ^ (c >>> 8);
crc = ~c;
}
/**
* Adds the byte array to the data checksum.
*
* @param buf the buffer which contains the data
* @param off the offset in the buffer where the data starts
* @param len the length of the data
*/
public void update(byte[] buf, int off, int len) {
int c = ~crc;
while (--len >= 0)
c = crc_table[(c ^ buf[off++]) & 0xff] ^ (c >>> 8);
crc = ~c;
}
/**
* Adds the complete byte array to the data checksum.
*/
public void update(byte[] buf) {
update(buf, 0, buf.length);
}
}

@ -0,0 +1,128 @@
/* CheckedInputStream.java - Compute checksum of data being read
Copyright (C) 1999, 2000 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/* Written using on-line Java Platform 1.2 API Specification
* and JCL book.
* Believed complete and correct.
*/
/**
* InputStream that computes a checksum of the data being read using a
* supplied Checksum object.
*
* @author Tom Tromey
* @date May 17, 1999
* @see Checksum
*/
public class CheckedInputStream extends FilterInputStream {
/**
* Creates a new CheckInputStream on top of the supplied OutputStream
* using the supplied Checksum.
*/
public CheckedInputStream(InputStream in, Checksum sum) {
super(in);
this.sum = sum;
}
/**
* Returns the Checksum object used. To get the data checksum computed so
* far call <code>getChecksum.getValue()</code>.
*/
public Checksum getChecksum() {
return sum;
}
/**
* Reads one byte, updates the checksum and returns the read byte
* (or -1 when the end of file was reached).
*/
public int read() throws IOException {
int x = in.read();
if (x != -1)
sum.update(x);
return x;
}
/**
* Reads at most len bytes in the supplied buffer and updates the checksum
* with it. Returns the number of bytes actually read or -1 when the end
* of file was reached.
*/
public int read(byte[] buf, int off, int len) throws IOException {
int r = in.read(buf, off, len);
if (r != -1)
sum.update(buf, off, r);
return r;
}
/**
* Skips n bytes by reading them in a temporary buffer and updating the
* the checksum with that buffer. Returns the actual number of bytes skiped
* which can be less then requested when the end of file is reached.
*/
public long skip(long n) throws IOException {
if (n == 0)
return 0;
int min = (int) Math.min(n, 1024);
byte[] buf = new byte[min];
long s = 0;
while (n > 0) {
int r = in.read(buf, 0, min);
if (r == -1)
break;
n -= r;
s += r;
min = (int) Math.min(n, 1024);
sum.update(buf, 0, r);
}
return s;
}
/**
* The checksum object.
*/
private final Checksum sum;
}

@ -0,0 +1,95 @@
/* CheckedOutputStream.java - Compute checksum of data being written.
Copyright (C) 1999, 2000 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/* Written using on-line Java Platform 1.2 API Specification
* and JCL book.
* Believed complete and correct.
*/
/**
* OutputStream that computes a checksum of data being written using a
* supplied Checksum object.
*
* @author Tom Tromey
* @date May 17, 1999
* @see Checksum
*/
public class CheckedOutputStream extends FilterOutputStream {
/**
* Creates a new CheckInputStream on top of the supplied OutputStream
* using the supplied Checksum.
*/
public CheckedOutputStream(OutputStream out, Checksum cksum) {
super(out);
this.sum = cksum;
}
/**
* Returns the Checksum object used. To get the data checksum computed so
* far call <code>getChecksum.getValue()</code>.
*/
public Checksum getChecksum() {
return sum;
}
/**
* Writes one byte to the OutputStream and updates the Checksum.
*/
public void write(int bval) throws IOException {
out.write(bval);
sum.update(bval);
}
/**
* Writes the byte array to the OutputStream and updates the Checksum.
*/
public void write(byte[] buf, int off, int len) throws IOException {
out.write(buf, off, len);
sum.update(buf, off, len);
}
/**
* The checksum object.
*/
private Checksum sum;
}

@ -0,0 +1,84 @@
/* Checksum.java - Interface to compute a data checksum
Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/*
* Written using on-line Java Platform 1.2 API Specification, as well
* as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
* Status: Believed complete and correct.
*/
/**
* Interface to compute a data checksum used by checked input/output streams.
* A data checksum can be updated by one byte or with a byte array. After each
* update the value of the current checksum can be returned by calling
* <code>getValue</code>. The complete checksum object can also be reset
* so it can be used again with new data.
*
* @author Per Bothner
* @author Jochen Hoenicke
* @see CheckedInputStream
* @see CheckedOutputStream
*/
public interface Checksum {
/**
* Returns the data checksum computed so far.
*/
long getValue();
/**
* Resets the data checksum as if no update was ever called.
*/
void reset();
/**
* Adds one byte to the data checksum.
*
* @param bval the data value to add. The high byte of the int is ignored.
*/
void update(int bval);
/**
* Adds the byte array to the data checksum.
*
* @param buf the buffer which contains the data
* @param off the offset in the buffer where the data starts
* @param len the length of the data
*/
void update(byte[] buf, int off, int len);
}

@ -0,0 +1,68 @@
/* DataformatException.java -- thrown when compressed data is corrupt
Copyright (C) 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/**
* Exception thrown when compressed data is corrupt.
*
* @author Tom Tromey
* @author John Leuner
* @status updated to 1.4
* @since 1.1
*/
public class DataFormatException extends Exception {
/**
* Compatible with JDK 1.1+.
*/
private static final long serialVersionUID = 2219632870893641452L;
/**
* Create an exception without a message.
*/
public DataFormatException() {
}
/**
* Create an exception with a message.
*
* @param msg the message
*/
public DataFormatException(String msg) {
super(msg);
}
}

@ -0,0 +1,512 @@
/* Deflater.java - Compress a data stream
Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/**
* This is the Deflater class. The deflater class compresses input
* with the deflate algorithm described in RFC 1951. It has several
* compression levels and three different strategies described below.
* <p>
* This class is <i>not</i> thread safe. This is inherent in the API, due
* to the split of deflate and setInput.
*
* @author Jochen Hoenicke
* @author Tom Tromey
*/
public class Deflater {
/**
* The best and slowest compression level. This tries to find very
* long and distant string repetitions.
*/
public static final int BEST_COMPRESSION = 9;
/**
* The worst but fastest compression level.
*/
public static final int BEST_SPEED = 1;
/**
* The default compression level.
*/
public static final int DEFAULT_COMPRESSION = -1;
/**
* This level won't compress at all but output uncompressed blocks.
*/
public static final int NO_COMPRESSION = 0;
/**
* The default strategy.
*/
public static final int DEFAULT_STRATEGY = 0;
/**
* This strategy will only allow longer string repetitions. It is
* useful for random data with a small character set.
*/
public static final int FILTERED = 1;
/**
* This strategy will not look for string repetitions at all. It
* only encodes with Huffman trees (which means, that more common
* characters get a smaller encoding.
*/
public static final int HUFFMAN_ONLY = 2;
/**
* The compression method. This is the only method supported so far.
* There is no need to use this constant at all.
*/
public static final int DEFLATED = 8;
/*
* The Deflater can do the following state transitions:
*
* (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---.
* / | (2) (5) |
* / v (5) |
* (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3)
* \ | (3) | ,-------'
* | | | (3) /
* v v (5) v v
* (1) -> BUSY_STATE ----> FINISHING_STATE
* | (6)
* v
* FINISHED_STATE
* \_____________________________________/
* | (7)
* v
* CLOSED_STATE
*
* (1) If we should produce a header we start in INIT_STATE, otherwise
* we start in BUSY_STATE.
* (2) A dictionary may be set only when we are in INIT_STATE, then
* we change the state as indicated.
* (3) Whether a dictionary is set or not, on the first call of deflate
* we change to BUSY_STATE.
* (4) -- intentionally left blank -- :)
* (5) FINISHING_STATE is entered, when flush() is called to indicate that
* there is no more INPUT. There are also states indicating, that
* the header wasn't written yet.
* (6) FINISHED_STATE is entered, when everything has been flushed to the
* internal pending output buffer.
* (7) At any time (7)
*
*/
private static final int IS_SETDICT = 0x01;
private static final int IS_FLUSHING = 0x04;
private static final int IS_FINISHING = 0x08;
private static final int INIT_STATE = 0x00;
private static final int SETDICT_STATE = 0x01;
private static final int INIT_FINISHING_STATE = 0x08;
private static final int SETDICT_FINISHING_STATE = 0x09;
private static final int BUSY_STATE = 0x10;
private static final int FLUSHING_STATE = 0x14;
private static final int FINISHING_STATE = 0x1c;
private static final int FINISHED_STATE = 0x1e;
private static final int CLOSED_STATE = 0x7f;
/**
* Compression level.
*/
private int level;
/**
* should we include a header.
*/
private boolean noHeader;
/**
* The current state.
*/
private int state;
/**
* The total bytes of output written.
*/
private int totalOut;
/**
* The pending output.
*/
private DeflaterPending pending;
/**
* The deflater engine.
*/
private DeflaterEngine engine;
/**
* Creates a new deflater with default compression level.
*/
public Deflater() {
this(DEFAULT_COMPRESSION, false);
}
/**
* Creates a new deflater with given compression level.
*
* @param lvl the compression level, a value between NO_COMPRESSION
* and BEST_COMPRESSION, or DEFAULT_COMPRESSION.
* @throws IllegalArgumentException if lvl is out of range.
*/
public Deflater(int lvl) {
this(lvl, false);
}
/**
* Creates a new deflater with given compression level.
*
* @param lvl the compression level, a value between NO_COMPRESSION
* and BEST_COMPRESSION.
* @param nowrap true, iff we should suppress the deflate header at the
* beginning and the adler checksum at the end of the output. This is
* useful for the GZIP format.
* @throws IllegalArgumentException if lvl is out of range.
*/
public Deflater(int lvl, boolean nowrap) {
if (lvl == DEFAULT_COMPRESSION)
lvl = 6;
else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION)
throw new IllegalArgumentException();
pending = new DeflaterPending();
engine = new DeflaterEngine(pending);
this.noHeader = nowrap;
setStrategy(DEFAULT_STRATEGY);
setLevel(lvl);
reset();
}
/**
* Resets the deflater. The deflater acts afterwards as if it was
* just created with the same compression level and strategy as it
* had before.
*/
public void reset() {
state = (noHeader ? BUSY_STATE : INIT_STATE);
totalOut = 0;
pending.reset();
engine.reset();
}
/**
* Frees all objects allocated by the compressor. There's no
* reason to call this, since you can just rely on garbage
* collection. Exists only for compatibility against Sun's JDK,
* where the compressor allocates native memory.
* If you call any method (even reset) afterwards the behaviour is
* <i>undefined</i>.
*
* @deprecated Just clear all references to deflater instead.
*/
public void end() {
engine = null;
pending = null;
state = CLOSED_STATE;
}
/**
* Gets the current adler checksum of the data that was processed so
* far.
*/
public int getAdler() {
return engine.getAdler();
}
/**
* Gets the number of input bytes processed so far.
*/
public int getTotalIn() {
return engine.getTotalIn();
}
/**
* Gets the number of output bytes so far.
*/
public int getTotalOut() {
return totalOut;
}
/**
* Finalizes this object.
*/
protected void finalize() {
/* Exists solely for compatibility. We don't have any native state. */
}
/**
* Flushes the current input block. Further calls to deflate() will
* produce enough output to inflate everything in the current input
* block. This is not part of Sun's JDK so I have made it package
* private. It is used by DeflaterOutputStream to implement
* flush().
*/
void flush() {
state |= IS_FLUSHING;
}
/**
* Finishes the deflater with the current input block. It is an error
* to give more input after this method was called. This method must
* be called to force all bytes to be flushed.
*/
public void finish() {
state |= IS_FLUSHING | IS_FINISHING;
}
/**
* Returns true iff the stream was finished and no more output bytes
* are available.
*/
public boolean finished() {
return state == FINISHED_STATE && pending.isFlushed();
}
/**
* Returns true, if the input buffer is empty.
* You should then call setInput(). <br>
*
* <em>NOTE</em>: This method can also return true when the stream
* was finished.
*/
public boolean needsInput() {
return engine.needsInput();
}
/**
* Sets the data which should be compressed next. This should be only
* called when needsInput indicates that more input is needed.
* If you call setInput when needsInput() returns false, the
* previous input that is still pending will be thrown away.
* The given byte array should not be changed, before needsInput() returns
* true again.
* This call is equivalent to <code>setInput(input, 0, input.length)</code>.
*
* @param input the buffer containing the input data.
* @throws IllegalStateException if the buffer was finished() or ended().
*/
public void setInput(byte[] input) {
setInput(input, 0, input.length);
}
/**
* Sets the data which should be compressed next. This should be
* only called when needsInput indicates that more input is needed.
* The given byte array should not be changed, before needsInput() returns
* true again.
*
* @param input the buffer containing the input data.
* @param off the start of the data.
* @param len the length of the data.
* @throws IllegalStateException if the buffer was finished() or ended()
* or if previous input is still pending.
*/
public void setInput(byte[] input, int off, int len) {
if ((state & IS_FINISHING) != 0)
throw new IllegalStateException("finish()/end() already called");
engine.setInput(input, off, len);
}
/**
* Sets the compression level. There is no guarantee of the exact
* position of the change, but if you call this when needsInput is
* true the change of compression level will occur somewhere near
* before the end of the so far given input.
*
* @param lvl the new compression level.
*/
public void setLevel(int lvl) {
if (lvl == DEFAULT_COMPRESSION)
lvl = 6;
else if (lvl < NO_COMPRESSION || lvl > BEST_COMPRESSION)
throw new IllegalArgumentException();
if (level != lvl) {
level = lvl;
engine.setLevel(lvl);
}
}
/**
* Sets the compression strategy. Strategy is one of
* DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact
* position where the strategy is changed, the same as for
* setLevel() applies.
*
* @param stgy the new compression strategy.
*/
public void setStrategy(int stgy) {
if (stgy != DEFAULT_STRATEGY && stgy != FILTERED
&& stgy != HUFFMAN_ONLY)
throw new IllegalArgumentException();
engine.setStrategy(stgy);
}
/**
* Deflates the current input block to the given array. It returns
* the number of bytes compressed, or 0 if either
* needsInput() or finished() returns true or length is zero.
*
* @param output the buffer where to write the compressed data.
*/
public int deflate(byte[] output) {
return deflate(output, 0, output.length);
}
/**
* Deflates the current input block to the given array. It returns
* the number of bytes compressed, or 0 if either
* needsInput() or finished() returns true or length is zero.
*
* @param output the buffer where to write the compressed data.
* @param offset the offset into the output array.
* @param length the maximum number of bytes that may be written.
* @throws IllegalStateException if end() was called.
* @throws IndexOutOfBoundsException if offset and/or length
* don't match the array length.
*/
public int deflate(byte[] output, int offset, int length) {
int origLength = length;
if (state == CLOSED_STATE)
throw new IllegalStateException("Deflater closed");
if (state < BUSY_STATE) {
/* output header */
int header = (DEFLATED +
((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8;
int level_flags = (level - 1) >> 1;
if (level_flags < 0 || level_flags > 3)
level_flags = 3;
header |= level_flags << 6;
if ((state & IS_SETDICT) != 0)
/* Dictionary was set */
header |= DeflaterConstants.PRESET_DICT;
header += 31 - (header % 31);
pending.writeShortMSB(header);
if ((state & IS_SETDICT) != 0) {
int chksum = engine.getAdler();
engine.resetAdler();
pending.writeShortMSB(chksum >> 16);
pending.writeShortMSB(chksum & 0xffff);
}
state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING));
}
for (; ; ) {
int count = pending.flush(output, offset, length);
offset += count;
totalOut += count;
length -= count;
if (length == 0 || state == FINISHED_STATE)
break;
if (!engine.deflate((state & IS_FLUSHING) != 0,
(state & IS_FINISHING) != 0)) {
if (state == BUSY_STATE)
/* We need more input now */
return origLength - length;
else if (state == FLUSHING_STATE) {
if (level != NO_COMPRESSION) {
/* We have to supply some lookahead. 8 bit lookahead
* are needed by the zlib inflater, and we must fill
* the next byte, so that all bits are flushed.
*/
int neededbits = 8 + ((-pending.getBitCount()) & 7);
while (neededbits > 0) {
/* write a static tree block consisting solely of
* an EOF:
*/
pending.writeBits(2, 10);
neededbits -= 10;
}
}
state = BUSY_STATE;
} else if (state == FINISHING_STATE) {
pending.alignToByte();
/* We have completed the stream */
if (!noHeader) {
int adler = engine.getAdler();
pending.writeShortMSB(adler >> 16);
pending.writeShortMSB(adler & 0xffff);
}
state = FINISHED_STATE;
}
}
}
return origLength - length;
}
/**
* Sets the dictionary which should be used in the deflate process.
* This call is equivalent to <code>setDictionary(dict, 0,
* dict.length)</code>.
*
* @param dict the dictionary.
* @throws IllegalStateException if setInput () or deflate ()
* were already called or another dictionary was already set.
*/
public void setDictionary(byte[] dict) {
setDictionary(dict, 0, dict.length);
}
/**
* Sets the dictionary which should be used in the deflate process.
* The dictionary should be a byte array containing strings that are
* likely to occur in the data which should be compressed. The
* dictionary is not stored in the compressed output, only a
* checksum. To decompress the output you need to supply the same
* dictionary again.
*
* @param dict the dictionary.
* @param offset an offset into the dictionary.
* @param length the length of the dictionary.
* @throws IllegalStateException if setInput () or deflate () were
* already called or another dictionary was already set.
*/
public void setDictionary(byte[] dict, int offset, int length) {
if (state != INIT_STATE)
throw new IllegalStateException();
state = SETDICT_STATE;
engine.setDictionary(dict, offset, length);
}
}

@ -0,0 +1,77 @@
/* me.ag2s.epublib.zip.DeflaterConstants
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
interface DeflaterConstants {
final static boolean DEBUGGING = false;
final static int STORED_BLOCK = 0;
final static int STATIC_TREES = 1;
final static int DYN_TREES = 2;
final static int PRESET_DICT = 0x20;
final static int DEFAULT_MEM_LEVEL = 8;
final static int MAX_MATCH = 258;
final static int MIN_MATCH = 3;
final static int MAX_WBITS = 15;
final static int WSIZE = 1 << MAX_WBITS;
final static int WMASK = WSIZE - 1;
final static int HASH_BITS = DEFAULT_MEM_LEVEL + 7;
final static int HASH_SIZE = 1 << HASH_BITS;
final static int HASH_MASK = HASH_SIZE - 1;
final static int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH;
final static int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
final static int MAX_DIST = WSIZE - MIN_LOOKAHEAD;
final static int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8);
final static int MAX_BLOCK_SIZE = Math.min(65535, PENDING_BUF_SIZE - 5);
final static int DEFLATE_STORED = 0;
final static int DEFLATE_FAST = 1;
final static int DEFLATE_SLOW = 2;
final static int GOOD_LENGTH[] = {0, 4, 4, 4, 4, 8, 8, 8, 32, 32};
final static int MAX_LAZY[] = {0, 4, 5, 6, 4, 16, 16, 32, 128, 258};
final static int NICE_LENGTH[] = {0, 8, 16, 32, 16, 32, 128, 128, 258, 258};
final static int MAX_CHAIN[] = {0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096};
final static int COMPR_FUNC[] = {0, 1, 1, 1, 1, 2, 2, 2, 2, 2};
}

@ -0,0 +1,653 @@
/* me.ag2s.epublib.zip.DeflaterEngine
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
class DeflaterEngine implements DeflaterConstants {
private final static int TOO_FAR = 4096;
private int ins_h;
/**
* Hashtable, hashing three characters to an index for window, so
* that window[index]..window[index+2] have this hash code.
* Note that the array should really be unsigned short, so you need
* to and the values with 0xffff.
*/
private short[] head;
/**
* prev[index & WMASK] points to the previous index that has the
* same hash code as the string starting at index. This way
* entries with the same hash code are in a linked list.
* Note that the array should really be unsigned short, so you need
* to and the values with 0xffff.
*/
private short[] prev;
private int matchStart, matchLen;
private boolean prevAvailable;
private int blockStart;
/**
* strstart points to the current character in window.
*/
private int strstart;
/**
* lookahead is the number of characters starting at strstart in
* window that are valid.
* So window[strstart] until window[strstart+lookahead-1] are valid
* characters.
*/
private int lookahead;
/**
* This array contains the part of the uncompressed stream that
* is of relevance. The current character is indexed by strstart.
*/
private byte[] window;
private int strategy, max_chain, max_lazy, niceLength, goodLength;
/**
* The current compression function.
*/
private int comprFunc;
/**
* The input data for compression.
*/
private byte[] inputBuf;
/**
* The total bytes of input read.
*/
private int totalIn;
/**
* The offset into inputBuf, where input data starts.
*/
private int inputOff;
/**
* The end offset of the input data.
*/
private int inputEnd;
private DeflaterPending pending;
private DeflaterHuffman huffman;
/**
* The adler checksum
*/
private Adler32 adler;
/* DEFLATE ALGORITHM:
*
* The uncompressed stream is inserted into the window array. When
* the window array is full the first half is thrown away and the
* second half is copied to the beginning.
*
* The head array is a hash table. Three characters build a hash value
* and they the value points to the corresponding index in window of
* the last string with this hash. The prev array implements a
* linked list of matches with the same hash: prev[index & WMASK] points
* to the previous index with the same hash.
*
*
*/
DeflaterEngine(DeflaterPending pending) {
this.pending = pending;
huffman = new DeflaterHuffman(pending);
adler = new Adler32();
window = new byte[2 * WSIZE];
head = new short[HASH_SIZE];
prev = new short[WSIZE];
/* We start at index 1, to avoid a implementation deficiency, that
* we cannot build a repeat pattern at index 0.
*/
blockStart = strstart = 1;
}
public void reset() {
huffman.reset();
adler.reset();
blockStart = strstart = 1;
lookahead = 0;
totalIn = 0;
prevAvailable = false;
matchLen = MIN_MATCH - 1;
for (int i = 0; i < HASH_SIZE; i++)
head[i] = 0;
for (int i = 0; i < WSIZE; i++)
prev[i] = 0;
}
public final void resetAdler() {
adler.reset();
}
public final int getAdler() {
int chksum = (int) adler.getValue();
return chksum;
}
public final int getTotalIn() {
return totalIn;
}
public final void setStrategy(int strat) {
strategy = strat;
}
public void setLevel(int lvl) {
goodLength = DeflaterConstants.GOOD_LENGTH[lvl];
max_lazy = DeflaterConstants.MAX_LAZY[lvl];
niceLength = DeflaterConstants.NICE_LENGTH[lvl];
max_chain = DeflaterConstants.MAX_CHAIN[lvl];
if (DeflaterConstants.COMPR_FUNC[lvl] != comprFunc) {
if (DeflaterConstants.DEBUGGING)
System.err.println("Change from " + comprFunc + " to "
+ DeflaterConstants.COMPR_FUNC[lvl]);
switch (comprFunc) {
case DEFLATE_STORED:
if (strstart > blockStart) {
huffman.flushStoredBlock(window, blockStart,
strstart - blockStart, false);
blockStart = strstart;
}
updateHash();
break;
case DEFLATE_FAST:
if (strstart > blockStart) {
huffman.flushBlock(window, blockStart, strstart - blockStart,
false);
blockStart = strstart;
}
break;
case DEFLATE_SLOW:
if (prevAvailable)
huffman.tallyLit(window[strstart - 1] & 0xff);
if (strstart > blockStart) {
huffman.flushBlock(window, blockStart, strstart - blockStart,
false);
blockStart = strstart;
}
prevAvailable = false;
matchLen = MIN_MATCH - 1;
break;
}
comprFunc = COMPR_FUNC[lvl];
}
}
private final void updateHash() {
if (DEBUGGING)
System.err.println("updateHash: " + strstart);
ins_h = (window[strstart] << HASH_SHIFT) ^ window[strstart + 1];
}
/**
* Inserts the current string in the head hash and returns the previous
* value for this hash.
*/
private final int insertString() {
short match;
int hash = ((ins_h << HASH_SHIFT) ^ window[strstart + (MIN_MATCH - 1)])
& HASH_MASK;
if (DEBUGGING) {
if (hash != (((window[strstart] << (2 * HASH_SHIFT))
^ (window[strstart + 1] << HASH_SHIFT)
^ (window[strstart + 2])) & HASH_MASK))
throw new InternalError("hash inconsistent: " + hash + "/"
+ window[strstart] + ","
+ window[strstart + 1] + ","
+ window[strstart + 2] + "," + HASH_SHIFT);
}
prev[strstart & WMASK] = match = head[hash];
head[hash] = (short) strstart;
ins_h = hash;
return match & 0xffff;
}
private void slideWindow() {
System.arraycopy(window, WSIZE, window, 0, WSIZE);
matchStart -= WSIZE;
strstart -= WSIZE;
blockStart -= WSIZE;
/* Slide the hash table (could be avoided with 32 bit values
* at the expense of memory usage).
*/
for (int i = 0; i < HASH_SIZE; i++) {
int m = head[i] & 0xffff;
head[i] = m >= WSIZE ? (short) (m - WSIZE) : 0;
}
/* Slide the prev table.
*/
for (int i = 0; i < WSIZE; i++) {
int m = prev[i] & 0xffff;
prev[i] = m >= WSIZE ? (short) (m - WSIZE) : 0;
}
}
/**
* Fill the window when the lookahead becomes insufficient.
* Updates strstart and lookahead.
* <p>
* OUT assertions: strstart + lookahead <= 2*WSIZE
* lookahead >= MIN_LOOKAHEAD or inputOff == inputEnd
*/
private void fillWindow() {
/* If the window is almost full and there is insufficient lookahead,
* move the upper half to the lower one to make room in the upper half.
*/
if (strstart >= WSIZE + MAX_DIST)
slideWindow();
/* If there is not enough lookahead, but still some input left,
* read in the input
*/
while (lookahead < DeflaterConstants.MIN_LOOKAHEAD && inputOff < inputEnd) {
int more = 2 * WSIZE - lookahead - strstart;
if (more > inputEnd - inputOff)
more = inputEnd - inputOff;
System.arraycopy(inputBuf, inputOff,
window, strstart + lookahead, more);
adler.update(inputBuf, inputOff, more);
inputOff += more;
totalIn += more;
lookahead += more;
}
if (lookahead >= MIN_MATCH)
updateHash();
}
/**
* Find the best (longest) string in the window matching the
* string starting at strstart.
* <p>
* Preconditions:
* strstart + MAX_MATCH <= window.length.
*
* @param curMatch
*/
private boolean findLongestMatch(int curMatch) {
int chainLength = this.max_chain;
int niceLength = this.niceLength;
short[] prev = this.prev;
int scan = this.strstart;
int match;
int best_end = this.strstart + matchLen;
int best_len = Math.max(matchLen, MIN_MATCH - 1);
int limit = Math.max(strstart - MAX_DIST, 0);
int strend = scan + MAX_MATCH - 1;
byte scan_end1 = window[best_end - 1];
byte scan_end = window[best_end];
/* Do not waste too much time if we already have a good match: */
if (best_len >= this.goodLength)
chainLength >>= 2;
/* Do not look for matches beyond the end of the input. This is necessary
* to make deflate deterministic.
*/
if (niceLength > lookahead)
niceLength = lookahead;
if (DeflaterConstants.DEBUGGING
&& strstart > 2 * WSIZE - MIN_LOOKAHEAD)
throw new InternalError("need lookahead");
do {
if (DeflaterConstants.DEBUGGING && curMatch >= strstart)
throw new InternalError("future match");
if (window[curMatch + best_len] != scan_end
|| window[curMatch + best_len - 1] != scan_end1
|| window[curMatch] != window[scan]
|| window[curMatch + 1] != window[scan + 1])
continue;
match = curMatch + 2;
scan += 2;
/* We check for insufficient lookahead only every 8th comparison;
* the 256th check will be made at strstart+258.
*/
while (window[++scan] == window[++match]
&& window[++scan] == window[++match]
&& window[++scan] == window[++match]
&& window[++scan] == window[++match]
&& window[++scan] == window[++match]
&& window[++scan] == window[++match]
&& window[++scan] == window[++match]
&& window[++scan] == window[++match]
&& scan < strend) ;
if (scan > best_end) {
// if (DeflaterConstants.DEBUGGING && ins_h == 0)
// System.err.println("Found match: "+curMatch+"-"+(scan-strstart));
matchStart = curMatch;
best_end = scan;
best_len = scan - strstart;
if (best_len >= niceLength)
break;
scan_end1 = window[best_end - 1];
scan_end = window[best_end];
}
scan = strstart;
} while ((curMatch = (prev[curMatch & WMASK] & 0xffff)) > limit
&& --chainLength != 0);
matchLen = Math.min(best_len, lookahead);
return matchLen >= MIN_MATCH;
}
void setDictionary(byte[] buffer, int offset, int length) {
if (DeflaterConstants.DEBUGGING && strstart != 1)
throw new IllegalStateException("strstart not 1");
adler.update(buffer, offset, length);
if (length < MIN_MATCH)
return;
if (length > MAX_DIST) {
offset += length - MAX_DIST;
length = MAX_DIST;
}
System.arraycopy(buffer, offset, window, strstart, length);
updateHash();
length--;
while (--length > 0) {
insertString();
strstart++;
}
strstart += 2;
blockStart = strstart;
}
private boolean deflateStored(boolean flush, boolean finish) {
if (!flush && lookahead == 0)
return false;
strstart += lookahead;
lookahead = 0;
int storedLen = strstart - blockStart;
if ((storedLen >= DeflaterConstants.MAX_BLOCK_SIZE)
/* Block is full */
|| (blockStart < WSIZE && storedLen >= MAX_DIST)
/* Block may move out of window */
|| flush) {
boolean lastBlock = finish;
if (storedLen > DeflaterConstants.MAX_BLOCK_SIZE) {
storedLen = DeflaterConstants.MAX_BLOCK_SIZE;
lastBlock = false;
}
if (DeflaterConstants.DEBUGGING)
System.err.println("storedBlock[" + storedLen + "," + lastBlock + "]");
huffman.flushStoredBlock(window, blockStart, storedLen, lastBlock);
blockStart += storedLen;
return !lastBlock;
}
return true;
}
private boolean deflateFast(boolean flush, boolean finish) {
if (lookahead < MIN_LOOKAHEAD && !flush)
return false;
while (lookahead >= MIN_LOOKAHEAD || flush) {
if (lookahead == 0) {
/* We are flushing everything */
huffman.flushBlock(window, blockStart, strstart - blockStart,
finish);
blockStart = strstart;
return false;
}
if (strstart > 2 * WSIZE - MIN_LOOKAHEAD) {
/* slide window, as findLongestMatch need this.
* This should only happen when flushing and the window
* is almost full.
*/
slideWindow();
}
int hashHead;
if (lookahead >= MIN_MATCH
&& (hashHead = insertString()) != 0
&& strategy != Deflater.HUFFMAN_ONLY
&& strstart - hashHead <= MAX_DIST
&& findLongestMatch(hashHead)) {
/* longestMatch sets matchStart and matchLen */
if (DeflaterConstants.DEBUGGING) {
for (int i = 0; i < matchLen; i++) {
if (window[strstart + i] != window[matchStart + i])
throw new InternalError();
}
}
huffman.tallyDist(strstart - matchStart, matchLen);
lookahead -= matchLen;
if (matchLen <= max_lazy && lookahead >= MIN_MATCH) {
while (--matchLen > 0) {
strstart++;
insertString();
}
strstart++;
} else {
strstart += matchLen;
if (lookahead >= MIN_MATCH - 1)
updateHash();
}
matchLen = MIN_MATCH - 1;
continue;
} else {
/* No match found */
huffman.tallyLit(window[strstart] & 0xff);
strstart++;
lookahead--;
}
if (huffman.isFull()) {
boolean lastBlock = finish && lookahead == 0;
huffman.flushBlock(window, blockStart, strstart - blockStart,
lastBlock);
blockStart = strstart;
return !lastBlock;
}
}
return true;
}
private boolean deflateSlow(boolean flush, boolean finish) {
if (lookahead < MIN_LOOKAHEAD && !flush)
return false;
while (lookahead >= MIN_LOOKAHEAD || flush) {
if (lookahead == 0) {
if (prevAvailable)
huffman.tallyLit(window[strstart - 1] & 0xff);
prevAvailable = false;
/* We are flushing everything */
if (DeflaterConstants.DEBUGGING && !flush)
throw new InternalError("Not flushing, but no lookahead");
huffman.flushBlock(window, blockStart, strstart - blockStart,
finish);
blockStart = strstart;
return false;
}
if (strstart >= 2 * WSIZE - MIN_LOOKAHEAD) {
/* slide window, as findLongestMatch need this.
* This should only happen when flushing and the window
* is almost full.
*/
slideWindow();
}
int prevMatch = matchStart;
int prevLen = matchLen;
if (lookahead >= MIN_MATCH) {
int hashHead = insertString();
if (strategy != Deflater.HUFFMAN_ONLY
&& hashHead != 0 && strstart - hashHead <= MAX_DIST
&& findLongestMatch(hashHead)) {
/* longestMatch sets matchStart and matchLen */
/* Discard match if too small and too far away */
if (matchLen <= 5
&& (strategy == Deflater.FILTERED
|| (matchLen == MIN_MATCH
&& strstart - matchStart > TOO_FAR))) {
matchLen = MIN_MATCH - 1;
}
}
}
/* previous match was better */
if (prevLen >= MIN_MATCH && matchLen <= prevLen) {
if (DeflaterConstants.DEBUGGING) {
for (int i = 0; i < matchLen; i++) {
if (window[strstart - 1 + i] != window[prevMatch + i])
throw new InternalError();
}
}
huffman.tallyDist(strstart - 1 - prevMatch, prevLen);
prevLen -= 2;
do {
strstart++;
lookahead--;
if (lookahead >= MIN_MATCH)
insertString();
}
while (--prevLen > 0);
strstart++;
lookahead--;
prevAvailable = false;
matchLen = MIN_MATCH - 1;
} else {
if (prevAvailable)
huffman.tallyLit(window[strstart - 1] & 0xff);
prevAvailable = true;
strstart++;
lookahead--;
}
if (huffman.isFull()) {
int len = strstart - blockStart;
if (prevAvailable)
len--;
boolean lastBlock = (finish && lookahead == 0 && !prevAvailable);
huffman.flushBlock(window, blockStart, len, lastBlock);
blockStart += len;
return !lastBlock;
}
}
return true;
}
public boolean deflate(boolean flush, boolean finish) {
boolean progress;
do {
fillWindow();
boolean canFlush = flush && inputOff == inputEnd;
if (DeflaterConstants.DEBUGGING)
System.err.println("window: [" + blockStart + "," + strstart + ","
+ lookahead + "], " + comprFunc + "," + canFlush);
switch (comprFunc) {
case DEFLATE_STORED:
progress = deflateStored(canFlush, finish);
break;
case DEFLATE_FAST:
progress = deflateFast(canFlush, finish);
break;
case DEFLATE_SLOW:
progress = deflateSlow(canFlush, finish);
break;
default:
throw new InternalError();
}
}
while (pending.isFlushed() /* repeat while we have no pending output */
&& progress); /* and progress was made */
return progress;
}
public void setInput(byte[] buf, int off, int len) {
if (inputOff < inputEnd)
throw new IllegalStateException
("Old input was not completely processed");
int end = off + len;
/* We want to throw an ArrayIndexOutOfBoundsException early. The
* check is very tricky: it also handles integer wrap around.
*/
if (0 > off || off > end || end > buf.length)
throw new ArrayIndexOutOfBoundsException();
inputBuf = buf;
inputOff = off;
inputEnd = end;
}
public final boolean needsInput() {
return inputEnd == inputOff;
}
}

@ -0,0 +1,704 @@
/* me.ag2s.epublib.zip.DeflaterHuffman
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/**
* This is the DeflaterHuffman class.
* <p>
* This class is <i>not</i> thread safe. This is inherent in the API, due
* to the split of deflate and setInput.
*
* @author Jochen Hoenicke
* @date Jan 6, 2000
*/
class DeflaterHuffman {
private static final int BUFSIZE = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6);
private static final int LITERAL_NUM = 286;
private static final int DIST_NUM = 30;
private static final int BITLEN_NUM = 19;
private static final int REP_3_6 = 16;
private static final int REP_3_10 = 17;
private static final int REP_11_138 = 18;
private static final int EOF_SYMBOL = 256;
private static final int[] BL_ORDER =
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
private final static String bit4Reverse =
"\000\010\004\014\002\012\006\016\001\011\005\015\003\013\007\017";
class Tree {
short[] freqs;
short[] codes;
byte[] length;
int[] bl_counts;
int minNumCodes, numCodes;
int maxLength;
Tree(int elems, int minCodes, int maxLength) {
this.minNumCodes = minCodes;
this.maxLength = maxLength;
freqs = new short[elems];
bl_counts = new int[maxLength];
}
void reset() {
for (int i = 0; i < freqs.length; i++)
freqs[i] = 0;
codes = null;
length = null;
}
final void writeSymbol(int code) {
if (DeflaterConstants.DEBUGGING) {
freqs[code]--;
// System.err.print("writeSymbol("+freqs.length+","+code+"): ");
}
pending.writeBits(codes[code] & 0xffff, length[code]);
}
final void checkEmpty() {
boolean empty = true;
for (int i = 0; i < freqs.length; i++)
if (freqs[i] != 0) {
System.err.println("freqs[" + i + "] == " + freqs[i]);
empty = false;
}
if (!empty)
throw new InternalError();
System.err.println("checkEmpty suceeded!");
}
void setStaticCodes(short[] stCodes, byte[] stLength) {
codes = stCodes;
length = stLength;
}
public void buildCodes() {
int[] nextCode = new int[maxLength];
int code = 0;
codes = new short[freqs.length];
if (DeflaterConstants.DEBUGGING)
System.err.println("buildCodes: " + freqs.length);
for (int bits = 0; bits < maxLength; bits++) {
nextCode[bits] = code;
code += bl_counts[bits] << (15 - bits);
if (DeflaterConstants.DEBUGGING)
System.err.println("bits: " + (bits + 1) + " count: " + bl_counts[bits]
+ " nextCode: " + Integer.toHexString(code));
}
if (DeflaterConstants.DEBUGGING && code != 65536)
throw new RuntimeException("Inconsistent bl_counts!");
for (int i = 0; i < numCodes; i++) {
int bits = length[i];
if (bits > 0) {
if (DeflaterConstants.DEBUGGING)
System.err.println("codes[" + i + "] = rev("
+ Integer.toHexString(nextCode[bits - 1]) + "),"
+ bits);
codes[i] = bitReverse(nextCode[bits - 1]);
nextCode[bits - 1] += 1 << (16 - bits);
}
}
}
private void buildLength(int childs[]) {
this.length = new byte[freqs.length];
int numNodes = childs.length / 2;
int numLeafs = (numNodes + 1) / 2;
int overflow = 0;
for (int i = 0; i < maxLength; i++)
bl_counts[i] = 0;
/* First calculate optimal bit lengths */
int lengths[] = new int[numNodes];
lengths[numNodes - 1] = 0;
for (int i = numNodes - 1; i >= 0; i--) {
if (childs[2 * i + 1] != -1) {
int bitLength = lengths[i] + 1;
if (bitLength > maxLength) {
bitLength = maxLength;
overflow++;
}
lengths[childs[2 * i]] = lengths[childs[2 * i + 1]] = bitLength;
} else {
/* A leaf node */
int bitLength = lengths[i];
bl_counts[bitLength - 1]++;
this.length[childs[2 * i]] = (byte) lengths[i];
}
}
if (DeflaterConstants.DEBUGGING) {
System.err.println("Tree " + freqs.length + " lengths:");
for (int i = 0; i < numLeafs; i++)
System.err.println("Node " + childs[2 * i] + " freq: " + freqs[childs[2 * i]]
+ " len: " + length[childs[2 * i]]);
}
if (overflow == 0)
return;
int incrBitLen = maxLength - 1;
do {
/* Find the first bit length which could increase: */
while (bl_counts[--incrBitLen] == 0)
;
/* Move this node one down and remove a corresponding
* amount of overflow nodes.
*/
do {
bl_counts[incrBitLen]--;
bl_counts[++incrBitLen]++;
overflow -= 1 << (maxLength - 1 - incrBitLen);
}
while (overflow > 0 && incrBitLen < maxLength - 1);
}
while (overflow > 0);
/* We may have overshot above. Move some nodes from maxLength to
* maxLength-1 in that case.
*/
bl_counts[maxLength - 1] += overflow;
bl_counts[maxLength - 2] -= overflow;
/* Now recompute all bit lengths, scanning in increasing
* frequency. It is simpler to reconstruct all lengths instead of
* fixing only the wrong ones. This idea is taken from 'ar'
* written by Haruhiko Okumura.
*
* The nodes were inserted with decreasing frequency into the childs
* array.
*/
int nodePtr = 2 * numLeafs;
for (int bits = maxLength; bits != 0; bits--) {
int n = bl_counts[bits - 1];
while (n > 0) {
int childPtr = 2 * childs[nodePtr++];
if (childs[childPtr + 1] == -1) {
/* We found another leaf */
length[childs[childPtr]] = (byte) bits;
n--;
}
}
}
if (DeflaterConstants.DEBUGGING) {
System.err.println("*** After overflow elimination. ***");
for (int i = 0; i < numLeafs; i++)
System.err.println("Node " + childs[2 * i] + " freq: " + freqs[childs[2 * i]]
+ " len: " + length[childs[2 * i]]);
}
}
void buildTree() {
int numSymbols = freqs.length;
/* heap is a priority queue, sorted by frequency, least frequent
* nodes first. The heap is a binary tree, with the property, that
* the parent node is smaller than both child nodes. This assures
* that the smallest node is the first parent.
*
* The binary tree is encoded in an array: 0 is root node and
* the nodes 2*n+1, 2*n+2 are the child nodes of node n.
*/
int[] heap = new int[numSymbols];
int heapLen = 0;
int maxCode = 0;
for (int n = 0; n < numSymbols; n++) {
int freq = freqs[n];
if (freq != 0) {
/* Insert n into heap */
int pos = heapLen++;
int ppos;
while (pos > 0 &&
freqs[heap[ppos = (pos - 1) / 2]] > freq) {
heap[pos] = heap[ppos];
pos = ppos;
}
heap[pos] = n;
maxCode = n;
}
}
/* We could encode a single literal with 0 bits but then we
* don't see the literals. Therefore we force at least two
* literals to avoid this case. We don't care about order in
* this case, both literals get a 1 bit code.
*/
while (heapLen < 2) {
int node = maxCode < 2 ? ++maxCode : 0;
heap[heapLen++] = node;
}
numCodes = Math.max(maxCode + 1, minNumCodes);
int numLeafs = heapLen;
int[] childs = new int[4 * heapLen - 2];
int[] values = new int[2 * heapLen - 1];
int numNodes = numLeafs;
for (int i = 0; i < heapLen; i++) {
int node = heap[i];
childs[2 * i] = node;
childs[2 * i + 1] = -1;
values[i] = freqs[node] << 8;
heap[i] = i;
}
/* Construct the Huffman tree by repeatedly combining the least two
* frequent nodes.
*/
do {
int first = heap[0];
int last = heap[--heapLen];
/* Propagate the hole to the leafs of the heap */
int ppos = 0;
int path = 1;
while (path < heapLen) {
if (path + 1 < heapLen
&& values[heap[path]] > values[heap[path + 1]])
path++;
heap[ppos] = heap[path];
ppos = path;
path = path * 2 + 1;
}
/* Now propagate the last element down along path. Normally
* it shouldn't go too deep.
*/
int lastVal = values[last];
while ((path = ppos) > 0
&& values[heap[ppos = (path - 1) / 2]] > lastVal)
heap[path] = heap[ppos];
heap[path] = last;
int second = heap[0];
/* Create a new node father of first and second */
last = numNodes++;
childs[2 * last] = first;
childs[2 * last + 1] = second;
int mindepth = Math.min(values[first] & 0xff, values[second] & 0xff);
values[last] = lastVal = values[first] + values[second] - mindepth + 1;
/* Again, propagate the hole to the leafs */
ppos = 0;
path = 1;
while (path < heapLen) {
if (path + 1 < heapLen
&& values[heap[path]] > values[heap[path + 1]])
path++;
heap[ppos] = heap[path];
ppos = path;
path = ppos * 2 + 1;
}
/* Now propagate the new element down along path */
while ((path = ppos) > 0
&& values[heap[ppos = (path - 1) / 2]] > lastVal)
heap[path] = heap[ppos];
heap[path] = last;
}
while (heapLen > 1);
if (heap[0] != childs.length / 2 - 1)
throw new RuntimeException("Weird!");
buildLength(childs);
}
int getEncodedLength() {
int len = 0;
for (int i = 0; i < freqs.length; i++)
len += freqs[i] * length[i];
return len;
}
void calcBLFreq(Tree blTree) {
int max_count; /* max repeat count */
int min_count; /* min repeat count */
int count; /* repeat count of the current code */
int curlen = -1; /* length of current code */
int i = 0;
while (i < numCodes) {
count = 1;
int nextlen = length[i];
if (nextlen == 0) {
max_count = 138;
min_count = 3;
} else {
max_count = 6;
min_count = 3;
if (curlen != nextlen) {
blTree.freqs[nextlen]++;
count = 0;
}
}
curlen = nextlen;
i++;
while (i < numCodes && curlen == length[i]) {
i++;
if (++count >= max_count)
break;
}
if (count < min_count)
blTree.freqs[curlen] += count;
else if (curlen != 0)
blTree.freqs[REP_3_6]++;
else if (count <= 10)
blTree.freqs[REP_3_10]++;
else
blTree.freqs[REP_11_138]++;
}
}
void writeTree(Tree blTree) {
int max_count; /* max repeat count */
int min_count; /* min repeat count */
int count; /* repeat count of the current code */
int curlen = -1; /* length of current code */
int i = 0;
while (i < numCodes) {
count = 1;
int nextlen = length[i];
if (nextlen == 0) {
max_count = 138;
min_count = 3;
} else {
max_count = 6;
min_count = 3;
if (curlen != nextlen) {
blTree.writeSymbol(nextlen);
count = 0;
}
}
curlen = nextlen;
i++;
while (i < numCodes && curlen == length[i]) {
i++;
if (++count >= max_count)
break;
}
if (count < min_count) {
while (count-- > 0)
blTree.writeSymbol(curlen);
} else if (curlen != 0) {
blTree.writeSymbol(REP_3_6);
pending.writeBits(count - 3, 2);
} else if (count <= 10) {
blTree.writeSymbol(REP_3_10);
pending.writeBits(count - 3, 3);
} else {
blTree.writeSymbol(REP_11_138);
pending.writeBits(count - 11, 7);
}
}
}
}
DeflaterPending pending;
private Tree literalTree, distTree, blTree;
private short d_buf[];
private byte l_buf[];
private int last_lit;
private int extra_bits;
private static short staticLCodes[];
private static byte staticLLength[];
private static short staticDCodes[];
private static byte staticDLength[];
/**
* Reverse the bits of a 16 bit value.
*/
static short bitReverse(int value) {
return (short) (bit4Reverse.charAt(value & 0xf) << 12
| bit4Reverse.charAt((value >> 4) & 0xf) << 8
| bit4Reverse.charAt((value >> 8) & 0xf) << 4
| bit4Reverse.charAt(value >> 12));
}
static {
/* See RFC 1951 3.2.6 */
/* Literal codes */
staticLCodes = new short[LITERAL_NUM];
staticLLength = new byte[LITERAL_NUM];
int i = 0;
while (i < 144) {
staticLCodes[i] = bitReverse((0x030 + i) << 8);
staticLLength[i++] = 8;
}
while (i < 256) {
staticLCodes[i] = bitReverse((0x190 - 144 + i) << 7);
staticLLength[i++] = 9;
}
while (i < 280) {
staticLCodes[i] = bitReverse((0x000 - 256 + i) << 9);
staticLLength[i++] = 7;
}
while (i < LITERAL_NUM) {
staticLCodes[i] = bitReverse((0x0c0 - 280 + i) << 8);
staticLLength[i++] = 8;
}
/* Distant codes */
staticDCodes = new short[DIST_NUM];
staticDLength = new byte[DIST_NUM];
for (i = 0; i < DIST_NUM; i++) {
staticDCodes[i] = bitReverse(i << 11);
staticDLength[i] = 5;
}
}
public DeflaterHuffman(DeflaterPending pending) {
this.pending = pending;
literalTree = new Tree(LITERAL_NUM, 257, 15);
distTree = new Tree(DIST_NUM, 1, 15);
blTree = new Tree(BITLEN_NUM, 4, 7);
d_buf = new short[BUFSIZE];
l_buf = new byte[BUFSIZE];
}
public final void reset() {
last_lit = 0;
extra_bits = 0;
literalTree.reset();
distTree.reset();
blTree.reset();
}
private final int l_code(int len) {
if (len == 255)
return 285;
int code = 257;
while (len >= 8) {
code += 4;
len >>= 1;
}
return code + len;
}
private final int d_code(int distance) {
int code = 0;
while (distance >= 4) {
code += 2;
distance >>= 1;
}
return code + distance;
}
public void sendAllTrees(int blTreeCodes) {
blTree.buildCodes();
literalTree.buildCodes();
distTree.buildCodes();
pending.writeBits(literalTree.numCodes - 257, 5);
pending.writeBits(distTree.numCodes - 1, 5);
pending.writeBits(blTreeCodes - 4, 4);
for (int rank = 0; rank < blTreeCodes; rank++)
pending.writeBits(blTree.length[BL_ORDER[rank]], 3);
literalTree.writeTree(blTree);
distTree.writeTree(blTree);
if (DeflaterConstants.DEBUGGING)
blTree.checkEmpty();
}
public void compressBlock() {
for (int i = 0; i < last_lit; i++) {
int litlen = l_buf[i] & 0xff;
int dist = d_buf[i];
if (dist-- != 0) {
if (DeflaterConstants.DEBUGGING)
System.err.print("[" + (dist + 1) + "," + (litlen + 3) + "]: ");
int lc = l_code(litlen);
literalTree.writeSymbol(lc);
int bits = (lc - 261) / 4;
if (bits > 0 && bits <= 5)
pending.writeBits(litlen & ((1 << bits) - 1), bits);
int dc = d_code(dist);
distTree.writeSymbol(dc);
bits = dc / 2 - 1;
if (bits > 0)
pending.writeBits(dist & ((1 << bits) - 1), bits);
} else {
if (DeflaterConstants.DEBUGGING) {
if (litlen > 32 && litlen < 127)
System.err.print("(" + (char) litlen + "): ");
else
System.err.print("{" + litlen + "}: ");
}
literalTree.writeSymbol(litlen);
}
}
if (DeflaterConstants.DEBUGGING)
System.err.print("EOF: ");
literalTree.writeSymbol(EOF_SYMBOL);
if (DeflaterConstants.DEBUGGING) {
literalTree.checkEmpty();
distTree.checkEmpty();
}
}
public void flushStoredBlock(byte[] stored,
int stored_offset, int stored_len,
boolean lastBlock) {
if (DeflaterConstants.DEBUGGING)
System.err.println("Flushing stored block " + stored_len);
pending.writeBits((DeflaterConstants.STORED_BLOCK << 1)
+ (lastBlock ? 1 : 0), 3);
pending.alignToByte();
pending.writeShort(stored_len);
pending.writeShort(~stored_len);
pending.writeBlock(stored, stored_offset, stored_len);
reset();
}
public void flushBlock(byte[] stored, int stored_offset, int stored_len,
boolean lastBlock) {
literalTree.freqs[EOF_SYMBOL]++;
/* Build trees */
literalTree.buildTree();
distTree.buildTree();
/* Calculate bitlen frequency */
literalTree.calcBLFreq(blTree);
distTree.calcBLFreq(blTree);
/* Build bitlen tree */
blTree.buildTree();
int blTreeCodes = 4;
for (int i = 18; i > blTreeCodes; i--) {
if (blTree.length[BL_ORDER[i]] > 0)
blTreeCodes = i + 1;
}
int opt_len = 14 + blTreeCodes * 3 + blTree.getEncodedLength()
+ literalTree.getEncodedLength() + distTree.getEncodedLength()
+ extra_bits;
int static_len = extra_bits;
for (int i = 0; i < LITERAL_NUM; i++)
static_len += literalTree.freqs[i] * staticLLength[i];
for (int i = 0; i < DIST_NUM; i++)
static_len += distTree.freqs[i] * staticDLength[i];
if (opt_len >= static_len) {
/* Force static trees */
opt_len = static_len;
}
if (stored_offset >= 0 && stored_len + 4 < opt_len >> 3) {
/* Store Block */
if (DeflaterConstants.DEBUGGING)
System.err.println("Storing, since " + stored_len + " < " + opt_len
+ " <= " + static_len);
flushStoredBlock(stored, stored_offset, stored_len, lastBlock);
} else if (opt_len == static_len) {
/* Encode with static tree */
pending.writeBits((DeflaterConstants.STATIC_TREES << 1)
+ (lastBlock ? 1 : 0), 3);
literalTree.setStaticCodes(staticLCodes, staticLLength);
distTree.setStaticCodes(staticDCodes, staticDLength);
compressBlock();
reset();
} else {
/* Encode with dynamic tree */
pending.writeBits((DeflaterConstants.DYN_TREES << 1)
+ (lastBlock ? 1 : 0), 3);
sendAllTrees(blTreeCodes);
compressBlock();
reset();
}
}
public final boolean isFull() {
return last_lit == BUFSIZE;
}
public final boolean tallyLit(int lit) {
if (DeflaterConstants.DEBUGGING) {
if (lit > 32 && lit < 127)
System.err.println("(" + (char) lit + ")");
else
System.err.println("{" + lit + "}");
}
d_buf[last_lit] = 0;
l_buf[last_lit++] = (byte) lit;
literalTree.freqs[lit]++;
return last_lit == BUFSIZE;
}
public final boolean tallyDist(int dist, int len) {
if (DeflaterConstants.DEBUGGING)
System.err.println("[" + dist + "," + len + "]");
d_buf[last_lit] = (short) dist;
l_buf[last_lit++] = (byte) (len - 3);
int lc = l_code(len - 3);
literalTree.freqs[lc]++;
if (lc >= 265 && lc < 285)
extra_bits += (lc - 261) / 4;
int dc = d_code(dist - 1);
distTree.freqs[dc]++;
if (dc >= 4)
extra_bits += dc / 2 - 1;
return last_lit == BUFSIZE;
}
}

@ -0,0 +1,190 @@
/* DeflaterOutputStream.java - Output filter for compressing.
Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/* Written using on-line Java Platform 1.2 API Specification
* and JCL book.
* Believed complete and correct.
*/
/**
* This is a special FilterOutputStream deflating the bytes that are
* written through it. It uses the Deflater for deflating.
* <p>
* A special thing to be noted is that flush() doesn't flush
* everything in Sun's JDK, but it does so in jazzlib. This is because
* Sun's Deflater doesn't have a way to flush() everything, without
* finishing the stream.
*
* @author Tom Tromey, Jochen Hoenicke
* @date Jan 11, 2001
*/
public class DeflaterOutputStream extends FilterOutputStream {
/**
* This buffer is used temporarily to retrieve the bytes from the
* deflater and write them to the underlying output stream.
*/
protected byte[] buf;
/**
* The deflater which is used to deflate the stream.
*/
protected Deflater def;
/**
* Deflates everything in the def's input buffers. This will call
* <code>def.deflate()</code> until all bytes from the input buffers
* are processed.
*/
protected void deflate() throws IOException {
while (!def.needsInput()) {
int len = def.deflate(buf, 0, buf.length);
// System.err.println("DOS deflated " + len + " out of " + buf.length);
if (len <= 0)
break;
out.write(buf, 0, len);
}
if (!def.needsInput())
throw new InternalError("Can't deflate all input?");
}
/**
* Creates a new DeflaterOutputStream with a default Deflater and
* default buffer size.
*
* @param out the output stream where deflated output should be written.
*/
public DeflaterOutputStream(OutputStream out) {
this(out, new Deflater(), 512);
}
/**
* Creates a new DeflaterOutputStream with the given Deflater and
* default buffer size.
*
* @param out the output stream where deflated output should be written.
* @param defl the underlying deflater.
*/
public DeflaterOutputStream(OutputStream out, Deflater defl) {
this(out, defl, 512);
}
/**
* Creates a new DeflaterOutputStream with the given Deflater and
* buffer size.
*
* @param out the output stream where deflated output should be written.
* @param defl the underlying deflater.
* @param bufsize the buffer size.
* @throws IllegalArgumentException if bufsize isn't positive.
*/
public DeflaterOutputStream(OutputStream out, Deflater defl, int bufsize) {
super(out);
if (bufsize <= 0)
throw new IllegalArgumentException("bufsize <= 0");
buf = new byte[bufsize];
def = defl;
}
/**
* Flushes the stream by calling flush() on the deflater and then
* on the underlying stream. This ensures that all bytes are
* flushed. This function doesn't work in Sun's JDK, but only in
* jazzlib.
*/
public void flush() throws IOException {
def.flush();
deflate();
out.flush();
}
/**
* Finishes the stream by calling finish() on the deflater. This
* was the only way to ensure that all bytes are flushed in Sun's
* JDK.
*/
public void finish() throws IOException {
def.finish();
while (!def.finished()) {
int len = def.deflate(buf, 0, buf.length);
if (len <= 0)
break;
out.write(buf, 0, len);
}
if (!def.finished())
throw new InternalError("Can't deflate all input?");
out.flush();
}
/**
* Calls finish () and closes the stream.
*/
public void close() throws IOException {
finish();
out.close();
}
/**
* Writes a single byte to the compressed output stream.
*
* @param bval the byte value.
*/
public void write(int bval) throws IOException {
byte[] b = new byte[1];
b[0] = (byte) bval;
write(b, 0, 1);
}
/**
* Writes a len bytes from an array to the compressed stream.
*
* @param buf the byte array.
* @param off the offset into the byte array where to start.
* @param len the number of bytes to write.
*/
public void write(byte[] buf, int off, int len) throws IOException {
def.setInput(buf, off, len);
deflate();
}
}

@ -0,0 +1,52 @@
/* me.ag2s.epublib.zip.DeflaterPending
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/**
* This class stores the pending output of the Deflater.
*
* @author Jochen Hoenicke
* @date Jan 5, 2000
*/
class DeflaterPending extends PendingBuffer {
public DeflaterPending() {
super(DeflaterConstants.PENDING_BUF_SIZE);
}
}

@ -0,0 +1,338 @@
/* GZIPInputStream.java - Input filter for reading gzip file
Copyright (C) 1999, 2000, 2001, 2002, 2004 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* This filter stream is used to decompress a "GZIP" format stream.
* The "GZIP" format is described in RFC 1952.
*
* @author John Leuner
* @author Tom Tromey
* @since JDK 1.1
*/
public class GZIPInputStream
extends InflaterInputStream {
/**
* The magic number found at the start of a GZIP stream.
*/
public static final int GZIP_MAGIC = 0x1f8b;
/**
* The mask for bit 0 of the flag byte.
*/
static final int FTEXT = 0x1;
/**
* The mask for bit 1 of the flag byte.
*/
static final int FHCRC = 0x2;
/**
* The mask for bit 2 of the flag byte.
*/
static final int FEXTRA = 0x4;
/**
* The mask for bit 3 of the flag byte.
*/
static final int FNAME = 0x8;
/**
* The mask for bit 4 of the flag byte.
*/
static final int FCOMMENT = 0x10;
/**
* The CRC-32 checksum value for uncompressed data.
*/
protected CRC32 crc;
/**
* Indicates whether or not the end of the stream has been reached.
*/
protected boolean eos;
/**
* Indicates whether or not the GZIP header has been read in.
*/
private boolean readGZIPHeader;
/**
* Creates a GZIPInputStream with the default buffer size.
*
* @param in The stream to read compressed data from
* (in GZIP format).
* @throws IOException if an error occurs during an I/O operation.
*/
public GZIPInputStream(InputStream in)
throws IOException {
this(in, 4096);
}
/**
* Creates a GZIPInputStream with the specified buffer size.
*
* @param in The stream to read compressed data from
* (in GZIP format).
* @param size The size of the buffer to use.
* @throws IOException if an error occurs during an I/O operation.
* @throws IllegalArgumentException if <code>size</code>
* is less than or equal to 0.
*/
public GZIPInputStream(InputStream in, int size)
throws IOException {
super(in, new Inflater(true), size);
crc = new CRC32();
}
/**
* Closes the input stream.
*
* @throws IOException if an error occurs during an I/O operation.
*/
public void close()
throws IOException {
// Nothing to do here.
super.close();
}
/**
* Reads in GZIP-compressed data and stores it in uncompressed form
* into an array of bytes. The method will block until either
* enough input data becomes available or the compressed stream
* reaches its end.
*
* @param buf the buffer into which the uncompressed data will
* be stored.
* @param offset the offset indicating where in <code>buf</code>
* the uncompressed data should be placed.
* @param len the number of uncompressed bytes to be read.
*/
public int read(byte[] buf, int offset, int len) throws IOException {
// We first have to slurp in the GZIP header, then we feed all the
// rest of the data to the superclass.
//
// As we do that we continually update the CRC32. Once the data is
// finished, we check the CRC32.
//
// This means we don't need our own buffer, as everything is done
// in the superclass.
if (!readGZIPHeader)
readHeader();
if (eos)
return -1;
// System.err.println("GZIPIS.read(byte[], off, len ... " + offset + " and len " + len);
/* We don't have to read the header,
* so we just grab data from the superclass.
*/
int numRead = super.read(buf, offset, len);
if (numRead > 0)
crc.update(buf, offset, numRead);
if (inf.finished())
readFooter();
return numRead;
}
/**
* Reads in the GZIP header.
*/
private void readHeader() throws IOException {
/* 1. Check the two magic bytes */
CRC32 headCRC = new CRC32();
int magic = in.read();
if (magic < 0) {
eos = true;
return;
}
headCRC.update(magic);
if (magic != (GZIP_MAGIC >> 8))
throw new IOException("Error in GZIP header, first byte doesn't match");
magic = in.read();
if (magic != (GZIP_MAGIC & 0xff))
throw new IOException("Error in GZIP header, second byte doesn't match");
headCRC.update(magic);
/* 2. Check the compression type (must be 8) */
int CM = in.read();
if (CM != 8)
throw new IOException("Error in GZIP header, data not in deflate format");
headCRC.update(CM);
/* 3. Check the flags */
int flags = in.read();
if (flags < 0)
throw new EOFException("Early EOF in GZIP header");
headCRC.update(flags);
/* This flag byte is divided into individual bits as follows:
bit 0 FTEXT
bit 1 FHCRC
bit 2 FEXTRA
bit 3 FNAME
bit 4 FCOMMENT
bit 5 reserved
bit 6 reserved
bit 7 reserved
*/
/* 3.1 Check the reserved bits are zero */
if ((flags & 0xd0) != 0)
throw new IOException("Reserved flag bits in GZIP header != 0");
/* 4.-6. Skip the modification time, extra flags, and OS type */
for (int i = 0; i < 6; i++) {
int readByte = in.read();
if (readByte < 0)
throw new EOFException("Early EOF in GZIP header");
headCRC.update(readByte);
}
/* 7. Read extra field */
if ((flags & FEXTRA) != 0) {
/* Skip subfield id */
for (int i = 0; i < 2; i++) {
int readByte = in.read();
if (readByte < 0)
throw new EOFException("Early EOF in GZIP header");
headCRC.update(readByte);
}
if (in.read() < 0 || in.read() < 0)
throw new EOFException("Early EOF in GZIP header");
int len1, len2, extraLen;
len1 = in.read();
len2 = in.read();
if ((len1 < 0) || (len2 < 0))
throw new EOFException("Early EOF in GZIP header");
headCRC.update(len1);
headCRC.update(len2);
extraLen = (len1 << 8) | len2;
for (int i = 0; i < extraLen; i++) {
int readByte = in.read();
if (readByte < 0)
throw new EOFException("Early EOF in GZIP header");
headCRC.update(readByte);
}
}
/* 8. Read file name */
if ((flags & FNAME) != 0) {
int readByte;
while ((readByte = in.read()) > 0)
headCRC.update(readByte);
if (readByte < 0)
throw new EOFException("Early EOF in GZIP file name");
headCRC.update(readByte);
}
/* 9. Read comment */
if ((flags & FCOMMENT) != 0) {
int readByte;
while ((readByte = in.read()) > 0)
headCRC.update(readByte);
if (readByte < 0)
throw new EOFException("Early EOF in GZIP comment");
headCRC.update(readByte);
}
/* 10. Read header CRC */
if ((flags & FHCRC) != 0) {
int tempByte;
int crcval = in.read();
if (crcval < 0)
throw new EOFException("Early EOF in GZIP header");
tempByte = in.read();
if (tempByte < 0)
throw new EOFException("Early EOF in GZIP header");
crcval = (crcval << 8) | tempByte;
if (crcval != ((int) headCRC.getValue() & 0xffff))
throw new IOException("Header CRC value mismatch");
}
readGZIPHeader = true;
//System.err.println("Read GZIP header");
}
private void readFooter() throws IOException {
byte[] footer = new byte[8];
int avail = inf.getRemaining();
if (avail > 8)
avail = 8;
System.arraycopy(buf, len - inf.getRemaining(), footer, 0, avail);
int needed = 8 - avail;
while (needed > 0) {
int count = in.read(footer, 8 - needed, needed);
if (count <= 0)
throw new EOFException("Early EOF in GZIP footer");
needed -= count; //Jewel Jan 16
}
int crcval = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8)
| ((footer[2] & 0xff) << 16) | (footer[3] << 24);
if (crcval != (int) crc.getValue())
throw new IOException("GZIP crc sum mismatch, theirs \""
+ Integer.toHexString(crcval)
+ "\" and ours \""
+ Integer.toHexString((int) crc.getValue()));
int total = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8)
| ((footer[6] & 0xff) << 16) | (footer[7] << 24);
if (total != inf.getTotalOut())
throw new IOException("Number of bytes mismatch");
/* FIXME" XXX Should we support multiple members.
* Difficult, since there may be some bytes still in buf
*/
eos = true;
}
}

@ -0,0 +1,146 @@
/* GZIPOutputStream.java - Create a file in gzip format
Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.IOException;
import java.io.OutputStream;
/**
* This filter stream is used to compress a stream into a "GZIP" stream.
* The "GZIP" format is described in RFC 1952.
*
* @author John Leuner
* @author Tom Tromey
* @since JDK 1.1
*/
/* Written using on-line Java Platform 1.2 API Specification
* and JCL book.
* Believed complete and correct.
*/
public class GZIPOutputStream extends DeflaterOutputStream {
/**
* CRC-32 value for uncompressed data
*/
protected CRC32 crc;
/* Creates a GZIPOutputStream with the default buffer size
*
*
* @param out The stream to read data (to be compressed) from
*
*/
public GZIPOutputStream(OutputStream out) throws IOException {
this(out, 4096);
}
/**
* Creates a GZIPOutputStream with the specified buffer size
*
* @param out The stream to read compressed data from
* @param size Size of the buffer to use
*/
public GZIPOutputStream(OutputStream out, int size) throws IOException {
super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size);
crc = new CRC32();
int mod_time = (int) (System.currentTimeMillis() / 1000L);
byte[] gzipHeader =
{
/* The two magic bytes */
(byte) (GZIPInputStream.GZIP_MAGIC >> 8),
(byte) GZIPInputStream.GZIP_MAGIC,
/* The compression type */
(byte) Deflater.DEFLATED,
/* The flags (not set) */
0,
/* The modification time */
(byte) mod_time, (byte) (mod_time >> 8),
(byte) (mod_time >> 16), (byte) (mod_time >> 24),
/* The extra flags */
0,
/* The OS type (unknown) */
(byte) 255
};
out.write(gzipHeader);
// System.err.println("wrote GZIP header (" + gzipHeader.length + " bytes )");
}
public synchronized void write(byte[] buf, int off, int len)
throws IOException {
super.write(buf, off, len);
crc.update(buf, off, len);
}
/**
* Writes remaining compressed output data to the output stream
* and closes it.
*/
public void close() throws IOException {
finish();
out.close();
}
public void finish() throws IOException {
super.finish();
int totalin = def.getTotalIn();
int crcval = (int) (crc.getValue() & 0xffffffff);
// System.err.println("CRC val is " + Integer.toHexString( crcval ) + " and length " + Integer.toHexString(totalin));
byte[] gzipFooter =
{
(byte) crcval, (byte) (crcval >> 8),
(byte) (crcval >> 16), (byte) (crcval >> 24),
(byte) totalin, (byte) (totalin >> 8),
(byte) (totalin >> 16), (byte) (totalin >> 24)
};
out.write(gzipFooter);
// System.err.println("wrote GZIP trailer (" + gzipFooter.length + " bytes )");
}
}

@ -0,0 +1,681 @@
/* Inflater.java - Decompress a data stream
Copyright (C) 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/* Written using on-line Java Platform 1.2 API Specification
* and JCL book.
* Believed complete and correct.
*/
/**
* Inflater is used to decompress data that has been compressed according
* to the "deflate" standard described in rfc1950.
* <p>
* The usage is as following. First you have to set some input with
* <code>setInput()</code>, then inflate() it. If inflate doesn't
* inflate any bytes there may be three reasons:
* <ul>
* <li>needsInput() returns true because the input buffer is empty.
* You have to provide more input with <code>setInput()</code>.
* NOTE: needsInput() also returns true when, the stream is finished.
* </li>
* <li>needsDictionary() returns true, you have to provide a preset
* dictionary with <code>setDictionary()</code>.</li>
* <li>finished() returns true, the inflater has finished.</li>
* </ul>
* Once the first output byte is produced, a dictionary will not be
* needed at a later stage.
*
* @author John Leuner, Jochen Hoenicke
* @author Tom Tromey
* @date May 17, 1999
* @since JDK 1.1
*/
public class Inflater {
/* Copy lengths for literal codes 257..285 */
private static final int CPLENS[] =
{
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
};
/* Extra bits for literal codes 257..285 */
private static final int CPLEXT[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
};
/* Copy offsets for distance codes 0..29 */
private static final int CPDIST[] = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
8193, 12289, 16385, 24577
};
/* Extra bits for distance codes */
private static final int CPDEXT[] = {
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 13, 13
};
/* This are the state in which the inflater can be. */
private static final int DECODE_HEADER = 0;
private static final int DECODE_DICT = 1;
private static final int DECODE_BLOCKS = 2;
private static final int DECODE_STORED_LEN1 = 3;
private static final int DECODE_STORED_LEN2 = 4;
private static final int DECODE_STORED = 5;
private static final int DECODE_DYN_HEADER = 6;
private static final int DECODE_HUFFMAN = 7;
private static final int DECODE_HUFFMAN_LENBITS = 8;
private static final int DECODE_HUFFMAN_DIST = 9;
private static final int DECODE_HUFFMAN_DISTBITS = 10;
private static final int DECODE_CHKSUM = 11;
private static final int FINISHED = 12;
/**
* This variable contains the current state.
*/
private int mode;
/**
* The adler checksum of the dictionary or of the decompressed
* stream, as it is written in the header resp. footer of the
* compressed stream. <br>
* <p>
* Only valid if mode is DECODE_DICT or DECODE_CHKSUM.
*/
private int readAdler;
/**
* The number of bits needed to complete the current state. This
* is valid, if mode is DECODE_DICT, DECODE_CHKSUM,
* DECODE_HUFFMAN_LENBITS or DECODE_HUFFMAN_DISTBITS.
*/
private int neededBits;
private int repLength, repDist;
private int uncomprLen;
/**
* True, if the last block flag was set in the last block of the
* inflated stream. This means that the stream ends after the
* current block.
*/
private boolean isLastBlock;
/**
* The total number of inflated bytes.
*/
private int totalOut;
/**
* The total number of bytes set with setInput(). This is not the
* value returned by getTotalIn(), since this also includes the
* unprocessed input.
*/
private int totalIn;
/**
* This variable stores the nowrap flag that was given to the constructor.
* True means, that the inflated stream doesn't contain a header nor the
* checksum in the footer.
*/
private boolean nowrap;
private StreamManipulator input;
private OutputWindow outputWindow;
private InflaterDynHeader dynHeader;
private InflaterHuffmanTree litlenTree, distTree;
private Adler32 adler;
/**
* Creates a new inflater.
*/
public Inflater() {
this(false);
}
/**
* Creates a new inflater.
*
* @param nowrap true if no header and checksum field appears in the
* stream. This is used for GZIPed input. For compatibility with
* Sun JDK you should provide one byte of input more than needed in
* this case.
*/
public Inflater(boolean nowrap) {
this.nowrap = nowrap;
this.adler = new Adler32();
input = new StreamManipulator();
outputWindow = new OutputWindow();
mode = nowrap ? DECODE_BLOCKS : DECODE_HEADER;
}
/**
* Finalizes this object.
*/
protected void finalize() {
/* Exists only for compatibility */
}
/**
* Frees all objects allocated by the inflater. There's no reason
* to call this, since you can just rely on garbage collection (even
* for the Sun implementation). Exists only for compatibility
* with Sun's JDK, where the compressor allocates native memory.
* If you call any method (even reset) afterwards the behaviour is
* <i>undefined</i>.
*
* @deprecated Just clear all references to inflater instead.
*/
public void end() {
outputWindow = null;
input = null;
dynHeader = null;
litlenTree = null;
distTree = null;
adler = null;
}
/**
* Returns true, if the inflater has finished. This means, that no
* input is needed and no output can be produced.
*/
public boolean finished() {
return mode == FINISHED && outputWindow.getAvailable() == 0;
}
/**
* Gets the adler checksum. This is either the checksum of all
* uncompressed bytes returned by inflate(), or if needsDictionary()
* returns true (and thus no output was yet produced) this is the
* adler checksum of the expected dictionary.
*
* @returns the adler checksum.
*/
public int getAdler() {
return needsDictionary() ? readAdler : (int) adler.getValue();
}
/**
* Gets the number of unprocessed input. Useful, if the end of the
* stream is reached and you want to further process the bytes after
* the deflate stream.
*
* @return the number of bytes of the input which were not processed.
*/
public int getRemaining() {
return input.getAvailableBytes();
}
/**
* Gets the total number of processed compressed input bytes.
*
* @return the total number of bytes of processed input bytes.
*/
public int getTotalIn() {
return totalIn - getRemaining();
}
/**
* Gets the total number of output bytes returned by inflate().
*
* @return the total number of output bytes.
*/
public int getTotalOut() {
return totalOut;
}
/**
* Inflates the compressed stream to the output buffer. If this
* returns 0, you should check, whether needsDictionary(),
* needsInput() or finished() returns true, to determine why no
* further output is produced.
*
* @param buffer the output buffer.
* @return the number of bytes written to the buffer, 0 if no further
* output can be produced.
* @throws DataFormatException if deflated stream is invalid.
* @throws IllegalArgumentException if buf has length 0.
*/
public int inflate(byte[] buf) throws DataFormatException {
return inflate(buf, 0, buf.length);
}
/**
* Inflates the compressed stream to the output buffer. If this
* returns 0, you should check, whether needsDictionary(),
* needsInput() or finished() returns true, to determine why no
* further output is produced.
*
* @param buffer the output buffer.
* @param off the offset into buffer where the output should start.
* @param len the maximum length of the output.
* @return the number of bytes written to the buffer, 0 if no further
* output can be produced.
* @throws DataFormatException if deflated stream is invalid.
* @throws IndexOutOfBoundsException if the off and/or len are wrong.
*/
public int inflate(byte[] buf, int off, int len) throws DataFormatException {
/* Special case: len may be zero */
if (len == 0)
return 0;
/* Check for correct buff, off, len triple */
if (0 > off || off > off + len || off + len > buf.length)
throw new ArrayIndexOutOfBoundsException();
int count = 0;
int more;
do {
if (mode != DECODE_CHKSUM) {
/* Don't give away any output, if we are waiting for the
* checksum in the input stream.
*
* With this trick we have always:
* needsInput() and not finished()
* implies more output can be produced.
*/
more = outputWindow.copyOutput(buf, off, len);
adler.update(buf, off, more);
off += more;
count += more;
totalOut += more;
len -= more;
if (len == 0)
return count;
}
}
while (decode() || (outputWindow.getAvailable() > 0
&& mode != DECODE_CHKSUM));
return count;
}
/**
* Returns true, if a preset dictionary is needed to inflate the input.
*/
public boolean needsDictionary() {
return mode == DECODE_DICT && neededBits == 0;
}
/**
* Returns true, if the input buffer is empty.
* You should then call setInput(). <br>
*
* <em>NOTE</em>: This method also returns true when the stream is finished.
*/
public boolean needsInput() {
return input.needsInput();
}
/**
* Resets the inflater so that a new stream can be decompressed. All
* pending input and output will be discarded.
*/
public void reset() {
mode = nowrap ? DECODE_BLOCKS : DECODE_HEADER;
totalIn = totalOut = 0;
input.reset();
outputWindow.reset();
dynHeader = null;
litlenTree = null;
distTree = null;
isLastBlock = false;
adler.reset();
}
/**
* Sets the preset dictionary. This should only be called, if
* needsDictionary() returns true and it should set the same
* dictionary, that was used for deflating. The getAdler()
* function returns the checksum of the dictionary needed.
*
* @param buffer the dictionary.
* @throws IllegalStateException if no dictionary is needed.
* @throws IllegalArgumentException if the dictionary checksum is
* wrong.
*/
public void setDictionary(byte[] buffer) {
setDictionary(buffer, 0, buffer.length);
}
/**
* Sets the preset dictionary. This should only be called, if
* needsDictionary() returns true and it should set the same
* dictionary, that was used for deflating. The getAdler()
* function returns the checksum of the dictionary needed.
*
* @param buffer the dictionary.
* @param off the offset into buffer where the dictionary starts.
* @param len the length of the dictionary.
* @throws IllegalStateException if no dictionary is needed.
* @throws IllegalArgumentException if the dictionary checksum is
* wrong.
* @throws IndexOutOfBoundsException if the off and/or len are wrong.
*/
public void setDictionary(byte[] buffer, int off, int len) {
if (!needsDictionary())
throw new IllegalStateException();
adler.update(buffer, off, len);
if ((int) adler.getValue() != readAdler)
throw new IllegalArgumentException("Wrong adler checksum");
adler.reset();
outputWindow.copyDict(buffer, off, len);
mode = DECODE_BLOCKS;
}
/**
* Sets the input. This should only be called, if needsInput()
* returns true.
*
* @param buffer the input.
* @throws IllegalStateException if no input is needed.
*/
public void setInput(byte[] buf) {
setInput(buf, 0, buf.length);
}
/**
* Sets the input. This should only be called, if needsInput()
* returns true.
*
* @param buffer the input.
* @param off the offset into buffer where the input starts.
* @param len the length of the input.
* @throws IllegalStateException if no input is needed.
* @throws IndexOutOfBoundsException if the off and/or len are wrong.
*/
public void setInput(byte[] buf, int off, int len) {
input.setInput(buf, off, len);
totalIn += len;
}
/**
* Decodes the deflate header.
*
* @return false if more input is needed.
* @throws DataFormatException if header is invalid.
*/
private boolean decodeHeader() throws DataFormatException {
int header = input.peekBits(16);
if (header < 0)
return false;
input.dropBits(16);
/* The header is written in "wrong" byte order */
header = ((header << 8) | (header >> 8)) & 0xffff;
if (header % 31 != 0)
throw new DataFormatException("Header checksum illegal");
if ((header & 0x0f00) != (Deflater.DEFLATED << 8))
throw new DataFormatException("Compression Method unknown");
/* Maximum size of the backwards window in bits.
* We currently ignore this, but we could use it to make the
* inflater window more space efficient. On the other hand the
* full window (15 bits) is needed most times, anyway.
int max_wbits = ((header & 0x7000) >> 12) + 8;
*/
if ((header & 0x0020) == 0) // Dictionary flag?
{
mode = DECODE_BLOCKS;
} else {
mode = DECODE_DICT;
neededBits = 32;
}
return true;
}
/**
* Decodes the dictionary checksum after the deflate header.
*
* @return false if more input is needed.
*/
private boolean decodeDict() {
while (neededBits > 0) {
int dictByte = input.peekBits(8);
if (dictByte < 0)
return false;
input.dropBits(8);
readAdler = (readAdler << 8) | dictByte;
neededBits -= 8;
}
return false;
}
/**
* Decodes the huffman encoded symbols in the input stream.
*
* @return false if more input is needed, true if output window is
* full or the current block ends.
* @throws DataFormatException if deflated stream is invalid.
*/
private boolean decodeHuffman() throws DataFormatException {
int free = outputWindow.getFreeSpace();
while (free >= 258) {
int symbol;
switch (mode) {
case DECODE_HUFFMAN:
/* This is the inner loop so it is optimized a bit */
while (((symbol = litlenTree.getSymbol(input)) & ~0xff) == 0) {
outputWindow.write(symbol);
if (--free < 258)
return true;
}
if (symbol < 257) {
if (symbol < 0)
return false;
else {
/* symbol == 256: end of block */
distTree = null;
litlenTree = null;
mode = DECODE_BLOCKS;
return true;
}
}
try {
repLength = CPLENS[symbol - 257];
neededBits = CPLEXT[symbol - 257];
} catch (ArrayIndexOutOfBoundsException ex) {
throw new DataFormatException("Illegal rep length code");
}
/* fall through */
case DECODE_HUFFMAN_LENBITS:
if (neededBits > 0) {
mode = DECODE_HUFFMAN_LENBITS;
int i = input.peekBits(neededBits);
if (i < 0)
return false;
input.dropBits(neededBits);
repLength += i;
}
mode = DECODE_HUFFMAN_DIST;
/* fall through */
case DECODE_HUFFMAN_DIST:
symbol = distTree.getSymbol(input);
if (symbol < 0)
return false;
try {
repDist = CPDIST[symbol];
neededBits = CPDEXT[symbol];
} catch (ArrayIndexOutOfBoundsException ex) {
throw new DataFormatException("Illegal rep dist code");
}
/* fall through */
case DECODE_HUFFMAN_DISTBITS:
if (neededBits > 0) {
mode = DECODE_HUFFMAN_DISTBITS;
int i = input.peekBits(neededBits);
if (i < 0)
return false;
input.dropBits(neededBits);
repDist += i;
}
outputWindow.repeat(repLength, repDist);
free -= repLength;
mode = DECODE_HUFFMAN;
break;
default:
throw new IllegalStateException();
}
}
return true;
}
/**
* Decodes the adler checksum after the deflate stream.
*
* @return false if more input is needed.
* @throws DataFormatException if checksum doesn't match.
*/
private boolean decodeChksum() throws DataFormatException {
while (neededBits > 0) {
int chkByte = input.peekBits(8);
if (chkByte < 0)
return false;
input.dropBits(8);
readAdler = (readAdler << 8) | chkByte;
neededBits -= 8;
}
if ((int) adler.getValue() != readAdler)
throw new DataFormatException("Adler chksum doesn't match: "
+ Integer.toHexString((int) adler.getValue())
+ " vs. " + Integer.toHexString(readAdler));
mode = FINISHED;
return false;
}
/**
* Decodes the deflated stream.
*
* @return false if more input is needed, or if finished.
* @throws DataFormatException if deflated stream is invalid.
*/
private boolean decode() throws DataFormatException {
switch (mode) {
case DECODE_HEADER:
return decodeHeader();
case DECODE_DICT:
return decodeDict();
case DECODE_CHKSUM:
return decodeChksum();
case DECODE_BLOCKS:
if (isLastBlock) {
if (nowrap) {
mode = FINISHED;
return false;
} else {
input.skipToByteBoundary();
neededBits = 32;
mode = DECODE_CHKSUM;
return true;
}
}
int type = input.peekBits(3);
if (type < 0)
return false;
input.dropBits(3);
if ((type & 1) != 0)
isLastBlock = true;
switch (type >> 1) {
case DeflaterConstants.STORED_BLOCK:
input.skipToByteBoundary();
mode = DECODE_STORED_LEN1;
break;
case DeflaterConstants.STATIC_TREES:
litlenTree = InflaterHuffmanTree.defLitLenTree;
distTree = InflaterHuffmanTree.defDistTree;
mode = DECODE_HUFFMAN;
break;
case DeflaterConstants.DYN_TREES:
dynHeader = new InflaterDynHeader();
mode = DECODE_DYN_HEADER;
break;
default:
throw new DataFormatException("Unknown block type " + type);
}
return true;
case DECODE_STORED_LEN1: {
if ((uncomprLen = input.peekBits(16)) < 0)
return false;
input.dropBits(16);
mode = DECODE_STORED_LEN2;
}
/* fall through */
case DECODE_STORED_LEN2: {
int nlen = input.peekBits(16);
if (nlen < 0)
return false;
input.dropBits(16);
if (nlen != (uncomprLen ^ 0xffff))
throw new DataFormatException("broken uncompressed block");
mode = DECODE_STORED;
}
/* fall through */
case DECODE_STORED: {
int more = outputWindow.copyStored(input, uncomprLen);
uncomprLen -= more;
if (uncomprLen == 0) {
mode = DECODE_BLOCKS;
return true;
}
return !input.needsInput();
}
case DECODE_DYN_HEADER:
if (!dynHeader.decode(input))
return false;
litlenTree = dynHeader.buildLitLenTree();
distTree = dynHeader.buildDistTree();
mode = DECODE_HUFFMAN;
/* fall through */
case DECODE_HUFFMAN:
case DECODE_HUFFMAN_LENBITS:
case DECODE_HUFFMAN_DIST:
case DECODE_HUFFMAN_DISTBITS:
return decodeHuffman();
case FINISHED:
return false;
default:
throw new IllegalStateException();
}
}
}

@ -0,0 +1,187 @@
/* me.ag2s.epublib.zip.InflaterDynHeader
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
class InflaterDynHeader {
private static final int LNUM = 0;
private static final int DNUM = 1;
private static final int BLNUM = 2;
private static final int BLLENS = 3;
private static final int LENS = 4;
private static final int REPS = 5;
private static final int repMin[] = {3, 3, 11};
private static final int repBits[] = {2, 3, 7};
private byte[] blLens;
private byte[] litdistLens;
private InflaterHuffmanTree blTree;
private int mode;
private int lnum, dnum, blnum, num;
private int repSymbol;
private byte lastLen;
private int ptr;
private static final int[] BL_ORDER =
{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
public InflaterDynHeader() {
}
public boolean decode(StreamManipulator input) throws DataFormatException {
decode_loop:
for (; ; ) {
switch (mode) {
case LNUM:
lnum = input.peekBits(5);
if (lnum < 0)
return false;
lnum += 257;
input.dropBits(5);
// System.err.println("LNUM: "+lnum);
mode = DNUM;
/* fall through */
case DNUM:
dnum = input.peekBits(5);
if (dnum < 0)
return false;
dnum++;
input.dropBits(5);
// System.err.println("DNUM: "+dnum);
num = lnum + dnum;
litdistLens = new byte[num];
mode = BLNUM;
/* fall through */
case BLNUM:
blnum = input.peekBits(4);
if (blnum < 0)
return false;
blnum += 4;
input.dropBits(4);
blLens = new byte[19];
ptr = 0;
// System.err.println("BLNUM: "+blnum);
mode = BLLENS;
/* fall through */
case BLLENS:
while (ptr < blnum) {
int len = input.peekBits(3);
if (len < 0)
return false;
input.dropBits(3);
// System.err.println("blLens["+BL_ORDER[ptr]+"]: "+len);
blLens[BL_ORDER[ptr]] = (byte) len;
ptr++;
}
blTree = new InflaterHuffmanTree(blLens);
blLens = null;
ptr = 0;
mode = LENS;
/* fall through */
case LENS: {
int symbol;
while (((symbol = blTree.getSymbol(input)) & ~15) == 0) {
/* Normal case: symbol in [0..15] */
// System.err.println("litdistLens["+ptr+"]: "+symbol);
litdistLens[ptr++] = lastLen = (byte) symbol;
if (ptr == num) {
/* Finished */
return true;
}
}
/* need more input ? */
if (symbol < 0)
return false;
/* otherwise repeat code */
if (symbol >= 17) {
/* repeat zero */
// System.err.println("repeating zero");
lastLen = 0;
} else {
if (ptr == 0)
throw new DataFormatException();
}
repSymbol = symbol - 16;
mode = REPS;
}
/* fall through */
case REPS: {
int bits = repBits[repSymbol];
int count = input.peekBits(bits);
if (count < 0)
return false;
input.dropBits(bits);
count += repMin[repSymbol];
// System.err.println("litdistLens repeated: "+count);
if (ptr + count > num)
throw new DataFormatException();
while (count-- > 0)
litdistLens[ptr++] = lastLen;
if (ptr == num) {
/* Finished */
return true;
}
}
mode = LENS;
continue decode_loop;
}
}
}
public InflaterHuffmanTree buildLitLenTree() throws DataFormatException {
byte[] litlenLens = new byte[lnum];
System.arraycopy(litdistLens, 0, litlenLens, 0, lnum);
return new InflaterHuffmanTree(litlenLens);
}
public InflaterHuffmanTree buildDistTree() throws DataFormatException {
byte[] distLens = new byte[dnum];
System.arraycopy(litdistLens, lnum, distLens, 0, dnum);
return new InflaterHuffmanTree(distLens);
}
}

@ -0,0 +1,187 @@
/* me.ag2s.epublib.zip.InflaterHuffmanTree
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
public class InflaterHuffmanTree {
private final static int MAX_BITLEN = 15;
private short[] tree;
public static InflaterHuffmanTree defLitLenTree, defDistTree;
static {
try {
byte[] codeLengths = new byte[288];
int i = 0;
while (i < 144)
codeLengths[i++] = 8;
while (i < 256)
codeLengths[i++] = 9;
while (i < 280)
codeLengths[i++] = 7;
while (i < 288)
codeLengths[i++] = 8;
defLitLenTree = new InflaterHuffmanTree(codeLengths);
codeLengths = new byte[32];
i = 0;
while (i < 32)
codeLengths[i++] = 5;
defDistTree = new InflaterHuffmanTree(codeLengths);
} catch (DataFormatException ex) {
throw new InternalError
("InflaterHuffmanTree: static tree length illegal");
}
}
/**
* Constructs a Huffman tree from the array of code lengths.
*
* @param codeLengths the array of code lengths
*/
public InflaterHuffmanTree(byte[] codeLengths) throws DataFormatException {
buildTree(codeLengths);
}
private void buildTree(byte[] codeLengths) throws DataFormatException {
int[] blCount = new int[MAX_BITLEN + 1];
int[] nextCode = new int[MAX_BITLEN + 1];
for (int i = 0; i < codeLengths.length; i++) {
int bits = codeLengths[i];
if (bits > 0)
blCount[bits]++;
}
int code = 0;
int treeSize = 512;
for (int bits = 1; bits <= MAX_BITLEN; bits++) {
nextCode[bits] = code;
code += blCount[bits] << (16 - bits);
if (bits >= 10) {
/* We need an extra table for bit lengths >= 10. */
int start = nextCode[bits] & 0x1ff80;
int end = code & 0x1ff80;
treeSize += (end - start) >> (16 - bits);
}
}
if (code != 65536)
throw new DataFormatException("Code lengths don't add up properly.");
/* Now create and fill the extra tables from longest to shortest
* bit len. This way the sub trees will be aligned.
*/
tree = new short[treeSize];
int treePtr = 512;
for (int bits = MAX_BITLEN; bits >= 10; bits--) {
int end = code & 0x1ff80;
code -= blCount[bits] << (16 - bits);
int start = code & 0x1ff80;
for (int i = start; i < end; i += 1 << 7) {
tree[DeflaterHuffman.bitReverse(i)]
= (short) ((-treePtr << 4) | bits);
treePtr += 1 << (bits - 9);
}
}
for (int i = 0; i < codeLengths.length; i++) {
int bits = codeLengths[i];
if (bits == 0)
continue;
code = nextCode[bits];
int revcode = DeflaterHuffman.bitReverse(code);
if (bits <= 9) {
do {
tree[revcode] = (short) ((i << 4) | bits);
revcode += 1 << bits;
}
while (revcode < 512);
} else {
int subTree = tree[revcode & 511];
int treeLen = 1 << (subTree & 15);
subTree = -(subTree >> 4);
do {
tree[subTree | (revcode >> 9)] = (short) ((i << 4) | bits);
revcode += 1 << bits;
}
while (revcode < treeLen);
}
nextCode[bits] = code + (1 << (16 - bits));
}
}
/**
* Reads the next symbol from input. The symbol is encoded using the
* huffman tree.
*
* @param input the input source.
* @return the next symbol, or -1 if not enough input is available.
*/
public int getSymbol(StreamManipulator input) throws DataFormatException {
int lookahead, symbol;
if ((lookahead = input.peekBits(9)) >= 0) {
if ((symbol = tree[lookahead]) >= 0) {
input.dropBits(symbol & 15);
return symbol >> 4;
}
int subtree = -(symbol >> 4);
int bitlen = symbol & 15;
if ((lookahead = input.peekBits(bitlen)) >= 0) {
symbol = tree[subtree | (lookahead >> 9)];
input.dropBits(symbol & 15);
return symbol >> 4;
} else {
int bits = input.getAvailableBits();
lookahead = input.peekBits(bits);
symbol = tree[subtree | (lookahead >> 9)];
if ((symbol & 15) <= bits) {
input.dropBits(symbol & 15);
return symbol >> 4;
} else
return -1;
}
} else {
int bits = input.getAvailableBits();
lookahead = input.peekBits(bits);
symbol = tree[lookahead];
if (symbol >= 0 && (symbol & 15) <= bits) {
input.dropBits(symbol & 15);
return symbol >> 4;
} else
return -1;
}
}
}

@ -0,0 +1,230 @@
/* InflaterInputStream.java - Input stream filter for decompressing
Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004
Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* This filter stream is used to decompress data compressed in the "deflate"
* format. The "deflate" format is described in RFC 1951.
* <p>
* This stream may form the basis for other decompression filters, such
* as the <code>GZIPInputStream</code>.
*
* @author John Leuner
* @author Tom Tromey
* @since 1.1
*/
public class InflaterInputStream extends FilterInputStream {
/**
* Decompressor for this filter
*/
protected Inflater inf;
/**
* Byte array used as a buffer
*/
protected byte[] buf;
/**
* Size of buffer
*/
protected int len;
/*
* We just use this if we are decoding one byte at a time with the read() call
*/
private byte[] onebytebuffer = new byte[1];
/**
* Create an InflaterInputStream with the default decompresseor
* and a default buffer size.
*
* @param in the InputStream to read bytes from
*/
public InflaterInputStream(InputStream in) {
this(in, new Inflater(), 4096);
}
/**
* Create an InflaterInputStream with the specified decompresseor
* and a default buffer size.
*
* @param in the InputStream to read bytes from
* @param inf the decompressor used to decompress data read from in
*/
public InflaterInputStream(InputStream in, Inflater inf) {
this(in, inf, 4096);
}
/**
* Create an InflaterInputStream with the specified decompresseor
* and a specified buffer size.
*
* @param in the InputStream to read bytes from
* @param inf the decompressor used to decompress data read from in
* @param size size of the buffer to use
*/
public InflaterInputStream(InputStream in, Inflater inf, int size) {
super(in);
this.len = 0;
if (in == null)
throw new NullPointerException("in may not be null");
if (inf == null)
throw new NullPointerException("inf may not be null");
if (size < 0)
throw new IllegalArgumentException("size may not be negative");
this.inf = inf;
this.buf = new byte[size];
}
/**
* Returns 0 once the end of the stream (EOF) has been reached.
* Otherwise returns 1.
*/
public int available() throws IOException {
// According to the JDK 1.2 docs, this should only ever return 0
// or 1 and should not be relied upon by Java programs.
return inf.finished() ? 0 : 1;
}
/**
* Closes the input stream
*/
public synchronized void close() throws IOException {
if (in != null)
in.close();
in = null;
}
/**
* Fills the buffer with more data to decompress.
*/
protected void fill() throws IOException {
if (in == null)
throw new ZipException("InflaterInputStream is closed");
len = in.read(buf, 0, buf.length);
if (len < 0)
throw new ZipException("Deflated stream ends early.");
inf.setInput(buf, 0, len);
}
/**
* Reads one byte of decompressed data.
* <p>
* The byte is in the lower 8 bits of the int.
*/
public int read() throws IOException {
int nread = read(onebytebuffer, 0, 1); //read one byte
if (nread > 0)
return onebytebuffer[0] & 0xff;
return -1;
}
/**
* Decompresses data into the byte array
*
* @param b the array to read and decompress data into
* @param off the offset indicating where the data should be placed
* @param len the number of bytes to decompress
*/
public int read(byte[] b, int off, int len) throws IOException {
if (len == 0)
return 0;
for (; ; ) {
int count;
try {
count = inf.inflate(b, off, len);
} catch (DataFormatException dfe) {
throw new ZipException(dfe.getMessage());
}
if (count > 0)
return count;
if (inf.needsDictionary()
| inf.finished())
return -1;
else if (inf.needsInput())
fill();
else
throw new InternalError("Don't know what to do");
}
}
/**
* Skip specified number of bytes of uncompressed data
*
* @param n number of bytes to skip
*/
public long skip(long n) throws IOException {
if (n < 0)
throw new IllegalArgumentException();
if (n == 0)
return 0;
// Implementation copied from InputStream
// Throw away n bytes by reading them into a temp byte[].
// Limit the temp array to 2Kb so we don't grab too much memory.
final int buflen = n > 2048 ? 2048 : (int) n;
byte[] tmpbuf = new byte[buflen];
final long origN = n;
while (n > 0L) {
int numread = read(tmpbuf, 0, n > buflen ? buflen : (int) n);
if (numread <= 0)
break;
n -= numread;
}
return origN - n;
}
}

@ -0,0 +1,161 @@
/* me.ag2s.epublib.zip.OutputWindow
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/*
* Contains the output from the Inflation process.
*
* We need to have a window so that we can refer backwards into the output stream
* to repeat stuff.
*
* @author John Leuner
* @since JDK 1.1
*/
class OutputWindow {
private final int WINDOW_SIZE = 1 << 15;
private final int WINDOW_MASK = WINDOW_SIZE - 1;
private byte[] window = new byte[WINDOW_SIZE]; //The window is 2^15 bytes
private int window_end = 0;
private int window_filled = 0;
public void write(int abyte) {
if (window_filled++ == WINDOW_SIZE)
throw new IllegalStateException("Window full");
window[window_end++] = (byte) abyte;
window_end &= WINDOW_MASK;
}
private final void slowRepeat(int rep_start, int len, int dist) {
while (len-- > 0) {
window[window_end++] = window[rep_start++];
window_end &= WINDOW_MASK;
rep_start &= WINDOW_MASK;
}
}
public void repeat(int len, int dist) {
if ((window_filled += len) > WINDOW_SIZE)
throw new IllegalStateException("Window full");
int rep_start = (window_end - dist) & WINDOW_MASK;
int border = WINDOW_SIZE - len;
if (rep_start <= border && window_end < border) {
if (len <= dist) {
System.arraycopy(window, rep_start, window, window_end, len);
window_end += len;
} else {
/* We have to copy manually, since the repeat pattern overlaps.
*/
while (len-- > 0)
window[window_end++] = window[rep_start++];
}
} else
slowRepeat(rep_start, len, dist);
}
public int copyStored(StreamManipulator input, int len) {
len = Math.min(Math.min(len, WINDOW_SIZE - window_filled),
input.getAvailableBytes());
int copied;
int tailLen = WINDOW_SIZE - window_end;
if (len > tailLen) {
copied = input.copyBytes(window, window_end, tailLen);
if (copied == tailLen)
copied += input.copyBytes(window, 0, len - tailLen);
} else
copied = input.copyBytes(window, window_end, len);
window_end = (window_end + copied) & WINDOW_MASK;
window_filled += copied;
return copied;
}
public void copyDict(byte[] dict, int offset, int len) {
if (window_filled > 0)
throw new IllegalStateException();
if (len > WINDOW_SIZE) {
offset += len - WINDOW_SIZE;
len = WINDOW_SIZE;
}
System.arraycopy(dict, offset, window, 0, len);
window_end = len & WINDOW_MASK;
}
public int getFreeSpace() {
return WINDOW_SIZE - window_filled;
}
public int getAvailable() {
return window_filled;
}
public int copyOutput(byte[] output, int offset, int len) {
int copy_end = window_end;
if (len > window_filled)
len = window_filled;
else
copy_end = (window_end - window_filled + len) & WINDOW_MASK;
int copied = len;
int tailLen = len - copy_end;
if (tailLen > 0) {
System.arraycopy(window, WINDOW_SIZE - tailLen,
output, offset, tailLen);
offset += tailLen;
len = copy_end;
}
System.arraycopy(window, copy_end - len, output, offset, len);
window_filled -= copied;
if (window_filled < 0)
throw new IllegalStateException();
return copied;
}
public void reset() {
window_filled = window_end = 0;
}
}

@ -0,0 +1,181 @@
/* me.ag2s.epublib.zip.PendingBuffer
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/**
* This class is general purpose class for writing data to a buffer.
* <p>
* It allows you to write bits as well as bytes
* <p>
* Based on DeflaterPending.java
*
* @author Jochen Hoenicke
* @date Jan 5, 2000
*/
class PendingBuffer {
protected byte[] buf;
int start;
int end;
int bits;
int bitCount;
public PendingBuffer() {
this(4096);
}
public PendingBuffer(int bufsize) {
buf = new byte[bufsize];
}
public final void reset() {
start = end = bitCount = 0;
}
public final void writeByte(int b) {
if (DeflaterConstants.DEBUGGING && start != 0)
throw new IllegalStateException();
buf[end++] = (byte) b;
}
public final void writeShort(int s) {
if (DeflaterConstants.DEBUGGING && start != 0)
throw new IllegalStateException();
buf[end++] = (byte) s;
buf[end++] = (byte) (s >> 8);
}
public final void writeInt(int s) {
if (DeflaterConstants.DEBUGGING && start != 0)
throw new IllegalStateException();
buf[end++] = (byte) s;
buf[end++] = (byte) (s >> 8);
buf[end++] = (byte) (s >> 16);
buf[end++] = (byte) (s >> 24);
}
public final void writeBlock(byte[] block, int offset, int len) {
if (DeflaterConstants.DEBUGGING && start != 0)
throw new IllegalStateException();
System.arraycopy(block, offset, buf, end, len);
end += len;
}
public final int getBitCount() {
return bitCount;
}
public final void alignToByte() {
if (DeflaterConstants.DEBUGGING && start != 0)
throw new IllegalStateException();
if (bitCount > 0) {
buf[end++] = (byte) bits;
if (bitCount > 8)
buf[end++] = (byte) (bits >>> 8);
}
bits = 0;
bitCount = 0;
}
public final void writeBits(int b, int count) {
if (DeflaterConstants.DEBUGGING && start != 0)
throw new IllegalStateException();
if (DeflaterConstants.DEBUGGING)
System.err.println("writeBits(" + Integer.toHexString(b) + "," + count + ")");
bits |= b << bitCount;
bitCount += count;
if (bitCount >= 16) {
buf[end++] = (byte) bits;
buf[end++] = (byte) (bits >>> 8);
bits >>>= 16;
bitCount -= 16;
}
}
public final void writeShortMSB(int s) {
if (DeflaterConstants.DEBUGGING && start != 0)
throw new IllegalStateException();
buf[end++] = (byte) (s >> 8);
buf[end++] = (byte) s;
}
public final boolean isFlushed() {
return end == 0;
}
/**
* Flushes the pending buffer into the given output array. If the
* output array is to small, only a partial flush is done.
*
* @param output the output array;
* @param offset the offset into output array;
* @param length the maximum number of bytes to store;
* @throws IndexOutOfBoundsException if offset or length are
* invalid.
*/
public final int flush(byte[] output, int offset, int length) {
if (bitCount >= 8) {
buf[end++] = (byte) bits;
bits >>>= 8;
bitCount -= 8;
}
if (length > end - start) {
length = end - start;
System.arraycopy(buf, start, output, offset, length);
start = 0;
end = 0;
} else {
System.arraycopy(buf, start, output, offset, length);
start += length;
}
return length;
}
public final byte[] toByteArray() {
byte[] ret = new byte[end - start];
System.arraycopy(buf, start, ret, 0, ret.length);
start = 0;
end = 0;
return ret;
}
}

@ -0,0 +1,206 @@
/* me.ag2s.epublib.zip.StreamManipulator
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
/**
* This class allows us to retrieve a specified amount of bits from
* the input buffer, as well as copy big byte blocks.
* <p>
* It uses an int buffer to store up to 31 bits for direct
* manipulation. This guarantees that we can get at least 16 bits,
* but we only need at most 15, so this is all safe.
* <p>
* There are some optimizations in this class, for example, you must
* never peek more then 8 bits more than needed, and you must first
* peek bits before you may drop them. This is not a general purpose
* class but optimized for the behaviour of the Inflater.
*
* @author John Leuner, Jochen Hoenicke
*/
class StreamManipulator {
private byte[] window;
private int window_start = 0;
private int window_end = 0;
private int buffer = 0;
private int bits_in_buffer = 0;
/**
* Get the next n bits but don't increase input pointer. n must be
* less or equal 16 and if you if this call succeeds, you must drop
* at least n-8 bits in the next call.
*
* @return the value of the bits, or -1 if not enough bits available.
*/
public final int peekBits(int n) {
if (bits_in_buffer < n) {
if (window_start == window_end)
return -1;
buffer |= (window[window_start++] & 0xff
| (window[window_start++] & 0xff) << 8) << bits_in_buffer;
bits_in_buffer += 16;
}
return buffer & ((1 << n) - 1);
}
/* Drops the next n bits from the input. You should have called peekBits
* with a bigger or equal n before, to make sure that enough bits are in
* the bit buffer.
*/
public final void dropBits(int n) {
buffer >>>= n;
bits_in_buffer -= n;
}
/**
* Gets the next n bits and increases input pointer. This is equivalent
* to peekBits followed by dropBits, except for correct error handling.
*
* @return the value of the bits, or -1 if not enough bits available.
*/
public final int getBits(int n) {
int bits = peekBits(n);
if (bits >= 0)
dropBits(n);
return bits;
}
/**
* Gets the number of bits available in the bit buffer. This must be
* only called when a previous peekBits() returned -1.
*
* @return the number of bits available.
*/
public final int getAvailableBits() {
return bits_in_buffer;
}
/**
* Gets the number of bytes available.
*
* @return the number of bytes available.
*/
public final int getAvailableBytes() {
return window_end - window_start + (bits_in_buffer >> 3);
}
/**
* Skips to the next byte boundary.
*/
public void skipToByteBoundary() {
buffer >>= (bits_in_buffer & 7);
bits_in_buffer &= ~7;
}
public final boolean needsInput() {
return window_start == window_end;
}
/* Copies length bytes from input buffer to output buffer starting
* at output[offset]. You have to make sure, that the buffer is
* byte aligned. If not enough bytes are available, copies fewer
* bytes.
* @param length the length to copy, 0 is allowed.
* @return the number of bytes copied, 0 if no byte is available.
*/
public int copyBytes(byte[] output, int offset, int length) {
if (length < 0)
throw new IllegalArgumentException("length negative");
if ((bits_in_buffer & 7) != 0)
/* bits_in_buffer may only be 0 or 8 */
throw new IllegalStateException("Bit buffer is not aligned!");
int count = 0;
while (bits_in_buffer > 0 && length > 0) {
output[offset++] = (byte) buffer;
buffer >>>= 8;
bits_in_buffer -= 8;
length--;
count++;
}
if (length == 0)
return count;
int avail = window_end - window_start;
if (length > avail)
length = avail;
System.arraycopy(window, window_start, output, offset, length);
window_start += length;
if (((window_start - window_end) & 1) != 0) {
/* We always want an even number of bytes in input, see peekBits */
buffer = (window[window_start++] & 0xff);
bits_in_buffer = 8;
}
return count + length;
}
public StreamManipulator() {
}
public void reset() {
window_start = window_end = buffer = bits_in_buffer = 0;
}
public void setInput(byte[] buf, int off, int len) {
if (window_start < window_end)
throw new IllegalStateException
("Old input was not completely processed");
int end = off + len;
/* We want to throw an ArrayIndexOutOfBoundsException early. The
* check is very tricky: it also handles integer wrap around.
*/
if (0 > off || off > end || end > buf.length)
throw new ArrayIndexOutOfBoundsException();
if ((len & 1) != 0) {
/* We always want an even number of bytes in input, see peekBits */
buffer |= (buf[off++] & 0xff) << bits_in_buffer;
bits_in_buffer += 8;
}
window = buf;
window_start = off;
window_end = end;
}
}

@ -0,0 +1,96 @@
/* me.ag2s.epublib.zip.ZipConstants
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of thata
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
interface ZipConstants {
/* The local file header */
int LOCHDR = 30;
int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24);
int LOCVER = 4;
int LOCFLG = 6;
int LOCHOW = 8;
int LOCTIM = 10;
int LOCCRC = 14;
int LOCSIZ = 18;
int LOCLEN = 22;
int LOCNAM = 26;
int LOCEXT = 28;
/* The Data descriptor */
int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24);
int EXTHDR = 16;
int EXTCRC = 4;
int EXTSIZ = 8;
int EXTLEN = 12;
/* The central directory file header */
int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24);
int CENHDR = 46;
int CENVEM = 4;
int CENVER = 6;
int CENFLG = 8;
int CENHOW = 10;
int CENTIM = 12;
int CENCRC = 16;
int CENSIZ = 20;
int CENLEN = 24;
int CENNAM = 28;
int CENEXT = 30;
int CENCOM = 32;
int CENDSK = 34;
int CENATT = 36;
int CENATX = 38;
int CENOFF = 42;
/* The entries in the end of central directory */
int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24);
int ENDHDR = 22;
/* The following two fields are missing in SUN JDK */
int ENDNRD = 4;
int ENDDCD = 6;
int ENDSUB = 8;
int ENDTOT = 10;
int ENDSIZ = 12;
int ENDOFF = 16;
int ENDCOM = 20;
}

@ -0,0 +1,389 @@
/* me.ag2s.epublib.zip.ZipEntry
Copyright (C) 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.util.Calendar;
import java.util.Date;
/**
* This class represents a member of a zip archive. ZipFile and
* ZipInputStream will give you instances of this class as information
* about the members in an archive. On the other hand ZipOutputStream
* needs an instance of this class to create a new member.
*
* @author Jochen Hoenicke
*/
public class ZipEntry implements ZipConstants, Cloneable {
private static final int KNOWN_SIZE = 1;
private static final int KNOWN_CSIZE = 2;
private static final int KNOWN_CRC = 4;
private static final int KNOWN_TIME = 8;
private static Calendar cal;
private final String name;
private int size;
private int compressedSize;
private int crc;
private int dostime;
private short known = 0;
private short method = -1;
private byte[] extra = null;
private String comment = null;
int flags; /* used by ZipOutputStream */
int offset; /* used by ZipFile and ZipOutputStream */
/**
* Compression method. This method doesn't compress at all.
*/
public final static int STORED = 0;
/**
* Compression method. This method uses the Deflater.
*/
public final static int DEFLATED = 8;
/**
* Creates a zip entry with the given name.
*
* @param name the name. May include directory components separated
* by '/'.
* @throws NullPointerException when name is null.
* @throws IllegalArgumentException when name is bigger then 65535 chars.
*/
public ZipEntry(String name) {
int length = name.length();
if (length > 65535)
throw new IllegalArgumentException("name length is " + length);
this.name = name;
}
/**
* Creates a copy of the given zip entry.
*
* @param e the entry to copy.
*/
public ZipEntry(ZipEntry e) {
name = e.name;
known = e.known;
size = e.size;
compressedSize = e.compressedSize;
crc = e.crc;
dostime = e.dostime;
method = e.method;
extra = e.extra;
comment = e.comment;
}
final void setDOSTime(int dostime) {
this.dostime = dostime;
known |= KNOWN_TIME;
}
final int getDOSTime() {
if ((known & KNOWN_TIME) == 0)
return 0;
else
return dostime;
}
/**
* Creates a copy of this zip entry.
*/
/**
* Clones the entry.
*/
public Object clone() {
try {
// The JCL says that the `extra' field is also copied.
ZipEntry clone = (ZipEntry) super.clone();
if (extra != null)
clone.extra = (byte[]) extra.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new InternalError();
}
}
/**
* Returns the entry name. The path components in the entry are
* always separated by slashes ('/').
*/
public String getName() {
return name;
}
/**
* Sets the time of last modification of the entry.
*
* @time the time of last modification of the entry.
*/
public void setTime(long time) {
Calendar cal = getCalendar();
synchronized (cal) {
cal.setTime(new Date(time * 1000L));
dostime = (cal.get(Calendar.YEAR) - 1980 & 0x7f) << 25
| (cal.get(Calendar.MONTH) + 1) << 21
| (cal.get(Calendar.DAY_OF_MONTH)) << 16
| (cal.get(Calendar.HOUR_OF_DAY)) << 11
| (cal.get(Calendar.MINUTE)) << 5
| (cal.get(Calendar.SECOND)) >> 1;
}
dostime = (int) (dostime / 1000L);
this.known |= KNOWN_TIME;
}
/**
* Gets the time of last modification of the entry.
*
* @return the time of last modification of the entry, or -1 if unknown.
*/
public long getTime() {
if ((known & KNOWN_TIME) == 0)
return -1;
int sec = 2 * (dostime & 0x1f);
int min = (dostime >> 5) & 0x3f;
int hrs = (dostime >> 11) & 0x1f;
int day = (dostime >> 16) & 0x1f;
int mon = ((dostime >> 21) & 0xf) - 1;
int year = ((dostime >> 25) & 0x7f) + 1980; /* since 1900 */
try {
cal = getCalendar();
synchronized (cal) {
cal.set(year, mon, day, hrs, min, sec);
return cal.getTime().getTime();
}
} catch (RuntimeException ex) {
/* Ignore illegal time stamp */
known &= ~KNOWN_TIME;
return -1;
}
}
private static synchronized Calendar getCalendar() {
if (cal == null)
cal = Calendar.getInstance();
return cal;
}
/**
* Sets the size of the uncompressed data.
*
* @throws IllegalArgumentException if size is not in 0..0xffffffffL
*/
public void setSize(long size) {
if ((size & 0xffffffff00000000L) != 0)
throw new IllegalArgumentException();
this.size = (int) size;
this.known |= KNOWN_SIZE;
}
/**
* Gets the size of the uncompressed data.
*
* @return the size or -1 if unknown.
*/
public long getSize() {
return (known & KNOWN_SIZE) != 0 ? size & 0xffffffffL : -1L;
}
/**
* Sets the size of the compressed data.
*
* @throws IllegalArgumentException if size is not in 0..0xffffffffL
*/
public void setCompressedSize(long csize) {
if ((csize & 0xffffffff00000000L) != 0)
throw new IllegalArgumentException();
this.compressedSize = (int) csize;
this.known |= KNOWN_CSIZE;
}
/**
* Gets the size of the compressed data.
*
* @return the size or -1 if unknown.
*/
public long getCompressedSize() {
return (known & KNOWN_CSIZE) != 0 ? compressedSize & 0xffffffffL : -1L;
}
/**
* Sets the crc of the uncompressed data.
*
* @throws IllegalArgumentException if crc is not in 0..0xffffffffL
*/
public void setCrc(long crc) {
if ((crc & 0xffffffff00000000L) != 0)
throw new IllegalArgumentException();
this.crc = (int) crc;
this.known |= KNOWN_CRC;
}
/**
* Gets the crc of the uncompressed data.
*
* @return the crc or -1 if unknown.
*/
public long getCrc() {
return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L;
}
/**
* Sets the compression method. Only DEFLATED and STORED are
* supported.
*
* @throws IllegalArgumentException if method is not supported.
* @see ZipOutputStream#DEFLATED
* @see ZipOutputStream#STORED
*/
public void setMethod(int method) {
if (method != ZipOutputStream.STORED
&& method != ZipOutputStream.DEFLATED)
throw new IllegalArgumentException();
this.method = (short) method;
}
/**
* Gets the compression method.
*
* @return the compression method or -1 if unknown.
*/
public int getMethod() {
return method;
}
/**
* Sets the extra data.
*
* @throws IllegalArgumentException if extra is longer than 0xffff bytes.
*/
public void setExtra(byte[] extra) {
if (extra == null) {
this.extra = null;
return;
}
if (extra.length > 0xffff)
throw new IllegalArgumentException();
this.extra = extra;
try {
int pos = 0;
while (pos < extra.length) {
int sig = (extra[pos++] & 0xff)
| (extra[pos++] & 0xff) << 8;
int len = (extra[pos++] & 0xff)
| (extra[pos++] & 0xff) << 8;
if (sig == 0x5455) {
/* extended time stamp */
int flags = extra[pos];
if ((flags & 1) != 0) {
long time = ((extra[pos + 1] & 0xff)
| (extra[pos + 2] & 0xff) << 8
| (extra[pos + 3] & 0xff) << 16
| (extra[pos + 4] & 0xff) << 24);
setTime(time);
}
}
pos += len;
}
} catch (ArrayIndexOutOfBoundsException ex) {
/* be lenient */
return;
}
}
/**
* Gets the extra data.
*
* @return the extra data or null if not set.
*/
public byte[] getExtra() {
return extra;
}
/**
* Sets the entry comment.
*
* @throws IllegalArgumentException if comment is longer than 0xffff.
*/
public void setComment(String comment) {
if (comment != null && comment.length() > 0xffff)
throw new IllegalArgumentException();
this.comment = comment;
}
/**
* Gets the comment.
*
* @return the comment or null if not set.
*/
public String getComment() {
return comment;
}
/**
* Gets true, if the entry is a directory. This is solely
* determined by the name, a trailing slash '/' marks a directory.
*/
public boolean isDirectory() {
int nlen = name.length();
return nlen > 0 && name.charAt(nlen - 1) == '/';
}
/**
* Gets the string representation of this ZipEntry. This is just
* the name as returned by getName().
*/
public String toString() {
return name;
}
/**
* Gets the hashCode of this ZipEntry. This is just the hashCode
* of the name. Note that the equals method isn't changed, though.
*/
public int hashCode() {
return name.hashCode();
}
}

@ -0,0 +1,69 @@
/* ZipException.java - exception representing a zip related error
Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.IOException;
/**
* Thrown during the creation or input of a zip file.
*
* @author Jochen Hoenicke
* @author Per Bothner
* @status updated to 1.4
*/
public class ZipException extends IOException {
/**
* Compatible with JDK 1.0+.
*/
private static final long serialVersionUID = 8000196834066748623L;
/**
* Create an exception without a message.
*/
public ZipException() {
}
/**
* Create an exception with a message.
*
* @param msg the message
*/
public ZipException(String msg) {
super(msg);
}
}

@ -0,0 +1,526 @@
/* me.ag2s.epublib.zip.ZipFile
Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import android.content.Context;
import android.net.Uri;
import android.system.ErrnoException;
import androidx.annotation.NonNull;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.DataInput;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* This class represents a Zip archive. You can ask for the contained
* entries, or get an input stream for a file entry. The entry is
* automatically decompressed.
* <p>
* This class is thread safe: You can open input streams for arbitrary
* entries in different threads.
*
* @author Jochen Hoenicke
* @author Artur Biesiadowski
*/
public class ZipFile implements ZipConstants, Closeable {
/**
* Mode flag to open a zip file for reading.
*/
public static final int OPEN_READ = 0x1;
/**
* Mode flag to delete a zip file after reading.
*/
public static final int OPEN_DELETE = 0x4;
@NonNull
public Context getContext() {
return context;
}
public Uri getUri() {
return Uri.parse(name);
}
@NonNull
private final Context context;
// Name of this zip file.
@NonNull
private final String name;
// File from which zip entries are read.
@NonNull
private final /*RandomAccessFile*/ AndroidRandomReadableFile araf;
// The entries of this zip file when initialized and not yet closed.
private HashMap<String, ZipEntry> entries;
private boolean closed = false;
/**
* Opens a Zip file with the given name for reading.
*
* @throws IOException if a i/o error occured.
* @throws ZipException if the file doesn't contain a valid zip
* archive.
*/
public ZipFile(@NonNull Context context, @NonNull Uri uri) throws ZipException, IOException {
this.context = context;
this.araf = new AndroidRandomReadableFile(context, uri);
this.name = uri.toString();
}
/**
* Opens a Zip file reading the given File in the given mode.
*
* If the OPEN_DELETE mode is specified, the zip file will be deleted at
* some time moment after it is opened. It will be deleted before the zip
* file is closed or the Virtual Machine exits.
*
* The contents of the zip file will be accessible until it is closed.
*
* The OPEN_DELETE mode is currently unimplemented in this library
*
* @since JDK1.3
* @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE
*
* @exception IOException if a i/o error occured.
* @exception ZipException if the file doesn't contain a valid zip
* archive.
*/
// public ZipFile(File file, int mode) throws ZipException, IOException
// {
// if ((mode & OPEN_DELETE) != 0)
// {
// throw new IllegalArgumentException
// ("OPEN_DELETE mode not supported yet in me.ag2s.epublib.zip.ZipFile");
// }
// this.raf = new RandomAccessFile(file, "r");
// this.name = file.getPath();
// }
/**
* Read an unsigned short in little endian byte order from the given
* DataInput stream using the given byte buffer.
*
* @param di DataInput stream to read from.
* @param b the byte buffer to read in (must be at least 2 bytes long).
* @return The value read.
* @throws IOException if a i/o error occured.
* @throws EOFException if the file ends prematurely
*/
private final int readLeShort(DataInput di, byte[] b) throws IOException {
di.readFully(b, 0, 2);
return (b[0] & 0xff) | (b[1] & 0xff) << 8;
}
/**
* Read an int in little endian byte order from the given
* DataInput stream using the given byte buffer.
*
* @param di DataInput stream to read from.
* @param b the byte buffer to read in (must be at least 4 bytes long).
* @return The value read.
* @throws IOException if a i/o error occured.
* @throws EOFException if the file ends prematurely
*/
private final int readLeInt(DataInput di, byte[] b) throws IOException {
di.readFully(b, 0, 4);
return ((b[0] & 0xff) | (b[1] & 0xff) << 8)
| ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;
}
/**
* Read an unsigned short in little endian byte order from the given
* byte buffer at the given offset.
*
* @param b the byte array to read from.
* @param off the offset to read from.
* @return The value read.
*/
private final int readLeShort(byte[] b, int off) {
return (b[off] & 0xff) | (b[off + 1] & 0xff) << 8;
}
/**
* Read an int in little endian byte order from the given
* byte buffer at the given offset.
*
* @param b the byte array to read from.
* @param off the offset to read from.
* @return The value read.
*/
private final int readLeInt(byte[] b, int off) {
return ((b[off] & 0xff) | (b[off + 1] & 0xff) << 8)
| ((b[off + 2] & 0xff) | (b[off + 3] & 0xff) << 8) << 16;
}
/**
* Read the central directory of a zip file and fill the entries
* array. This is called exactly once when first needed. It is called
* while holding the lock on <code>raf</code>.
*
* @throws IOException if a i/o error occured.
* @throws ZipException if the central directory is malformed
*/
private void readEntries() throws ZipException, IOException, ErrnoException {
/* Search for the End Of Central Directory. When a zip comment is
* present the directory may start earlier.
* FIXME: This searches the whole file in a very slow manner if the
* file isn't a zip file.
*/
long pos = araf.length() - ENDHDR;
byte[] ebs = new byte[CENHDR];
do {
if (pos < 0)
throw new ZipException
("central directory not found, probably not a zip file: " + name);
araf.seek(pos--);
}
while (readLeInt(araf, ebs) != ENDSIG);
if (araf.skipBytes(ENDTOT - ENDNRD) != ENDTOT - ENDNRD)
throw new EOFException(name);
int count = readLeShort(araf, ebs);
if (araf.skipBytes(ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)
throw new EOFException(name);
int centralOffset = readLeInt(araf, ebs);
entries = new HashMap<>(count + count / 2);
araf.seek(centralOffset);
byte[] buffer = new byte[16];
for (int i = 0; i < count; i++) {
araf.readFully(ebs);
if (readLeInt(ebs, 0) != CENSIG)
throw new ZipException("Wrong Central Directory signature: " + name);
int method = readLeShort(ebs, CENHOW);
int dostime = readLeInt(ebs, CENTIM);
int crc = readLeInt(ebs, CENCRC);
int csize = readLeInt(ebs, CENSIZ);
int size = readLeInt(ebs, CENLEN);
int nameLen = readLeShort(ebs, CENNAM);
int extraLen = readLeShort(ebs, CENEXT);
int commentLen = readLeShort(ebs, CENCOM);
int offset = readLeInt(ebs, CENOFF);
int needBuffer = Math.max(nameLen, commentLen);
if (buffer.length < needBuffer)
buffer = new byte[needBuffer];
araf.readFully(buffer, 0, nameLen);
String name = new String(buffer, 0, 0, nameLen);
ZipEntry entry = new ZipEntry(name);
entry.setMethod(method);
entry.setCrc(crc & 0xffffffffL);
entry.setSize(size & 0xffffffffL);
entry.setCompressedSize(csize & 0xffffffffL);
entry.setDOSTime(dostime);
if (extraLen > 0) {
byte[] extra = new byte[extraLen];
araf.readFully(extra);
entry.setExtra(extra);
}
if (commentLen > 0) {
araf.readFully(buffer, 0, commentLen);
entry.setComment(new String(buffer, 0, commentLen));
}
entry.offset = offset;
entries.put(name, entry);
}
}
/**
* Closes the ZipFile. This also closes all input streams given by
* this class. After this is called, no further method should be
* called.
*
* @throws IOException if a i/o error occured.
*/
@Override
public void close() throws IOException {
synchronized (araf) {
closed = true;
entries = null;
araf.close();
}
}
/**
* Calls the <code>close()</code> method when this ZipFile has not yet
* been explicitly closed.
*/
protected void finalize() throws IOException {
if (!closed && araf != null) close();
}
/**
* Returns an enumeration of all Zip entries in this Zip file.
*/
public Enumeration<? extends ZipEntry> entries() {
try {
return new ZipEntryEnumeration(getEntries().values().iterator());
} catch (IOException ioe) {
return null;
}
}
/**
* Checks that the ZipFile is still open and reads entries when necessary.
*
* @throws IllegalStateException when the ZipFile has already been closed.
* @throws IOException, ErrnoException when the entries could not be read.
*/
private HashMap<String, ZipEntry> getEntries() throws IOException {
synchronized (araf) {
if (closed)
throw new IllegalStateException("ZipFile has closed: " + name);
if (entries == null) {
try {
readEntries();
} catch (ErrnoException e) {
e.printStackTrace();
}
}
return entries;
}
}
/**
* Searches for a zip entry in this archive with the given name.
*
* @param name May contain directory components separated by
* slashes ('/').
* @return the zip entry, or null if no entry with that name exists.
*/
public ZipEntry getEntry(String name) {
try {
HashMap<String, ZipEntry> entries = getEntries();
ZipEntry entry = entries.get(name);
return entry != null ? (ZipEntry) entry.clone() : null;
} catch (IOException ioe) {
return null;
}
}
//access should be protected by synchronized(raf)
private final byte[] locBuf = new byte[LOCHDR];
/**
* Checks, if the local header of the entry at index i matches the
* central directory, and returns the offset to the data.
*
* @param entry to check.
* @return the start offset of the (compressed) data.
* @throws IOException if a i/o error occured.
* @throws ZipException if the local header doesn't match the
* central directory header
*/
private long checkLocalHeader(ZipEntry entry) throws IOException {
synchronized (araf) {
try {
araf.seek(entry.offset);
} catch (IOException e) {
e.printStackTrace();
}
araf.readFully(locBuf);
if (readLeInt(locBuf, 0) != LOCSIG)
throw new ZipException("Wrong Local header signature: " + name);
if (entry.getMethod() != readLeShort(locBuf, LOCHOW))
throw new ZipException("Compression method mismatch: " + name);
if (entry.getName().length() != readLeShort(locBuf, LOCNAM))
throw new ZipException("file name length mismatch: " + name);
int extraLen = entry.getName().length() + readLeShort(locBuf, LOCEXT);
return entry.offset + LOCHDR + extraLen;
}
}
/**
* Creates an input stream reading the given zip entry as
* uncompressed data. Normally zip entry should be an entry
* returned by getEntry() or entries().
*
* @param entry the entry to create an InputStream for.
* @return the input stream.
* @throws IOException if a i/o error occured.
* @throws ZipException if the Zip archive is malformed.
*/
public InputStream getInputStream(ZipEntry entry) throws IOException {
HashMap<String, ZipEntry> entries = getEntries();
String name = entry.getName();
ZipEntry zipEntry = entries.get(name);
if (zipEntry == null)
throw new NoSuchElementException(name);
long start = checkLocalHeader(zipEntry);
int method = zipEntry.getMethod();
InputStream is = new BufferedInputStream(new PartialInputStream
(araf, start, zipEntry.getCompressedSize()));
switch (method) {
case ZipOutputStream.STORED:
return is;
case ZipOutputStream.DEFLATED:
return new InflaterInputStream(is, new Inflater(true));
default:
throw new ZipException("Unknown compression method " + method);
}
}
/**
* Returns the (path) name of this zip file.
*/
@NonNull
public String getName() {
return name;
}
/**
* Returns the number of entries in this zip file.
*/
public int size() {
try {
return getEntries().size();
} catch (IOException ioe) {
return 0;
}
}
private static class ZipEntryEnumeration implements Enumeration<ZipEntry> {
private final Iterator<ZipEntry> elements;
public ZipEntryEnumeration(Iterator<ZipEntry> elements) {
this.elements = elements;
}
public boolean hasMoreElements() {
return elements.hasNext();
}
public ZipEntry nextElement() {
/* We return a clone, just to be safe that the user doesn't
* change the entry.
*/
return (ZipEntry) elements.next().clone();
}
}
private static class PartialInputStream extends InputStream {
private final /*RandomAccessFile*/ AndroidRandomReadableFile raf;
long filepos, end;
public PartialInputStream(AndroidRandomReadableFile raf, long start, long len) {
this.raf = raf;
filepos = start;
end = start + len;
}
public int available() {
long amount = end - filepos;
if (amount > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
return (int) amount;
}
public int read() throws IOException {
if (filepos == end)
return -1;
synchronized (raf) {
try {
raf.seek(filepos++);
} catch (IOException e) {
e.printStackTrace();
}
return raf.read();
}
}
public int read(byte[] b, int off, int len) throws IOException {
if (len > end - filepos) {
len = (int) (end - filepos);
if (len == 0)
return -1;
}
synchronized (raf) {
try {
raf.seek(filepos);
} catch (IOException e) {
e.printStackTrace();
}
int count = raf.read(b, off, len);
if (count > 0)
filepos += len;
return count;
}
}
public long skip(long amount) {
if (amount < 0)
throw new IllegalArgumentException();
if (amount > end - filepos)
amount = end - filepos;
filepos += amount;
return amount;
}
}
}

@ -0,0 +1,344 @@
/* me.ag2s.epublib.zip.ZipInputStream
Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* This is a FilterInputStream that reads the files in an zip archive
* one after another. It has a special method to get the zip entry of
* the next file. The zip entry contains information about the file name
* size, compressed size, CRC, etc.
* <p>
* It includes support for STORED and DEFLATED entries.
*
* @author Jochen Hoenicke
*/
public class ZipInputStream extends InflaterInputStream implements ZipConstants {
private CRC32 crc = new CRC32();
private ZipEntry entry = null;
private int csize;
private int size;
private int method;
private int flags;
private int avail;
private boolean entryAtEOF;
/**
* Creates a new Zip input stream, reading a zip archive.
*/
public ZipInputStream(InputStream in) {
super(in, new Inflater(true));
}
private void fillBuf() throws IOException {
avail = len = in.read(buf, 0, buf.length);
}
private int readBuf(byte[] out, int offset, int length) throws IOException {
if (avail <= 0) {
fillBuf();
if (avail <= 0)
return -1;
}
if (length > avail)
length = avail;
System.arraycopy(buf, len - avail, out, offset, length);
avail -= length;
return length;
}
private void readFully(byte[] out) throws IOException {
int off = 0;
int len = out.length;
while (len > 0) {
int count = readBuf(out, off, len);
if (count == -1)
throw new EOFException();
off += count;
len -= count;
}
}
private final int readLeByte() throws IOException {
if (avail <= 0) {
fillBuf();
if (avail <= 0)
throw new ZipException("EOF in header");
}
return buf[len - avail--] & 0xff;
}
/**
* Read an unsigned short in little endian byte order.
*/
private final int readLeShort() throws IOException {
return readLeByte() | (readLeByte() << 8);
}
/**
* Read an int in little endian byte order.
*/
private final int readLeInt() throws IOException {
return readLeShort() | (readLeShort() << 16);
}
/**
* Open the next entry from the zip archive, and return its description.
* If the previous entry wasn't closed, this method will close it.
*/
public ZipEntry getNextEntry() throws IOException {
if (crc == null)
throw new IOException("Stream closed.");
if (entry != null)
closeEntry();
int header = readLeInt();
if (header == CENSIG) {
/* Central Header reached. */
close();
return null;
}
if (header != LOCSIG)
throw new ZipException("Wrong Local header signature: "
+ Integer.toHexString(header));
/* skip version */
readLeShort();
flags = readLeShort();
method = readLeShort();
int dostime = readLeInt();
int crc = readLeInt();
csize = readLeInt();
size = readLeInt();
int nameLen = readLeShort();
int extraLen = readLeShort();
if (method == ZipOutputStream.STORED && csize != size)
throw new ZipException("Stored, but compressed != uncompressed");
byte[] buffer = new byte[nameLen];
readFully(buffer);
String name = new String(buffer);
entry = createZipEntry(name);
entryAtEOF = false;
entry.setMethod(method);
if ((flags & 8) == 0) {
entry.setCrc(crc & 0xffffffffL);
entry.setSize(size & 0xffffffffL);
entry.setCompressedSize(csize & 0xffffffffL);
}
entry.setDOSTime(dostime);
if (extraLen > 0) {
byte[] extra = new byte[extraLen];
readFully(extra);
entry.setExtra(extra);
}
if (method == ZipOutputStream.DEFLATED && avail > 0) {
System.arraycopy(buf, len - avail, buf, 0, avail);
len = avail;
avail = 0;
inf.setInput(buf, 0, len);
}
return entry;
}
private void readDataDescr() throws IOException {
if (readLeInt() != EXTSIG)
throw new ZipException("Data descriptor signature not found");
entry.setCrc(readLeInt() & 0xffffffffL);
csize = readLeInt();
size = readLeInt();
entry.setSize(size & 0xffffffffL);
entry.setCompressedSize(csize & 0xffffffffL);
}
/**
* Closes the current zip entry and moves to the next one.
*/
public void closeEntry() throws IOException {
if (crc == null)
throw new IOException("Stream closed.");
if (entry == null)
return;
if (method == ZipOutputStream.DEFLATED) {
if ((flags & 8) != 0) {
/* We don't know how much we must skip, read until end. */
byte[] tmp = new byte[2048];
while (read(tmp) > 0)
;
/* read will close this entry */
return;
}
csize -= inf.getTotalIn();
avail = inf.getRemaining();
}
if (avail > csize && csize >= 0)
avail -= csize;
else {
csize -= avail;
avail = 0;
while (csize != 0) {
long skipped = in.skip(csize & 0xffffffffL);
if (skipped <= 0)
throw new ZipException("zip archive ends early.");
csize -= skipped;
}
}
size = 0;
crc.reset();
if (method == ZipOutputStream.DEFLATED)
inf.reset();
entry = null;
entryAtEOF = true;
}
public int available() throws IOException {
return entryAtEOF ? 0 : 1;
}
/**
* Reads a byte from the current zip entry.
*
* @return the byte or -1 on EOF.
* @throws IOException if a i/o error occured.
* @throws ZipException if the deflated stream is corrupted.
*/
public int read() throws IOException {
byte[] b = new byte[1];
if (read(b, 0, 1) <= 0)
return -1;
return b[0] & 0xff;
}
/**
* Reads a block of bytes from the current zip entry.
*
* @return the number of bytes read (may be smaller, even before
* EOF), or -1 on EOF.
* @throws IOException if a i/o error occured.
* @throws ZipException if the deflated stream is corrupted.
*/
public int read(byte[] b, int off, int len) throws IOException {
if (len == 0)
return 0;
if (crc == null)
throw new IOException("Stream closed.");
if (entry == null)
return -1;
boolean finished = false;
switch (method) {
case ZipOutputStream.DEFLATED:
len = super.read(b, off, len);
if (len < 0) {
if (!inf.finished())
throw new ZipException("Inflater not finished!?");
avail = inf.getRemaining();
if ((flags & 8) != 0)
readDataDescr();
if (inf.getTotalIn() != csize
|| inf.getTotalOut() != size)
throw new ZipException("size mismatch: " + csize + ";" + size + " <-> " + inf.getTotalIn() + ";" + inf.getTotalOut());
inf.reset();
finished = true;
}
break;
case ZipOutputStream.STORED:
if (len > csize && csize >= 0)
len = csize;
len = readBuf(b, off, len);
if (len > 0) {
csize -= len;
size -= len;
}
if (csize == 0)
finished = true;
else if (len < 0)
throw new ZipException("EOF in stored block");
break;
}
if (len > 0)
crc.update(b, off, len);
if (finished) {
if ((crc.getValue() & 0xffffffffL) != entry.getCrc())
throw new ZipException("CRC mismatch");
crc.reset();
entry = null;
entryAtEOF = true;
}
return len;
}
/**
* Closes the zip file.
*
* @throws IOException if a i/o error occured.
*/
public void close() throws IOException {
super.close();
crc = null;
entry = null;
entryAtEOF = true;
}
/**
* Creates a new zip entry for the given name. This is equivalent
* to new ZipEntry(name).
*
* @param name the name of the zip entry.
*/
protected ZipEntry createZipEntry(String name) {
return new ZipEntry(name);
}
}

@ -0,0 +1,383 @@
/* me.ag2s.epublib.zip.ZipOutputStream
Copyright (C) 2001 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath 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, or (at your option)
any later version.
GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package me.ag2s.epublib.zip;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
/**
* This is a FilterOutputStream that writes the files into a zip
* archive one after another. It has a special method to start a new
* zip entry. The zip entries contains information about the file name
* size, compressed size, CRC, etc.
* <p>
* It includes support for STORED and DEFLATED entries.
* <p>
* This class is not thread safe.
*
* @author Jochen Hoenicke
*/
public class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
private ArrayList<ZipEntry> entries = new ArrayList<>();
private final CRC32 crc = new CRC32();
private ZipEntry curEntry = null;
private int curMethod;
private int size;
private int offset = 0;
private byte[] zipComment = new byte[0];
private int defaultMethod = DEFLATED;
/**
* Our Zip version is hard coded to 1.0 resp. 2.0
*/
private final static int ZIP_STORED_VERSION = 10;
private final static int ZIP_DEFLATED_VERSION = 20;
/**
* Compression method. This method doesn't compress at all.
*/
public final static int STORED = 0;
/**
* Compression method. This method uses the Deflater.
*/
public final static int DEFLATED = 8;
/**
* Creates a new Zip output stream, writing a zip archive.
*
* @param out the output stream to which the zip archive is written.
*/
public ZipOutputStream(OutputStream out) {
super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
}
/**
* Set the zip file comment.
*
* @param comment the comment.
* @throws IllegalArgumentException if encoding of comment is
* longer than 0xffff bytes.
*/
public void setComment(String comment) {
byte[] commentBytes;
commentBytes = comment.getBytes();
if (commentBytes.length > 0xffff)
throw new IllegalArgumentException("Comment too long.");
zipComment = commentBytes;
}
/**
* Sets default compression method. If the Zip entry specifies
* another method its method takes precedence.
*
* @param method the method.
* @throws IllegalArgumentException if method is not supported.
* @see #STORED
* @see #DEFLATED
*/
public void setMethod(int method) {
if (method != STORED && method != DEFLATED)
throw new IllegalArgumentException("Method not supported.");
defaultMethod = method;
}
/**
* Sets default compression level. The new level will be activated
* immediately.
*
* @throws IllegalArgumentException if level is not supported.
* @see Deflater
*/
public void setLevel(int level) {
def.setLevel(level);
}
/**
* Write an unsigned short in little endian byte order.
*/
private final void writeLeShort(int value) throws IOException {
out.write(value & 0xff);
out.write((value >> 8) & 0xff);
}
/**
* Write an int in little endian byte order.
*/
private final void writeLeInt(int value) throws IOException {
writeLeShort(value);
writeLeShort(value >> 16);
}
/**
* Starts a new Zip entry. It automatically closes the previous
* entry if present. If the compression method is stored, the entry
* must have a valid size and crc, otherwise all elements (except
* name) are optional, but must be correct if present. If the time
* is not set in the entry, the current time is used.
*
* @param entry the entry.
* @throws IOException if an I/O error occured.
* @throws ZipException if stream was finished.
*/
public void putNextEntry(ZipEntry entry) throws IOException {
if (entries == null)
throw new ZipException("ZipOutputStream was finished");
int method = entry.getMethod();
int flags = 0;
if (method == -1)
method = defaultMethod;
if (method == STORED) {
if (entry.getCompressedSize() >= 0) {
if (entry.getSize() < 0)
entry.setSize(entry.getCompressedSize());
else if (entry.getSize() != entry.getCompressedSize())
throw new ZipException
("Method STORED, but compressed size != size");
} else
entry.setCompressedSize(entry.getSize());
if (entry.getSize() < 0)
throw new ZipException("Method STORED, but size not set");
if (entry.getCrc() < 0)
throw new ZipException("Method STORED, but crc not set");
} else if (method == DEFLATED) {
if (entry.getCompressedSize() < 0
|| entry.getSize() < 0 || entry.getCrc() < 0)
flags |= 8;
}
if (curEntry != null)
closeEntry();
if (entry.getTime() < 0)
entry.setTime(System.currentTimeMillis());
entry.flags = flags;
entry.offset = offset;
entry.setMethod(method);
curMethod = method;
/* Write the local file header */
writeLeInt(LOCSIG);
writeLeShort(method == STORED
? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
writeLeShort(flags);
writeLeShort(method);
writeLeInt(entry.getDOSTime());
if ((flags & 8) == 0) {
writeLeInt((int) entry.getCrc());
writeLeInt((int) entry.getCompressedSize());
writeLeInt((int) entry.getSize());
} else {
writeLeInt(0);
writeLeInt(0);
writeLeInt(0);
}
byte[] name = entry.getName().getBytes();
if (name.length > 0xffff)
throw new ZipException("Name too long.");
byte[] extra = entry.getExtra();
if (extra == null)
extra = new byte[0];
writeLeShort(name.length);
writeLeShort(extra.length);
out.write(name);
out.write(extra);
offset += LOCHDR + name.length + extra.length;
/* Activate the entry. */
curEntry = entry;
crc.reset();
if (method == DEFLATED)
def.reset();
size = 0;
}
/**
* Closes the current entry.
*
* @throws IOException if an I/O error occured.
* @throws ZipException if no entry is active.
*/
public void closeEntry() throws IOException {
if (curEntry == null)
throw new ZipException("No open entry");
/* First finish the deflater, if appropriate */
if (curMethod == DEFLATED)
super.finish();
int csize = curMethod == DEFLATED ? def.getTotalOut() : size;
if (curEntry.getSize() < 0)
curEntry.setSize(size);
else if (curEntry.getSize() != size)
throw new ZipException("size was " + size
+ ", but I expected " + curEntry.getSize());
if (curEntry.getCompressedSize() < 0)
curEntry.setCompressedSize(csize);
else if (curEntry.getCompressedSize() != csize)
throw new ZipException("compressed size was " + csize
+ ", but I expected " + curEntry.getSize());
if (curEntry.getCrc() < 0)
curEntry.setCrc(crc.getValue());
else if (curEntry.getCrc() != crc.getValue())
throw new ZipException("crc was " + Long.toHexString(crc.getValue())
+ ", but I expected "
+ Long.toHexString(curEntry.getCrc()));
offset += csize;
/* Now write the data descriptor entry if needed. */
if (curMethod == DEFLATED && (curEntry.flags & 8) != 0) {
writeLeInt(EXTSIG);
writeLeInt((int) curEntry.getCrc());
writeLeInt((int) curEntry.getCompressedSize());
writeLeInt((int) curEntry.getSize());
offset += EXTHDR;
}
entries.add(curEntry);
//entries.addElement(curEntry);
curEntry = null;
}
/**
* Writes the given buffer to the current entry.
*
* @throws IOException if an I/O error occured.
* @throws ZipException if no entry is active.
*/
public void write(byte[] b, int off, int len) throws IOException {
if (curEntry == null)
throw new ZipException("No open entry.");
switch (curMethod) {
case DEFLATED:
super.write(b, off, len);
break;
case STORED:
out.write(b, off, len);
break;
}
crc.update(b, off, len);
size += len;
}
/**
* Finishes the stream. This will write the central directory at the
* end of the zip file and flush the stream.
*
* @throws IOException if an I/O error occured.
*/
public void finish() throws IOException {
if (entries == null)
return;
if (curEntry != null)
closeEntry();
int numEntries = 0;
int sizeEntries = 0;
//Enumeration<ZipEntry> items = entries.elements();
//items.nextElement();
for (ZipEntry entry : entries) {
int method = entry.getMethod();
writeLeInt(CENSIG);
writeLeShort(method == STORED
? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
writeLeShort(method == STORED
? ZIP_STORED_VERSION : ZIP_DEFLATED_VERSION);
writeLeShort(entry.flags);
writeLeShort(method);
writeLeInt(entry.getDOSTime());
writeLeInt((int) entry.getCrc());
writeLeInt((int) entry.getCompressedSize());
writeLeInt((int) entry.getSize());
byte[] name = entry.getName().getBytes();
if (name.length > 0xffff)
throw new ZipException("Name too long.");
byte[] extra = entry.getExtra();
if (extra == null)
extra = new byte[0];
String strComment = entry.getComment();
byte[] comment = strComment != null
? strComment.getBytes() : new byte[0];
if (comment.length > 0xffff)
throw new ZipException("Comment too long.");
writeLeShort(name.length);
writeLeShort(extra.length);
writeLeShort(comment.length);
writeLeShort(0); /* disk number */
writeLeShort(0); /* internal file attr */
writeLeInt(0); /* external file attr */
writeLeInt(entry.offset);
out.write(name);
out.write(extra);
out.write(comment);
numEntries++;
sizeEntries += CENHDR + name.length + extra.length + comment.length;
}
writeLeInt(ENDSIG);
writeLeShort(0); /* disk number */
writeLeShort(0); /* disk with start of central dir */
writeLeShort(numEntries);
writeLeShort(numEntries);
writeLeInt(sizeEntries);
writeLeInt(offset);
writeLeShort(zipComment.length);
out.write(zipComment);
out.flush();
entries = null;
}
}
Loading…
Cancel
Save