Browse Source

Fix #1843 (java.util.zip.ZipException in HttpZipLocator) (#1842)

* HttpZipLocator:fix invalid code lengths set & invalid distance too far back ZipExceptions.

* Get file name length and extra field length from local file header to calculate file data offset.

* Make fields `byteBuf`, `charBuf`, `utf8Decoder` non-static because they are not thread-safe.

* Surround streams with try-with-resources block.
Ali-RS 3 years ago
parent
commit
f0b7a96e64
1 changed files with 36 additions and 28 deletions
  1. 36 28
      jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java

+ 36 - 28
jme3-core/src/plugins/java/com/jme3/asset/plugins/HttpZipLocator.java

@@ -42,7 +42,7 @@ import java.net.URL;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CoderResult;
 import java.util.HashMap;
@@ -80,14 +80,9 @@ public class HttpZipLocator implements AssetLocator {
     private int tableLength;
     private HashMap<String, ZipEntry2> entries;
     
-    private static final ByteBuffer byteBuf = ByteBuffer.allocate(250);
-    private static final CharBuffer charBuf = CharBuffer.allocate(250);
-    private static final CharsetDecoder utf8Decoder;
-    
-    static {
-        Charset utf8 = Charset.forName("UTF-8");
-        utf8Decoder = utf8.newDecoder();
-    }
+    private final ByteBuffer byteBuf = ByteBuffer.allocate(250);
+    private final CharBuffer charBuf = CharBuffer.allocate(250);
+    private final CharsetDecoder utf8Decoder = StandardCharsets.UTF_8.newDecoder();
 
     private static class ZipEntry2 {
         String name;
@@ -96,6 +91,10 @@ public class HttpZipLocator implements AssetLocator {
         int compSize;
         long crc;
         boolean deflate;
+        // These fields will be fetched later from local file header,
+        // once asset requested.
+        Integer nameLength;
+        Integer extraLength;
 
         @Override
         public String toString(){
@@ -125,7 +124,7 @@ public class HttpZipLocator implements AssetLocator {
              (((long)(b[off]&0xff)) << 24);
     }
 
-    private static String getUTF8String(byte[] b, int off, int len) throws CharacterCodingException {
+    private String getUTF8String(byte[] b, int off, int len) throws CharacterCodingException {
         StringBuilder sb = new StringBuilder();
         
         int read = 0;
@@ -143,7 +142,7 @@ public class HttpZipLocator implements AssetLocator {
             byteBuf.flip();
             
             // decode data in byteBuf
-            CoderResult result = utf8Decoder.decode(byteBuf, charBuf, endOfInput); 
+            CoderResult result = utf8Decoder.decode(byteBuf, charBuf, endOfInput);
             
             // If the result is not an underflow, it's an error
             // that cannot be handled.
@@ -234,10 +233,6 @@ public class HttpZipLocator implements AssetLocator {
         entry.compSize = get32(table, offset + ZipEntry.CENSIZ);
         entry.offset   = get32(table, offset + ZipEntry.CENOFF);
 
-        // We want the offset in the file data:
-        // move the offset forward to skip the LOC header.
-        entry.offset += ZipEntry.LOCHDR + nameLen + extraLen;
-
         entries.put(entry.name, entry);
         
         return newOffset;
@@ -256,16 +251,15 @@ public class HttpZipLocator implements AssetLocator {
     }
 
     private void readCentralDirectory() throws IOException{
-        InputStream in = readData(tableOffset, tableLength);
         byte[] header = new byte[tableLength];
-
-        // Fix for "PK12 bug in town.zip": sometimes
-        // not entire byte array will be read with InputStream.read()
-        // (especially for big headers)
-        fillByteArray(header, in);
+        try (InputStream in = readData(tableOffset, tableLength)) {
+            // Fix for "PK12 bug in town.zip": sometimes
+            // not entire byte array will be read with InputStream.read()
+            // (especially for big headers)
+            fillByteArray(header, in);
 
 //        in.read(header);
-        in.close();
+        }
 
         entries = new HashMap<String, ZipEntry2>(numEntries);
         int offset = 0;
@@ -292,10 +286,10 @@ public class HttpZipLocator implements AssetLocator {
         // In that case, we have to search for it.
         // Increase search space to 200 bytes
 
-        InputStream in = readData(Integer.MAX_VALUE, 200);
         byte[] header = new byte[200];
-        fillByteArray(header, in);
-        in.close();
+        try (InputStream in = readData(Integer.MAX_VALUE, 200)) {
+            fillByteArray(header, in);
+        }
 
         int offset = -1;
         for (int i = 200 - 22; i >= 0; i--){
@@ -323,9 +317,23 @@ public class HttpZipLocator implements AssetLocator {
         readCentralDirectory();
     }
 
-    private InputStream openStream(ZipEntry2 entry) throws IOException{
-        InputStream in = readData(entry.offset, entry.compSize);
-        if (entry.deflate){
+    private InputStream openStream(ZipEntry2 entry) throws IOException {
+        if (entry.nameLength == null && entry.extraLength == null) {
+            // Need to fetch local file header to obtain file name length
+            // and extra field length.
+            try (InputStream in = readData(entry.offset, ZipEntry.LOCHDR)) {
+                byte[] localHeader = new byte[ZipEntry.LOCHDR];
+                in.read(localHeader);
+                entry.nameLength = get16(localHeader, ZipEntry.LOCNAM);
+                entry.extraLength = get16(localHeader, ZipEntry.LOCEXT);
+            }
+        }
+
+        // We want the offset in the file data:
+        // move the offset forward to skip the LOC header.
+        int fileDataOffset = entry.offset + ZipEntry.LOCHDR + entry.nameLength + entry.extraLength;
+        InputStream in = readData(fileDataOffset, entry.compSize);
+        if (entry.deflate) {
             return new InflaterInputStream(in, new Inflater(true));
         }
         return in;