Add support for more java.util.zip

This commit is contained in:
Alexey Andreev 2017-11-10 00:47:48 +03:00
parent 93549818ee
commit 86d151d953
19 changed files with 2527 additions and 35 deletions

View File

@ -20,7 +20,6 @@
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<module name="InterfaceIsType"/>
<module name="HideUtilityClassConstructor"/>
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>

View File

@ -0,0 +1,52 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.Adler32;
public class TAdler32 implements TChecksum {
private Adler32 impl = new Adler32();
@Override
public long getValue() {
return impl.getValue();
}
@Override
public void reset() {
impl.reset();
}
@Override
public void update(int i) {
update(new byte[] { (byte) i });
}
public void update(byte[] buf) {
update(buf, 0, buf.length);
}
@Override
public void update(byte[] buf, int off, int nbytes) {
// avoid int overflow, check null buf
if (off <= buf.length && nbytes >= 0 && off >= 0 && buf.length - off >= nbytes) {
impl.update(buf, off, nbytes);
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.CRC32;
public class TCRC32 implements TChecksum {
private CRC32 impl = new CRC32();
long tbytes;
@Override
public long getValue() {
return impl.getValue();
}
@Override
public void reset() {
impl.reset();
tbytes = 0;
}
@Override
public void update(int val) {
impl.update(new byte[] { (byte) val }, 0, 1);
}
public void update(byte[] buf) {
update(buf, 0, buf.length);
}
@Override
public void update(byte[] buf, int off, int nbytes) {
// avoid int overflow, check null buf
if (off <= buf.length && nbytes >= 0 && off >= 0 && buf.length - off >= nbytes) {
impl.update(buf, off, nbytes);
tbytes += nbytes;
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class TCheckedInputStream extends FilterInputStream {
private final TChecksum check;
public TCheckedInputStream(InputStream is, TChecksum csum) {
super(is);
check = csum;
}
@Override
public int read() throws IOException {
int x = in.read();
if (x != -1) {
check.update(x);
}
return x;
}
@Override
public int read(byte[] buf, int off, int nbytes) throws IOException {
int x = in.read(buf, off, nbytes);
if (x != -1) {
check.update(buf, off, x);
}
return x;
}
public TChecksum getChecksum() {
return check;
}
@Override
public long skip(long nbytes) throws IOException {
if (nbytes < 1) {
return 0;
}
long skipped = 0;
byte[] b = new byte[(int) Math.min(nbytes, 2048L)];
int x;
int v;
while (skipped != nbytes) {
v = (int) (nbytes - skipped);
x = in.read(b, 0, v > b.length ? b.length : v);
if (x == -1) {
return skipped;
}
check.update(b, 0, x);
skipped += x;
}
return skipped;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class TCheckedOutputStream extends FilterOutputStream {
private final TChecksum check;
public TCheckedOutputStream(OutputStream os, TChecksum cs) {
super(os);
check = cs;
}
public TChecksum getChecksum() {
return check;
}
@Override
public void write(int val) throws IOException {
out.write(val);
check.update(val);
}
@Override
public void write(byte[] buf, int off, int nbytes) throws IOException {
out.write(buf, off, nbytes);
check.update(buf, off, nbytes);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
public interface TChecksum {
long getValue();
void reset();
void update(byte[] buf, int off, int nbytes);
void update(int val);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Alexey Andreev.
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
public class TDataFormatException extends Exception {
private static final long serialVersionUID = 7856637411580418624L;
public TDataFormatException() {
super();
}
public TDataFormatException(String message) {
super(message);
public TDataFormatException(String detailMessage) {
super(detailMessage);
}
}

View File

@ -0,0 +1,222 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.Deflater;
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.JZlib;
import java.util.Arrays;
public class TDeflater {
public static final int BEST_COMPRESSION = 9;
public static final int BEST_SPEED = 1;
public static final int DEFAULT_COMPRESSION = -1;
public static final int DEFAULT_STRATEGY = 0;
public static final int DEFLATED = 8;
public static final int FILTERED = 1;
public static final int HUFFMAN_ONLY = 2;
public static final int NO_COMPRESSION = 0;
static final int Z_NO_FLUSH = 0;
static final int Z_SYNC_FLUSH = 2;
static final int Z_FINISH = 4;
private int flushParm = Z_NO_FLUSH;
private boolean finished;
private int compressLevel = DEFAULT_COMPRESSION;
private int strategy = DEFAULT_STRATEGY;
private Deflater impl;
private int inRead;
private int inLength;
private boolean nowrap;
public TDeflater() {
this(DEFAULT_COMPRESSION, false);
}
public TDeflater(int level) {
this(level, false);
}
public TDeflater(int level, boolean noHeader) {
super();
if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) {
throw new IllegalArgumentException();
}
compressLevel = level;
try {
impl = new Deflater(compressLevel, strategy, noHeader);
} catch (GZIPException e) {
// do nothing
}
nowrap = noHeader;
}
public int deflate(byte[] buf) {
return deflate(buf, 0, buf.length);
}
public int deflate(byte[] buf, int off, int nbytes) {
return deflate(buf, off, nbytes, flushParm);
}
int deflate(byte[] buf, int off, int nbytes, int flushParam) {
if (impl == null) {
throw new IllegalStateException();
}
// avoid int overflow, check null buf
if (off > buf.length || nbytes < 0 || off < 0 || buf.length - off < nbytes) {
throw new ArrayIndexOutOfBoundsException();
}
long sin = impl.total_in;
long sout = impl.total_out;
impl.setOutput(buf, off, nbytes);
int err = impl.deflate(flushParam);
switch (err) {
case JZlib.Z_OK:
break;
case JZlib.Z_STREAM_END:
finished = true;
break;
default:
throw new RuntimeException("Error: " + err);
}
inRead += impl.total_in - sin;
return (int) (impl.total_out - sout);
}
public void end() {
impl = null;
}
@Override
protected void finalize() {
end();
}
public void finish() {
flushParm = Z_FINISH;
}
public boolean finished() {
return finished;
}
public int getAdler() {
if (impl == null) {
throw new IllegalStateException();
}
return (int) impl.getAdler();
}
public int getTotalIn() {
if (impl == null) {
throw new IllegalStateException();
}
return (int) impl.getTotalIn();
}
public int getTotalOut() {
if (impl == null) {
throw new IllegalStateException();
}
return (int) impl.getTotalOut();
}
public boolean needsInput() {
return inRead == inLength;
}
public void reset() {
if (impl == null) {
throw new NullPointerException();
}
flushParm = Z_NO_FLUSH;
finished = false;
impl.init(compressLevel, strategy, nowrap);
}
public void setDictionary(byte[] buf) {
setDictionary(buf, 0, buf.length);
}
public void setDictionary(byte[] buf, int off, int nbytes) {
if (impl == null) {
throw new IllegalStateException();
}
// avoid int overflow, check null buf
if (off <= buf.length && nbytes >= 0 && off >= 0 && buf.length - off >= nbytes) {
impl.setDictionary(Arrays.copyOfRange(buf, off, buf.length), nbytes);
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
public void setInput(byte[] buf) {
setInput(buf, 0, buf.length);
}
public void setInput(byte[] buf, int off, int nbytes) {
if (impl == null) {
throw new IllegalStateException();
}
// avoid int overflow, check null buf
if (off <= buf.length && nbytes >= 0 && off >= 0 && buf.length - off >= nbytes) {
inLength = nbytes;
inRead = 0;
if (impl.next_in == null) {
impl.init(compressLevel, strategy, nowrap);
}
impl.setInput(buf, off, nbytes, false);
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
public void setLevel(int level) {
if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) {
throw new IllegalArgumentException();
}
compressLevel = level;
}
public void setStrategy(int strategy) {
if (strategy < DEFAULT_STRATEGY || strategy > HUFFMAN_ONLY) {
throw new IllegalArgumentException();
}
this.strategy = strategy;
}
public long getBytesRead() {
// Throw NPE here
if (impl == null) {
throw new NullPointerException();
}
return impl.getTotalIn();
}
public long getBytesWritten() {
// Throw NPE here
if (impl == null) {
throw new NullPointerException();
}
return impl.getTotalOut();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Alexey Andreev.
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,16 +13,93 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.DeflaterOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class TDeflaterOutputStream extends FilterOutputStream {
public TDeflaterOutputStream(OutputStream out) throws IOException {
super(out);
this.out = new DeflaterOutputStream(out);
static final int BUF_SIZE = 512;
protected byte[] buf;
protected TDeflater def;
boolean done;
public TDeflaterOutputStream(OutputStream os, TDeflater def) {
this(os, def, BUF_SIZE);
}
public TDeflaterOutputStream(OutputStream os) {
this(os, new TDeflater());
}
public TDeflaterOutputStream(OutputStream os, TDeflater def, int bsize) {
super(os);
if (os == null || def == null) {
throw new NullPointerException();
}
if (bsize <= 0) {
throw new IllegalArgumentException();
}
this.def = def;
buf = new byte[bsize];
}
protected void deflate() throws IOException {
int x;
do {
x = def.deflate(buf);
out.write(buf, 0, x);
} while (!def.needsInput());
}
@Override
public void close() throws IOException {
if (!def.finished()) {
finish();
}
def.end();
out.close();
}
public void finish() throws IOException {
if (done) {
return;
}
def.finish();
int x = 0;
while (!def.finished()) {
if (def.needsInput()) {
def.setInput(buf, 0, 0);
}
x = def.deflate(buf);
out.write(buf, 0, x);
}
done = true;
}
@Override
public void write(int i) throws IOException {
byte[] b = new byte[1];
b[0] = (byte) i;
write(b, 0, 1);
}
@Override
public void write(byte[] buffer, int off, int nbytes) throws IOException {
if (done) {
throw new IOException();
}
// avoid int overflow, check null buf
if (off <= buffer.length && nbytes >= 0 && off >= 0 && buffer.length - off >= nbytes) {
if (!def.needsInput()) {
throw new IOException();
}
def.setInput(buffer, off, nbytes);
deflate();
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Alexey Andreev.
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,20 +13,165 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.GZIPInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class TGZIPInputStream extends TInflaterInputStream {
public TGZIPInputStream(InputStream in, int size) throws IOException {
super(in);
this.in = new GZIPInputStream(in, size, false);
private static final int FCOMMENT = 16;
private static final int FEXTRA = 4;
private static final int FHCRC = 2;
private static final int FNAME = 8;
public final static int GZIP_MAGIC = 0x8b1f;
protected TCRC32 crc = new TCRC32();
protected boolean eos;
public TGZIPInputStream(InputStream is) throws IOException {
this(is, BUF_SIZE);
}
public TGZIPInputStream(InputStream in) throws IOException {
super(in);
this.in = new GZIPInputStream(in);
public TGZIPInputStream(InputStream is, int size) throws IOException {
super(is, new TInflater(true), size);
byte[] header = new byte[10];
readFully(header, 0, header.length);
if (getShort(header, 0) != GZIP_MAGIC) {
throw new IOException();
}
int flags = header[3];
boolean hcrc = (flags & FHCRC) != 0;
if (hcrc) {
crc.update(header, 0, header.length);
}
if ((flags & FEXTRA) != 0) {
readFully(header, 0, 2);
if (hcrc) {
crc.update(header, 0, 2);
}
int length = getShort(header, 0);
while (length > 0) {
int max = length > buf.length ? buf.length : length;
int result = in.read(buf, 0, max);
if (result == -1) {
throw new EOFException();
}
if (hcrc) {
crc.update(buf, 0, result);
}
length -= result;
}
}
if ((flags & FNAME) != 0) {
readZeroTerminated(hcrc);
}
if ((flags & FCOMMENT) != 0) {
readZeroTerminated(hcrc);
}
if (hcrc) {
readFully(header, 0, 2);
int crc16 = getShort(header, 0);
if ((crc.getValue() & 0xffff) != crc16) {
throw new IOException();
}
crc.reset();
}
}
@Override
public void close() throws IOException {
eos = true;
super.close();
}
private long getLong(byte[] buffer, int off) {
long l = 0;
l |= buffer[off] & 0xFF;
l |= (buffer[off + 1] & 0xFF) << 8;
l |= (buffer[off + 2] & 0xFF) << 16;
l |= ((long) (buffer[off + 3] & 0xFF)) << 24;
return l;
}
private int getShort(byte[] buffer, int off) {
return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8);
}
@Override
public int read(byte[] buffer, int off, int nbytes) throws IOException {
if (closed) {
throw new IOException();
}
if (eos) {
return -1;
}
// avoid int overflow, check null buffer
if (off > buffer.length || nbytes < 0 || off < 0 || buffer.length - off < nbytes) {
throw new ArrayIndexOutOfBoundsException();
}
int bytesRead;
try {
bytesRead = super.read(buffer, off, nbytes);
} finally {
eos = eof; // update eos after every read(), even when it throws
}
if (bytesRead != -1) {
crc.update(buffer, off, bytesRead);
}
if (eos) {
verifyCrc();
}
return bytesRead;
}
private void verifyCrc() throws IOException {
// Get non-compressed bytes read by fill
int size = inf.getRemaining();
final int trailerSize = 8; // crc (4 bytes) + total out (4 bytes)
byte[] b = new byte[trailerSize];
int copySize = (size > trailerSize) ? trailerSize : size;
System.arraycopy(buf, len - size, b, 0, copySize);
readFully(b, copySize, trailerSize - copySize);
if (getLong(b, 0) != crc.getValue()) {
throw new IOException();
}
if ((int) getLong(b, 4) != inf.getTotalOut()) {
throw new IOException();
}
}
private void readFully(byte[] buffer, int offset, int length) throws IOException {
int result;
while (length > 0) {
result = in.read(buffer, offset, length);
if (result == -1) {
throw new EOFException();
}
offset += result;
length -= result;
}
}
private void readZeroTerminated(boolean hcrc) throws IOException {
int result;
while ((result = in.read()) > 0) {
if (hcrc) {
crc.update(result);
}
}
if (result == -1) {
throw new EOFException();
}
// Add the zero
if (hcrc) {
crc.update(result);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Alexey Andreev.
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,27 +13,62 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.GZIPOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class TGZIPOutputStream extends TDeflaterOutputStream {
public TGZIPOutputStream(OutputStream out, int size, boolean syncFlush) throws IOException {
super(out);
this.out = new GZIPOutputStream(out, size, syncFlush);
protected TCRC32 crc = new TCRC32();
public TGZIPOutputStream(OutputStream os) throws IOException {
this(os, BUF_SIZE);
}
public TGZIPOutputStream(OutputStream out, boolean syncFlush) throws IOException {
this(out, 512, syncFlush);
public TGZIPOutputStream(OutputStream os, int size) throws IOException {
super(os, new TDeflater(java.util.zip.Deflater.DEFAULT_COMPRESSION, true), size);
writeShort(TGZIPInputStream.GZIP_MAGIC);
out.write(java.util.zip.Deflater.DEFLATED);
out.write(0); // flags
writeLong(0); // mod time
out.write(0); // extra flags
out.write(0); // operating system
}
public TGZIPOutputStream(OutputStream out, int size) throws IOException {
this(out, size, true);
@Override
public void flush() throws IOException {
int count = def.deflate(buf, 0, buf.length, TDeflater.Z_SYNC_FLUSH);
out.write(buf, 0, count);
out.flush();
}
public TGZIPOutputStream(OutputStream out) throws IOException {
this(out, 512, true);
@Override
public void finish() throws IOException {
super.finish();
writeLong(crc.getValue());
writeLong(crc.tbytes);
}
@Override
public void write(byte[] buffer, int off, int nbytes) throws IOException {
super.write(buffer, off, nbytes);
crc.update(buffer, off, nbytes);
}
private long writeLong(long i) throws IOException {
// Write out the long value as an unsigned int
int unsigned = (int) i;
out.write(unsigned & 0xFF);
out.write((unsigned >> 8) & 0xFF);
out.write((unsigned >> 16) & 0xFF);
out.write((unsigned >> 24) & 0xFF);
return i;
}
private int writeShort(int i) throws IOException {
out.write(i & 0xFF);
out.write((i >> 8) & 0xFF);
return i;
}
}

View File

@ -0,0 +1,201 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;
import java.util.Arrays;
public class TInflater {
private boolean finished;
private boolean nowrap;
int inLength;
int inRead;
private boolean needsDictionary;
private Inflater impl;
public TInflater() {
this(false);
}
public TInflater(boolean noHeader) {
nowrap = noHeader;
try {
impl = new Inflater(noHeader);
} catch (GZIPException e) {
// Do nothing
}
}
public void end() {
inRead = 0;
inLength = 0;
impl = null;
}
@Override
protected void finalize() {
end();
}
public boolean finished() {
return finished;
}
public int getAdler() {
if (impl == null) {
throw new IllegalStateException();
}
return (int) impl.getAdler();
}
private native int getAdlerImpl(long handle);
public long getBytesRead() {
if (impl == null) {
throw new IllegalStateException();
}
return impl.getTotalIn();
}
public long getBytesWritten() {
if (impl == null) {
throw new IllegalStateException();
}
return impl.getTotalOut();
}
public int getRemaining() {
return inLength - inRead;
}
public int getTotalIn() {
return (int) getBytesRead();
}
private native long getTotalInImpl(long handle);
public int getTotalOut() {
return (int) getBytesWritten();
}
private native long getTotalOutImpl(long handle);
public int inflate(byte[] buf) throws TDataFormatException {
return inflate(buf, 0, buf.length);
}
public int inflate(byte[] buf, int off, int nbytes) throws TDataFormatException {
// avoid int overflow, check null buf
if (off > buf.length || nbytes < 0 || off < 0 || buf.length - off < nbytes) {
throw new ArrayIndexOutOfBoundsException();
}
if (impl == null) {
throw new IllegalStateException();
}
if (needsInput()) {
return 0;
}
long lastInSize = impl.total_in;
long lastOutSize = impl.total_out;
boolean neededDict = needsDictionary;
needsDictionary = false;
impl.setOutput(buf, off, nbytes);
int errCode = impl.inflate(0);
switch (errCode) {
case JZlib.Z_OK:
break;
case JZlib.Z_NEED_DICT:
needsDictionary = true;
break;
case JZlib.Z_STREAM_END:
finished = true;
break;
default:
throw new TDataFormatException("Error occurred: " + errCode);
}
if (needsDictionary && neededDict) {
throw new TDataFormatException();
}
inRead += impl.total_in - lastInSize;
return (int) (impl.total_out - lastOutSize);
}
public boolean needsDictionary() {
return needsDictionary;
}
public boolean needsInput() {
return inRead == inLength;
}
public void reset() {
if (impl == null) {
throw new NullPointerException();
}
finished = false;
needsDictionary = false;
inLength = 0;
inRead = 0;
impl.init(nowrap);
}
private native void resetImpl(long handle);
public void setDictionary(byte[] buf) {
setDictionary(buf, 0, buf.length);
}
public void setDictionary(byte[] buf, int off, int nbytes) {
if (impl == null) {
throw new IllegalStateException();
}
// avoid int overflow, check null buf
if (off <= buf.length && nbytes >= 0 && off >= 0 && buf.length - off >= nbytes) {
if (off > 0) {
buf = Arrays.copyOfRange(buf, off, buf.length);
}
impl.setDictionary(buf, nbytes);
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
public void setInput(byte[] buf) {
setInput(buf, 0, buf.length);
}
public void setInput(byte[] buf, int off, int nbytes) {
if (impl == null) {
throw new IllegalStateException();
}
// avoid int overflow, check null buf
if (off <= buf.length && nbytes >= 0 && off >= 0 && buf.length - off >= nbytes) {
inRead = 0;
inLength = nbytes;
impl.setInput(buf, off, nbytes, false);
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Alexey Andreev.
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,16 +13,173 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import com.jcraft.jzlib.InflaterInputStream;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class TInflaterInputStream extends FilterInputStream {
public TInflaterInputStream(InputStream in) throws IOException {
super(in);
this.in = new InflaterInputStream(in);
protected TInflater inf;
protected byte[] buf;
protected int len;
boolean closed;
boolean eof;
static final int BUF_SIZE = 512;
public TInflaterInputStream(InputStream is) {
this(is, new TInflater(), BUF_SIZE);
}
public TInflaterInputStream(InputStream is, TInflater inf) {
this(is, inf, BUF_SIZE);
}
public TInflaterInputStream(InputStream is, TInflater inf, int bsize) {
super(is);
if (is == null || inf == null) {
throw new NullPointerException();
}
if (bsize <= 0) {
throw new IllegalArgumentException();
}
this.inf = inf;
buf = new byte[bsize];
}
@Override
public int read() throws IOException {
byte[] b = new byte[1];
if (read(b, 0, 1) == -1) {
return -1;
}
return b[0] & 0xff;
}
@Override
public int read(byte[] buffer, int off, int nbytes) throws IOException {
if (closed) {
throw new IOException("Stream is closed");
}
if (null == buffer) {
throw new NullPointerException();
}
if (off < 0 || nbytes < 0 || off + nbytes > buffer.length) {
throw new IndexOutOfBoundsException();
}
if (nbytes == 0) {
return 0;
}
if (eof) {
return -1;
}
// avoid int overflow, check null buffer
if (off > buffer.length || nbytes < 0 || off < 0 || buffer.length - off < nbytes) {
throw new ArrayIndexOutOfBoundsException();
}
do {
if (inf.needsInput()) {
fill();
}
// Invariant: if reading returns -1 or throws, eof must be true.
// It may also be true if the next read() should return -1.
try {
int result = inf.inflate(buffer, off, nbytes);
eof = inf.finished();
if (result > 0) {
return result;
} else if (eof) {
return -1;
} else if (inf.needsDictionary()) {
eof = true;
return -1;
} else if (len == -1) {
eof = true;
throw new EOFException();
// If result == 0, fill() and try again
}
} catch (TDataFormatException e) {
eof = true;
if (len == -1) {
throw new EOFException();
}
throw new IOException(e);
}
} while (true);
}
protected void fill() throws IOException {
if (closed) {
throw new IOException();
}
len = in.read(buf);
if (len > 0) {
inf.setInput(buf, 0, len);
}
}
@Override
public long skip(long nbytes) throws IOException {
if (nbytes >= 0) {
if (buf == null) {
buf = new byte[(int) Math.min(nbytes, BUF_SIZE)];
}
long count = 0;
long rem;
while (count < nbytes) {
rem = nbytes - count;
int x = read(buf, 0, rem > buf.length ? buf.length : (int) rem);
if (x == -1) {
return count;
}
count += x;
}
return count;
}
throw new IllegalArgumentException();
}
@Override
public int available() throws IOException {
if (closed) {
throw new IOException("Stream is closed");
}
if (eof) {
return 0;
}
return 1;
}
@Override
public void close() throws IOException {
if (!closed) {
inf.end();
closed = true;
eof = true;
super.close();
}
}
@Override
public void mark(int readlimit) {
// do nothing
}
@Override
public void reset() throws IOException {
throw new IOException();
}
@Override
public boolean markSupported() {
return false;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
interface TZipConstants {
long LOCSIG = 0x4034b50;
long EXTSIG = 0x8074b50;
long CENSIG = 0x2014b50;
long ENDSIG = 0x6054b50;
int LOCHDR = 30;
int EXTHDR = 16;
int CENHDR = 46;
int ENDHDR = 22;
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;
int EXTCRC = 4;
int EXTSIZ = 8;
int EXTLEN = 12;
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;
int ENDSUB = 8;
int ENDTOT = 10;
int ENDSIZ = 12;
int ENDOFF = 16;
int ENDCOM = 20;
}

View File

@ -0,0 +1,310 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class TZipEntry implements TZipConstants, Cloneable {
String name;
String comment;
long compressedSize = -1;
long crc = -1;
long size = -1;
int compressionMethod = -1;
int time = -1;
int modDate = -1;
byte[] extra;
int nameLen = -1;
long mLocalHeaderRelOffset = -1;
public static final int DEFLATED = 8;
public static final int STORED = 0;
public TZipEntry(String name) {
if (name == null) {
throw new NullPointerException();
}
if (name.length() > 0xFFFF) {
throw new IllegalArgumentException();
}
this.name = name;
}
public String getComment() {
return comment;
}
public long getCompressedSize() {
return compressedSize;
}
public long getCrc() {
return crc;
}
public byte[] getExtra() {
return extra;
}
public int getMethod() {
return compressionMethod;
}
public String getName() {
return name;
}
public long getSize() {
return size;
}
public long getTime() {
if (time != -1) {
GregorianCalendar cal = new GregorianCalendar();
cal.set(Calendar.MILLISECOND, 0);
cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1,
modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f,
(time & 0x1f) << 1);
return cal.getTime().getTime();
}
return -1;
}
public boolean isDirectory() {
return name.charAt(name.length() - 1) == '/';
}
public void setComment(String string) {
if (string == null || string.length() <= 0xFFFF) {
comment = string;
} else {
throw new IllegalArgumentException();
}
}
public void setCompressedSize(long value) {
compressedSize = value;
}
public void setCrc(long value) {
if (value >= 0 && value <= 0xFFFFFFFFL) {
crc = value;
} else {
throw new IllegalArgumentException();
}
}
public void setExtra(byte[] data) {
if (data == null || data.length <= 0xFFFF) {
extra = data;
} else {
throw new IllegalArgumentException();
}
}
public void setMethod(int value) {
if (value != STORED && value != DEFLATED) {
throw new IllegalArgumentException();
}
compressionMethod = value;
}
public void setSize(long value) {
if (value >= 0 && value <= 0xFFFFFFFFL) {
size = value;
} else {
throw new IllegalArgumentException();
}
}
public void setTime(long value) {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date(value));
int year = cal.get(Calendar.YEAR);
if (year < 1980) {
modDate = 0x21;
time = 0;
} else {
modDate = cal.get(Calendar.DATE);
modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
time = cal.get(Calendar.SECOND) >> 1;
time = (cal.get(Calendar.MINUTE) << 5) | time;
time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
}
}
@Override
public String toString() {
return name;
}
public TZipEntry(TZipEntry ze) {
name = ze.name;
comment = ze.comment;
time = ze.time;
size = ze.size;
compressedSize = ze.compressedSize;
crc = ze.crc;
compressionMethod = ze.compressionMethod;
modDate = ze.modDate;
extra = ze.extra;
nameLen = ze.nameLen;
mLocalHeaderRelOffset = ze.mLocalHeaderRelOffset;
}
@Override
public Object clone() {
return new TZipEntry(this);
}
/*
* Internal constructor. Creates a new TZipEntry by reading the
* Central Directory Entry from "in", which must be positioned at
* the CDE signature.
*
* On exit, "in" will be positioned at the start of the next entry.
*/
TZipEntry(LittleEndianReader ler, InputStream in) throws IOException {
/*
* We're seeing performance issues when we call readShortLE and
* readIntLE, so we're going to read the entire header at once
* and then parse the results out without using any function calls.
* Uglier, but should be much faster.
*
* Note that some lines look a bit different, because the corresponding
* fields or locals are long and so we need to do & 0xffffffffl to avoid
* problems induced by sign extension.
*/
byte[] hdrBuf = ler.hdrBuf;
myReadFully(in, hdrBuf);
long sig = (hdrBuf[0] & 0xff) | ((hdrBuf[1] & 0xff) << 8)
| ((hdrBuf[2] & 0xff) << 16) | ((hdrBuf[3] << 24) & 0xffffffffL);
if (sig != CENSIG) {
throw new TZipException();
}
compressionMethod = (hdrBuf[10] & 0xff) | ((hdrBuf[11] & 0xff) << 8);
time = (hdrBuf[12] & 0xff) | ((hdrBuf[13] & 0xff) << 8);
modDate = (hdrBuf[14] & 0xff) | ((hdrBuf[15] & 0xff) << 8);
crc = (hdrBuf[16] & 0xff) | ((hdrBuf[17] & 0xff) << 8)
| ((hdrBuf[18] & 0xff) << 16)
| ((hdrBuf[19] << 24) & 0xffffffffL);
compressedSize = (hdrBuf[20] & 0xff) | ((hdrBuf[21] & 0xff) << 8)
| ((hdrBuf[22] & 0xff) << 16)
| ((hdrBuf[23] << 24) & 0xffffffffL);
size = (hdrBuf[24] & 0xff) | ((hdrBuf[25] & 0xff) << 8)
| ((hdrBuf[26] & 0xff) << 16)
| ((hdrBuf[27] << 24) & 0xffffffffL);
nameLen = (hdrBuf[28] & 0xff) | ((hdrBuf[29] & 0xff) << 8);
int extraLen = (hdrBuf[30] & 0xff) | ((hdrBuf[31] & 0xff) << 8);
int commentLen = (hdrBuf[32] & 0xff) | ((hdrBuf[33] & 0xff) << 8);
mLocalHeaderRelOffset = (hdrBuf[42] & 0xff) | ((hdrBuf[43] & 0xff) << 8)
| ((hdrBuf[44] & 0xff) << 16)
| ((hdrBuf[45] << 24) & 0xffffffffL);
byte[] nameBytes = new byte[nameLen];
myReadFully(in, nameBytes);
byte[] commentBytes = null;
if (commentLen > 0) {
commentBytes = new byte[commentLen];
myReadFully(in, commentBytes);
}
if (extraLen > 0) {
extra = new byte[extraLen];
myReadFully(in, extra);
}
try {
/*
* The actual character set is "IBM Code Page 437". As of
* Sep 2006, the Zip spec (APPNOTE.TXT) supports UTF-8. When
* bit 11 of the GP flags field is set, the file name and
* comment fields are UTF-8.
*
* TODO: add correct UTF-8 support.
*/
name = new String(nameBytes, "ISO-8859-1");
comment = commentBytes != null ? new String(commentBytes, "ISO-8859-1") : null;
} catch (UnsupportedEncodingException uee) {
throw new InternalError(uee.getMessage());
}
}
private void myReadFully(InputStream in, byte[] b) throws IOException {
int len = b.length;
int off = 0;
while (len > 0) {
int count = in.read(b, off, len);
if (count <= 0) {
throw new EOFException();
}
off += count;
len -= count;
}
}
static long readIntLE(RandomAccessFile raf) throws IOException {
int b0 = raf.read();
int b1 = raf.read();
int b2 = raf.read();
int b3 = raf.read();
if (b3 < 0) {
throw new EOFException();
}
return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); // ATTENTION: DOES SIGN EXTENSION: IS THIS WANTED?
}
static class LittleEndianReader {
private byte[] b = new byte[4];
byte[] hdrBuf = new byte[CENHDR];
int readShortLE(InputStream in) throws IOException {
if (in.read(b, 0, 2) == 2) {
return (b[0] & 0XFF) | ((b[1] & 0XFF) << 8);
} else {
throw new EOFException();
}
}
long readIntLE(InputStream in) throws IOException {
if (in.read(b, 0, 4) == 4) {
return ((b[0] & 0XFF)
| ((b[1] & 0XFF) << 8)
| ((b[2] & 0XFF) << 16)
| ((b[3] & 0XFF) << 24))
& 0XFFFFFFFFL; // Here for sure NO sign extension is wanted.
} else {
throw new EOFException();
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import java.io.IOException;
public class TZipException extends IOException {
public TZipException() {
super();
}
public TZipException(String detailMessage) {
super(detailMessage);
}
}

View File

@ -0,0 +1,343 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import org.teavm.classlib.java.util.zip.TZipEntry.LittleEndianReader;
public class TZipFile implements TZipConstants {
public static final int OPEN_READ = 1;
public static final int OPEN_DELETE = 4;
private final String fileName;
private File fileToDeleteOnClose;
private RandomAccessFile mRaf;
private final LittleEndianReader ler = new LittleEndianReader();
private final LinkedHashMap<String, TZipEntry> mEntries = new LinkedHashMap<>();
public TZipFile(File file) throws TZipException, IOException {
this(file, OPEN_READ);
}
public TZipFile(File file, int mode) throws IOException {
fileName = file.getPath();
if (mode != OPEN_READ && mode != (OPEN_READ | OPEN_DELETE)) {
throw new IllegalArgumentException();
}
if ((mode & OPEN_DELETE) != 0) {
fileToDeleteOnClose = file; // file.deleteOnExit();
} else {
fileToDeleteOnClose = null;
}
mRaf = new RandomAccessFile(fileName, "r");
readCentralDir();
}
public TZipFile(String name) throws IOException {
this(new File(name), OPEN_READ);
}
@Override
protected void finalize() throws IOException {
close();
}
public void close() throws IOException {
RandomAccessFile raf = mRaf;
if (raf != null) {
mRaf = null;
raf.close();
if (fileToDeleteOnClose != null) {
new File(fileName).delete();
// fileToDeleteOnClose.delete();
fileToDeleteOnClose = null;
}
}
}
private void checkNotClosed() {
if (mRaf == null) {
throw new IllegalStateException();
}
}
public Enumeration<? extends TZipEntry> entries() {
checkNotClosed();
final Iterator<TZipEntry> iterator = mEntries.values().iterator();
return new Enumeration<TZipEntry>() {
@Override
public boolean hasMoreElements() {
checkNotClosed();
return iterator.hasNext();
}
@Override
public TZipEntry nextElement() {
checkNotClosed();
return iterator.next();
}
};
}
public TZipEntry getEntry(String entryName) {
checkNotClosed();
if (entryName == null) {
throw new NullPointerException();
}
TZipEntry ze = mEntries.get(entryName);
if (ze == null) {
ze = mEntries.get(entryName + "/");
}
return ze;
}
public InputStream getInputStream(TZipEntry entry) throws IOException {
/*
* Make sure this TZipEntry is in this Zip file. We run it through
* the name lookup.
*/
entry = getEntry(entry.getName());
if (entry == null) {
return null;
}
/*
* Create a TZipInputStream at the right part of the file.
*/
RandomAccessFile raf = mRaf;
// We don't know the entry data's start position. All we have is the
// position of the entry's local header. At position 28 we find the
// length of the extra data. In some cases this length differs from
// the one coming in the central header.
RAFStream rafstrm = new RAFStream(raf, entry.mLocalHeaderRelOffset + 28);
int localExtraLenOrWhatever = ler.readShortLE(rafstrm);
// Skip the name and this "extra" data or whatever it is:
rafstrm.skip(entry.nameLen + localExtraLenOrWhatever);
rafstrm.mLength = rafstrm.mOffset + entry.compressedSize;
if (entry.compressionMethod == java.util.zip.ZipEntry.DEFLATED) {
int bufSize = Math.max(1024, (int) Math.min(entry.getSize(), 65535L));
return new ZipInflaterInputStream(rafstrm, new TInflater(true), bufSize, entry);
} else {
return rafstrm;
}
}
public String getName() {
return fileName;
}
public int size() {
checkNotClosed();
return mEntries.size();
}
/**
* Find the central directory and read the contents.
*
* <p>The central directory can be followed by a variable-length comment
* field, so we have to scan through it backwards. The comment is at
* most 64K, plus we have 18 bytes for the end-of-central-dir stuff
* itself, plus apparently sometimes people throw random junk on the end
* just for the fun of it.
*
* <p>This is all a little wobbly. If the wrong value ends up in the EOCD
* area, we're hosed. This appears to be the way that everybody handles
* it though, so we're in good company if this fails.
*/
private void readCentralDir() throws IOException {
/*
* Scan back, looking for the End Of Central Directory field. If
* the archive doesn't have a comment, we'll hit it on the first
* try.
*
* No need to synchronize mRaf here -- we only do this when we
* first open the Zip file.
*/
long scanOffset = mRaf.length() - ENDHDR;
if (scanOffset < 0) {
throw new TZipException();
}
long stopOffset = scanOffset - 65536;
if (stopOffset < 0) {
stopOffset = 0;
}
while (true) {
mRaf.seek(scanOffset);
if (TZipEntry.readIntLE(mRaf) == 101010256L) {
break;
}
scanOffset--;
if (scanOffset < stopOffset) {
throw new TZipException();
}
}
/*
* Found it, read the EOCD.
*
* For performance we want to use buffered I/O when reading the
* file. We wrap a buffered stream around the random-access file
* object. If we just read from the RandomAccessFile we'll be
* doing a read() system call every time.
*/
RAFStream rafs = new RAFStream(mRaf, mRaf.getFilePointer());
BufferedInputStream bin = new BufferedInputStream(rafs, ENDHDR);
int diskNumber = ler.readShortLE(bin);
int diskWithCentralDir = ler.readShortLE(bin);
int numEntries = ler.readShortLE(bin);
int totalNumEntries = ler.readShortLE(bin);
/*centralDirSize =*/ ler.readIntLE(bin);
long centralDirOffset = ler.readIntLE(bin);
/*commentLen =*/ ler.readShortLE(bin);
if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
throw new TZipException();
}
/*
* Seek to the first CDE and read all entries.
* However, when Z_SYNC_FLUSH is used the offset may not point directly
* to the CDE so skip over until we find it.
* At most it will be 6 bytes away (one or two bytes for empty block, 4 bytes for
* empty block signature).
*/
scanOffset = centralDirOffset;
stopOffset = scanOffset + 6;
while (true) {
mRaf.seek(scanOffset);
if (TZipEntry.readIntLE(mRaf) == CENSIG) {
break;
}
scanOffset++;
if (scanOffset > stopOffset) {
throw new TZipException();
}
}
// If CDE is found then go and read all the entries
rafs = new RAFStream(mRaf, scanOffset);
bin = new BufferedInputStream(rafs, 4096);
for (int i = 0; i < numEntries; i++) {
TZipEntry newEntry = new TZipEntry(ler, bin);
mEntries.put(newEntry.getName(), newEntry);
}
}
static class RAFStream extends InputStream {
RandomAccessFile mSharedRaf;
long mOffset;
long mLength;
public RAFStream(RandomAccessFile raf, long pos) throws IOException {
mSharedRaf = raf;
mOffset = pos;
mLength = raf.length();
}
@Override
public int available() throws IOException {
if (mLength > mOffset) {
if (mLength - mOffset < Integer.MAX_VALUE) {
return (int) (mLength - mOffset);
} else {
return Integer.MAX_VALUE;
}
} else {
return 0;
}
}
@Override
public int read() throws IOException {
byte[] singleByteBuf = new byte[1];
if (read(singleByteBuf, 0, 1) == 1) {
return singleByteBuf[0] & 0XFF;
} else {
return -1;
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
mSharedRaf.seek(mOffset);
if (len > mLength - mOffset) {
len = (int) (mLength - mOffset);
}
int count = mSharedRaf.read(b, off, len);
if (count > 0) {
mOffset += count;
return count;
} else {
return -1;
}
}
@Override
public long skip(long n) throws IOException {
if (n > mLength - mOffset) {
n = mLength - mOffset;
}
mOffset += n;
return n;
}
}
static class ZipInflaterInputStream extends TInflaterInputStream {
TZipEntry entry;
long bytesRead;
public ZipInflaterInputStream(InputStream is, TInflater inf, int bsize, TZipEntry entry) {
super(is, inf, bsize);
this.entry = entry;
}
@Override
public int read(byte[] buffer, int off, int nbytes) throws IOException {
int i = super.read(buffer, off, nbytes);
if (i != -1) {
bytesRead += i;
}
return i;
}
@Override
public int available() throws IOException {
return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
}
}
}

View File

@ -0,0 +1,330 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
public class TZipInputStream extends TInflaterInputStream implements TZipConstants {
static final int DEFLATED = 8;
static final int STORED = 0;
static final int ZIPDataDescriptorFlag = 8;
static final int ZIPLocalHeaderVersionNeeded = 20;
private boolean entriesEnd;
private boolean hasDD;
private int entryIn;
private int inRead;
private int lastRead;
TZipEntry currentEntry;
private final byte[] hdrBuf = new byte[LOCHDR - LOCVER];
private final TCRC32 crc = new TCRC32();
private byte[] nameBuf = new byte[256];
private char[] charBuf = new char[256];
public TZipInputStream(InputStream stream) {
super(new PushbackInputStream(stream, 512), new TInflater(true));
if (stream == null) {
throw new NullPointerException();
}
}
@Override
public void close() throws IOException {
if (!closed) {
closeEntry(); // Close the current entry
super.close();
}
}
public void closeEntry() throws IOException {
if (closed) {
throw new IOException();
}
if (currentEntry == null) {
return;
}
/*
* The following code is careful to leave the TZipInputStream in a
* consistent state, even when close() results in an exception. It does
* so by:
* - pushing bytes back into the source stream
* - reading a data descriptor footer from the source stream
* - resetting fields that manage the entry being closed
*/
// Ensure all entry bytes are read
Exception failure = null;
try {
skip(Long.MAX_VALUE);
} catch (Exception e) {
failure = e;
}
int inB;
int out;
if (currentEntry.compressionMethod == DEFLATED) {
inB = inf.getTotalIn();
out = inf.getTotalOut();
} else {
inB = inRead;
out = inRead;
}
int diff = entryIn - inB;
// Pushback any required bytes
if (diff != 0) {
((PushbackInputStream) in).unread(buf, len - diff, diff);
}
try {
readAndVerifyDataDescriptor(inB, out);
} catch (Exception e) {
if (failure == null) { // otherwise we're already going to throw
failure = e;
}
}
inf.reset();
lastRead = 0;
inRead = 0;
entryIn = 0;
len = 0;
crc.reset();
currentEntry = null;
if (failure != null) {
if (failure instanceof IOException) {
throw (IOException) failure;
} else if (failure instanceof RuntimeException) {
throw (RuntimeException) failure;
}
throw new AssertionError(failure);
}
}
private void readAndVerifyDataDescriptor(int inB, int out) throws IOException {
if (hasDD) {
in.read(hdrBuf, 0, EXTHDR);
if (getLong(hdrBuf, 0) != EXTSIG) {
throw new TZipException();
}
currentEntry.crc = getLong(hdrBuf, EXTCRC);
currentEntry.compressedSize = getLong(hdrBuf, EXTSIZ);
currentEntry.size = getLong(hdrBuf, EXTLEN);
}
if (currentEntry.crc != crc.getValue()) {
throw new TZipException();
}
if (currentEntry.compressedSize != inB || currentEntry.size != out) {
throw new TZipException();
}
}
public TZipEntry getNextEntry() throws IOException {
closeEntry();
if (entriesEnd) {
return null;
}
int x = 0;
int count = 0;
while (count != 4) {
x = in.read(hdrBuf, count, 4 - count);
count += x;
if (x == -1) {
return null;
}
}
long hdr = getLong(hdrBuf, 0);
if (hdr == CENSIG) {
entriesEnd = true;
return null;
}
if (hdr != LOCSIG) {
return null;
}
// Read the local header
count = 0;
while (count != LOCHDR - LOCVER) {
x = in.read(hdrBuf, count, (LOCHDR - LOCVER) - count);
count += x;
if (x == -1) {
throw new EOFException();
}
}
int version = getShort(hdrBuf, 0) & 0xff;
if (version > ZIPLocalHeaderVersionNeeded) {
throw new TZipException();
}
int flags = getShort(hdrBuf, LOCFLG - LOCVER);
hasDD = (flags & ZIPDataDescriptorFlag) == ZIPDataDescriptorFlag;
int cetime = getShort(hdrBuf, LOCTIM - LOCVER);
int cemodDate = getShort(hdrBuf, LOCTIM - LOCVER + 2);
int cecompressionMethod = getShort(hdrBuf, LOCHOW - LOCVER);
long cecrc = 0;
long cecompressedSize = 0;
long cesize = -1;
if (!hasDD) {
cecrc = getLong(hdrBuf, LOCCRC - LOCVER);
cecompressedSize = getLong(hdrBuf, LOCSIZ - LOCVER);
cesize = getLong(hdrBuf, LOCLEN - LOCVER);
}
int flen = getShort(hdrBuf, LOCNAM - LOCVER);
if (flen == 0) {
throw new TZipException();
}
int elen = getShort(hdrBuf, LOCEXT - LOCVER);
count = 0;
if (flen > nameBuf.length) {
nameBuf = new byte[flen];
charBuf = new char[flen];
}
while (count != flen) {
x = in.read(nameBuf, count, flen - count);
count += x;
if (x == -1) {
throw new EOFException();
}
}
currentEntry = createZipEntry(new String(nameBuf, 0, flen, "UTF-8"));
currentEntry.time = cetime;
currentEntry.modDate = cemodDate;
currentEntry.setMethod(cecompressionMethod);
if (cesize != -1) {
currentEntry.setCrc(cecrc);
currentEntry.setSize(cesize);
currentEntry.setCompressedSize(cecompressedSize);
}
if (elen > 0) {
count = 0;
byte[] e = new byte[elen];
while (count != elen) {
x = in.read(e, count, elen - count);
count += x;
if (x == -1) {
throw new EOFException();
}
}
currentEntry.setExtra(e);
}
return currentEntry;
}
@Override
public int read(byte[] buffer, int start, int length) throws IOException {
if (closed) {
throw new IOException();
}
if (inf.finished() || currentEntry == null) {
return -1;
}
// avoid int overflow, check null buffer
if (start > buffer.length || length < 0 || start < 0 || buffer.length - start < length) {
throw new ArrayIndexOutOfBoundsException();
}
if (currentEntry.compressionMethod == STORED) {
int csize = (int) currentEntry.size;
if (inRead >= csize) {
return -1;
}
if (lastRead >= len) {
lastRead = 0;
len = in.read(buf);
if (len == -1) {
eof = true;
return -1;
}
entryIn += len;
}
int toRead = length > len - lastRead ? len - lastRead : length;
if ((csize - inRead) < toRead) {
toRead = csize - inRead;
}
System.arraycopy(buf, lastRead, buffer, start, toRead);
lastRead += toRead;
inRead += toRead;
crc.update(buffer, start, toRead);
return toRead;
}
if (inf.needsInput()) {
fill();
if (len > 0) {
entryIn += len;
}
}
int read;
try {
read = inf.inflate(buffer, start, length);
} catch (TDataFormatException e) {
throw new TZipException(e.getMessage());
}
if (read == 0 && inf.finished()) {
return -1;
}
crc.update(buffer, start, read);
return read;
}
@Override
public long skip(long value) throws IOException {
if (value < 0) {
throw new IllegalArgumentException();
}
long skipped = 0;
byte[] b = new byte[(int) Math.min(value, 2048L)];
while (skipped != value) {
long rem = value - skipped;
int x = read(b, 0, (int) (b.length > rem ? rem : b.length));
if (x == -1) {
return skipped;
}
skipped += x;
}
return skipped;
}
@Override
public int available() throws IOException {
if (closed) {
throw new IOException();
}
return (currentEntry == null || inRead < currentEntry.size) ? 1 : 0;
}
protected TZipEntry createZipEntry(String name) {
return new TZipEntry(name);
}
private int getShort(byte[] buffer, int off) {
return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8);
}
private long getLong(byte[] buffer, int off) {
long l = 0;
l |= buffer[off] & 0xFF;
l |= (buffer[off + 1] & 0xFF) << 8;
l |= (buffer[off + 2] & 0xFF) << 16;
l |= ((long) (buffer[off + 3] & 0xFF)) << 24;
return l;
}
}

View File

@ -0,0 +1,330 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.util.zip;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
public class TZipOutputStream extends TDeflaterOutputStream implements TZipConstants {
public static final int DEFLATED = 8;
public static final int STORED = 0;
static final int ZIPDataDescriptorFlag = 8;
static final int ZIPLocalHeaderVersionNeeded = 20;
private String comment;
private final List<String> entries = new ArrayList<>();
private int compressMethod = DEFLATED;
private int compressLevel = TDeflater.DEFAULT_COMPRESSION;
private ByteArrayOutputStream cDir = new ByteArrayOutputStream();
private TZipEntry currentEntry;
private final TCRC32 crc = new TCRC32();
private int offset;
private int curOffset;
private int nameLength;
private byte[] nameBytes;
public TZipOutputStream(OutputStream p1) {
super(p1, new TDeflater(TDeflater.DEFAULT_COMPRESSION, true));
}
@Override
public void close() throws IOException {
if (out != null) {
finish();
out.close();
out = null;
}
}
public void closeEntry() throws IOException {
if (cDir == null) {
throw new IOException();
}
if (currentEntry == null) {
return;
}
if (currentEntry.getMethod() == DEFLATED) {
super.finish();
}
// Verify values for STORED types
if (currentEntry.getMethod() == STORED) {
if (crc.getValue() != currentEntry.crc) {
throw new TZipException();
}
if (currentEntry.size != crc.tbytes) {
throw new TZipException();
}
}
curOffset = LOCHDR;
// Write the DataDescriptor
if (currentEntry.getMethod() != STORED) {
curOffset += EXTHDR;
writeLong(out, EXTSIG);
currentEntry.crc = crc.getValue();
writeLong(out, currentEntry.crc);
currentEntry.compressedSize = def.getTotalOut();
writeLong(out, currentEntry.compressedSize);
currentEntry.size = def.getTotalIn();
writeLong(out, currentEntry.size);
}
// Update the CentralDirectory
writeLong(cDir, CENSIG);
writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version created
writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version to extract
writeShort(cDir, currentEntry.getMethod() == STORED ? 0 : ZIPDataDescriptorFlag);
writeShort(cDir, currentEntry.getMethod());
writeShort(cDir, currentEntry.time);
writeShort(cDir, currentEntry.modDate);
writeLong(cDir, crc.getValue());
if (currentEntry.getMethod() == DEFLATED) {
curOffset += writeLong(cDir, def.getTotalOut());
writeLong(cDir, def.getTotalIn());
} else {
curOffset += writeLong(cDir, crc.tbytes);
writeLong(cDir, crc.tbytes);
}
curOffset += writeShort(cDir, nameLength);
if (currentEntry.extra != null) {
curOffset += writeShort(cDir, currentEntry.extra.length);
} else {
writeShort(cDir, 0);
}
String c = currentEntry.getComment();
writeShort(cDir, c != null ? c.length() : 0);
writeShort(cDir, 0); // Disk Start
writeShort(cDir, 0); // Internal File Attributes
writeLong(cDir, 0); // External File Attributes
writeLong(cDir, offset);
cDir.write(nameBytes);
nameBytes = null;
if (currentEntry.extra != null) {
cDir.write(currentEntry.extra);
}
offset += curOffset;
if (c != null) {
cDir.write(c.getBytes());
}
currentEntry = null;
crc.reset();
def.reset();
done = false;
}
@Override
public void finish() throws IOException {
if (out == null) {
throw new IOException();
}
if (cDir == null) {
return;
}
if (entries.size() == 0) {
throw new TZipException();
}
if (currentEntry != null) {
closeEntry();
}
int cdirSize = cDir.size();
// Write Central Dir End
writeLong(cDir, ENDSIG);
writeShort(cDir, 0); // Disk Number
writeShort(cDir, 0); // Start Disk
writeShort(cDir, entries.size()); // Number of entries
writeShort(cDir, entries.size()); // Number of entries
writeLong(cDir, cdirSize); // Size of central dir
writeLong(cDir, offset); // Offset of central dir
if (comment != null) {
writeShort(cDir, comment.length());
cDir.write(comment.getBytes());
} else {
writeShort(cDir, 0);
}
// Write the central dir
out.write(cDir.toByteArray());
cDir = null;
}
public void putNextEntry(TZipEntry ze) throws IOException {
if (currentEntry != null) {
closeEntry();
}
if (ze.getMethod() == STORED
|| (compressMethod == STORED && ze.getMethod() == -1)) {
if (ze.crc == -1) {
throw new TZipException("Crc mismatch");
}
if (ze.size == -1 && ze.compressedSize == -1) {
throw new TZipException("Size mismatch");
}
if (ze.size != ze.compressedSize && ze.compressedSize != -1 && ze.size != -1) {
throw new TZipException("Size mismatch");
}
}
if (cDir == null) {
throw new IOException("Stream is closed");
}
if (entries.contains(ze.name)) {
throw new TZipException("Entry already exists: " + ze.name);
}
nameLength = utf8Count(ze.name);
if (nameLength > 0xffff) {
throw new IllegalArgumentException("Name too long: " + ze.name);
}
def.setLevel(compressLevel);
currentEntry = ze;
entries.add(currentEntry.name);
if (currentEntry.getMethod() == -1) {
currentEntry.setMethod(compressMethod);
}
writeLong(out, LOCSIG); // Entry header
writeShort(out, ZIPLocalHeaderVersionNeeded); // Extraction version
writeShort(out, currentEntry.getMethod() == STORED ? 0 : ZIPDataDescriptorFlag);
writeShort(out, currentEntry.getMethod());
if (currentEntry.getTime() == -1) {
currentEntry.setTime(System.currentTimeMillis());
}
writeShort(out, currentEntry.time);
writeShort(out, currentEntry.modDate);
if (currentEntry.getMethod() == STORED) {
if (currentEntry.size == -1) {
currentEntry.size = currentEntry.compressedSize;
} else if (currentEntry.compressedSize == -1) {
currentEntry.compressedSize = currentEntry.size;
}
writeLong(out, currentEntry.crc);
writeLong(out, currentEntry.size);
writeLong(out, currentEntry.size);
} else {
writeLong(out, 0);
writeLong(out, 0);
writeLong(out, 0);
}
writeShort(out, nameLength);
writeShort(out, currentEntry.extra != null ? currentEntry.extra.length : 0);
nameBytes = toUTF8Bytes(currentEntry.name, nameLength);
out.write(nameBytes);
if (currentEntry.extra != null) {
out.write(currentEntry.extra);
}
}
public void setComment(String comment) {
if (comment.length() > 0xFFFF) {
throw new IllegalArgumentException();
}
this.comment = comment;
}
public void setLevel(int level) {
if (level < TDeflater.DEFAULT_COMPRESSION || level > TDeflater.BEST_COMPRESSION) {
throw new IllegalArgumentException();
}
compressLevel = level;
}
public void setMethod(int method) {
if (method != STORED && method != DEFLATED) {
throw new IllegalArgumentException();
}
compressMethod = method;
}
private long writeLong(OutputStream os, long i) throws IOException {
// Write out the long value as an unsigned int
os.write((int) (i & 0xFF));
os.write((int) (i >> 8) & 0xFF);
os.write((int) (i >> 16) & 0xFF);
os.write((int) (i >> 24) & 0xFF);
return i;
}
private int writeShort(OutputStream os, int i) throws IOException {
os.write(i & 0xFF);
os.write((i >> 8) & 0xFF);
return i;
}
/**
* Writes data for the current entry to the underlying stream.
*
* @exception IOException
* If an error occurs writing to the stream
*/
@Override
public void write(byte[] buffer, int off, int nbytes)
throws IOException {
// avoid int overflow, check null buf
if ((off < 0 || (nbytes < 0) || off > buffer.length) || (buffer.length - off < nbytes)) {
throw new IndexOutOfBoundsException();
}
if (currentEntry == null) {
throw new TZipException("No active entry");
}
if (currentEntry.getMethod() == STORED) {
out.write(buffer, off, nbytes);
} else {
super.write(buffer, off, nbytes);
}
crc.update(buffer, off, nbytes);
}
static int utf8Count(String value) {
int total = 0;
for (int i = value.length(); --i >= 0;) {
char ch = value.charAt(i);
if (ch < 0x80) {
total++;
} else if (ch < 0x800) {
total += 2;
} else {
total += 3;
}
}
return total;
}
static byte[] toUTF8Bytes(String value, int length) {
byte[] result = new byte[length];
int pos = result.length;
for (int i = value.length(); --i >= 0;) {
char ch = value.charAt(i);
if (ch < 0x80) {
result[--pos] = (byte) ch;
} else if (ch < 0x800) {
result[--pos] = (byte) (0x80 | (ch & 0x3f));
result[--pos] = (byte) (0xc0 | (ch >> 6));
} else {
result[--pos] = (byte) (0x80 | (ch & 0x3f));
result[--pos] = (byte) (0x80 | ((ch >> 6) & 0x3f));
result[--pos] = (byte) (0xe0 | (ch >> 12));
}
}
return result;
}
}