mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
实现epub的懒加载(未经广泛测试)
This commit is contained in:
@@ -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)
|
||||
//通过懒加载读取epub
|
||||
return EpubReader().readEpub(bis, "utf-8")
|
||||
val uri = Uri.parse(book.bookUrl)
|
||||
return if (uri.isContentScheme()) {
|
||||
//通过懒加载读取epub
|
||||
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
|
||||
|
||||
197
epublib/src/main/java/me/ag2s/epublib/zip/Adler32.java
Normal file
197
epublib/src/main/java/me/ag2s/epublib/zip/Adler32.java
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
134
epublib/src/main/java/me/ag2s/epublib/zip/CRC32.java
Normal file
134
epublib/src/main/java/me/ag2s/epublib/zip/CRC32.java
Normal file
@@ -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;
|
||||
}
|
||||
84
epublib/src/main/java/me/ag2s/epublib/zip/Checksum.java
Normal file
84
epublib/src/main/java/me/ag2s/epublib/zip/Checksum.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
512
epublib/src/main/java/me/ag2s/epublib/zip/Deflater.java
Normal file
512
epublib/src/main/java/me/ag2s/epublib/zip/Deflater.java
Normal file
@@ -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};
|
||||
}
|
||||
653
epublib/src/main/java/me/ag2s/epublib/zip/DeflaterEngine.java
Normal file
653
epublib/src/main/java/me/ag2s/epublib/zip/DeflaterEngine.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
704
epublib/src/main/java/me/ag2s/epublib/zip/DeflaterHuffman.java
Normal file
704
epublib/src/main/java/me/ag2s/epublib/zip/DeflaterHuffman.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
338
epublib/src/main/java/me/ag2s/epublib/zip/GZIPInputStream.java
Normal file
338
epublib/src/main/java/me/ag2s/epublib/zip/GZIPInputStream.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
146
epublib/src/main/java/me/ag2s/epublib/zip/GZIPOutputStream.java
Normal file
146
epublib/src/main/java/me/ag2s/epublib/zip/GZIPOutputStream.java
Normal file
@@ -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 )");
|
||||
}
|
||||
}
|
||||
681
epublib/src/main/java/me/ag2s/epublib/zip/Inflater.java
Normal file
681
epublib/src/main/java/me/ag2s/epublib/zip/Inflater.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
187
epublib/src/main/java/me/ag2s/epublib/zip/InflaterDynHeader.java
Normal file
187
epublib/src/main/java/me/ag2s/epublib/zip/InflaterDynHeader.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
161
epublib/src/main/java/me/ag2s/epublib/zip/OutputWindow.java
Normal file
161
epublib/src/main/java/me/ag2s/epublib/zip/OutputWindow.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
181
epublib/src/main/java/me/ag2s/epublib/zip/PendingBuffer.java
Normal file
181
epublib/src/main/java/me/ag2s/epublib/zip/PendingBuffer.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
206
epublib/src/main/java/me/ag2s/epublib/zip/StreamManipulator.java
Normal file
206
epublib/src/main/java/me/ag2s/epublib/zip/StreamManipulator.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
96
epublib/src/main/java/me/ag2s/epublib/zip/ZipConstants.java
Normal file
96
epublib/src/main/java/me/ag2s/epublib/zip/ZipConstants.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
389
epublib/src/main/java/me/ag2s/epublib/zip/ZipEntry.java
Normal file
389
epublib/src/main/java/me/ag2s/epublib/zip/ZipEntry.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
69
epublib/src/main/java/me/ag2s/epublib/zip/ZipException.java
Normal file
69
epublib/src/main/java/me/ag2s/epublib/zip/ZipException.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
526
epublib/src/main/java/me/ag2s/epublib/zip/ZipFile.java
Normal file
526
epublib/src/main/java/me/ag2s/epublib/zip/ZipFile.java
Normal file
@@ -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 = (ZipEntry) 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
344
epublib/src/main/java/me/ag2s/epublib/zip/ZipInputStream.java
Normal file
344
epublib/src/main/java/me/ag2s/epublib/zip/ZipInputStream.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
383
epublib/src/main/java/me/ag2s/epublib/zip/ZipOutputStream.java
Normal file
383
epublib/src/main/java/me/ag2s/epublib/zip/ZipOutputStream.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user