All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zving.preloader.zip.ZipFile Maven / Gradle / Ivy

There is a newer version: 0.3.0
Show newest version
package com.zving.preloader.zip;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.zip.CRC32;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipException;

public class ZipFile
{
  private static final int HASH_SIZE = 509;
  private static final int SHORT = 2;
  private static final int WORD = 4;
  private static final int NIBLET_MASK = 15;
  private static final int BYTE_SHIFT = 8;
  private static final int POS_0 = 0;
  private static final int POS_1 = 1;
  private static final int POS_2 = 2;
  private static final int POS_3 = 3;
  private final Map entries;
  private final Map nameMap;
  private String encoding;
  private final ZipEncoding zipEncoding;
  private RandomAccessFile archive;
  private final boolean useUnicodeExtraFields;
  private static final int CFH_LEN = 42;
  private static final int MIN_EOCD_SIZE = 22;
  private static final int MAX_EOCD_SIZE = 65557;
  private static final int CFD_LOCATOR_OFFSET = 16;
  private static final long LFH_OFFSET_FOR_FILENAME_LENGTH = 26L;
  
  private static final class OffsetEntry
  {
	  private long headerOffset;
      private long dataOffset;

      private OffsetEntry()
      {
          headerOffset = -1L;
          dataOffset = -1L;
      }

      OffsetEntry(OffsetEntry offsetentry)
      {
          this();
      }

  }
  
  public ZipFile(File f)
    throws IOException
  {
    this(f, null);
  }
  
  public ZipFile(String name)
    throws IOException
  {
    this(new File(name), null);
  }
  
  public ZipFile(String name, String encoding)
    throws IOException
  {
    this(new File(name), encoding, true);
  }
  
  public ZipFile(File f, String encoding)
    throws IOException
  {
    this(f, encoding, true);
  }
  
  public ZipFile(File f, String encoding, boolean useUnicodeExtraFields)
	        throws IOException
	    {
	        boolean success;
	        entries = new HashMap(509);
	        nameMap = new HashMap(509);
	        this.encoding = null;
	        this.encoding = encoding;
	        zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
	        this.useUnicodeExtraFields = useUnicodeExtraFields;
	        archive = new RandomAccessFile(f, "r");
	        success = false;
	        Map entriesWithoutUTF8Flag = populateFromCentralDirectory();
	        resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
	        success = true;
	        if(!success)
	            try
	            {
	                archive.close();
	            }
	            catch(IOException ioexception) { }
	        return;
	    }

  
  public String getEncoding()
  {
    return this.encoding;
  }
  
  public void close()
    throws IOException
  {
    this.archive.close();
  }
  
  public static void closeQuietly(ZipFile zipfile)
  {
    if (zipfile != null) {
      try
      {
        zipfile.close();
      }
      catch (IOException localIOException) {}
    }
  }
  
  public Enumeration getEntries()
  {
    return Collections.enumeration(this.entries.keySet());
  }
  
  public ZipEntry getEntry(String name)
  {
    return (ZipEntry)this.nameMap.get(name);
  }
  
  public InputStream getInputStream(ZipEntry ze)
    throws IOException, ZipException
  {
    OffsetEntry offsetEntry = (OffsetEntry)this.entries.get(ze);
    if (offsetEntry == null) {
      return null;
    }
    long start = offsetEntry.dataOffset;
    BoundedInputStream bis = new BoundedInputStream(start, ze.getCompressedSize());
    switch (ze.getMethod())
    {
    case 0: 
      return bis;
    case 8: 
      bis.addDummy();
      final Inflater inflater = new Inflater(true);
      new InflaterInputStream(bis, inflater)
      {
        public void close()
          throws IOException
        {
          super.close();
          inflater.end();
        }
      };
    }
    throw new ZipException("Found unsupported compression method " + ze.getMethod());
  }
  
  private Map populateFromCentralDirectory()
    throws IOException
  {
    HashMap noUTF8Flag = new HashMap();
    
    positionAtCentralDirectory();
    
    byte[] cfh = new byte[42];
    
    byte[] signatureBytes = new byte[4];
    this.archive.readFully(signatureBytes);
    long sig = ZipLong.getValue(signatureBytes);
    long cfhSig = ZipLong.getValue(ZipOutputStream.CFH_SIG);
    if ((sig != cfhSig) && (startsWithLocalFileHeader())) {
      throw new IOException("central directory is empty, can't expand corrupt archive.");
    }
    while (sig == cfhSig)
    {
      this.archive.readFully(cfh);
      int off = 0;
      ZipEntry ze = new ZipEntry();
      
      int versionMadeBy = ZipShort.getValue(cfh, off);
      off += 2;
      ze.setPlatform(versionMadeBy >> 8 & 0xF);
      
      off += 2;
      
      int generalPurposeFlag = ZipShort.getValue(cfh, off);
      boolean hasUTF8Flag = (generalPurposeFlag & 0x800) != 0;
      ZipEncoding entryEncoding = hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : this.zipEncoding;
      
      off += 2;
      
      ze.setMethod(ZipShort.getValue(cfh, off));
      off += 2;
      
      long time = dosToJavaTime(ZipLong.getValue(cfh, off));
      ze.setTime(time);
      off += 4;
      
      ze.setCrc(ZipLong.getValue(cfh, off));
      off += 4;
      
      ze.setCompressedSize(ZipLong.getValue(cfh, off));
      off += 4;
      
      ze.setSize(ZipLong.getValue(cfh, off));
      off += 4;
      
      int fileNameLen = ZipShort.getValue(cfh, off);
      off += 2;
      
      int extraLen = ZipShort.getValue(cfh, off);
      off += 2;
      
      int commentLen = ZipShort.getValue(cfh, off);
      off += 2;
      
      off += 2;
      
      ze.setInternalAttributes(ZipShort.getValue(cfh, off));
      off += 2;
      
      ze.setExternalAttributes(ZipLong.getValue(cfh, off));
      off += 4;
      
      byte[] fileName = new byte[fileNameLen];
      this.archive.readFully(fileName);
      String fileNameStr = entryEncoding.decode(fileName);
      if (ZipUtil.isUTF8(fileName))
      {
        fileNameStr = new String(fileName, "UTF-8");
        hasUTF8Flag = true;
      }
      ze.setName(fileNameStr);
      
      OffsetEntry offset = new OffsetEntry(null);
      offset.headerOffset = ZipLong.getValue(cfh, off);
      
      this.entries.put(ze, offset);
      
      this.nameMap.put(ze.getName(), ze);
      
      byte[] cdExtraData = new byte[extraLen];
      this.archive.readFully(cdExtraData);
      ze.setCentralDirectoryExtra(cdExtraData);
      
      byte[] comment = new byte[commentLen];
      this.archive.readFully(comment);
      String commentStr = entryEncoding.decode(comment);
      if (ZipUtil.isUTF8(comment))
      {
        commentStr = new String(comment, "UTF-8");
        hasUTF8Flag = true;
      }
      ze.setComment(commentStr);
      
      this.archive.readFully(signatureBytes);
      sig = ZipLong.getValue(signatureBytes);
      if ((!hasUTF8Flag) && (this.useUnicodeExtraFields)) {
        noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
      }
    }
    return noUTF8Flag;
  }
  
  private void positionAtCentralDirectory()
    throws IOException
  {
    boolean found = false;
    long off = this.archive.length() - 22L;
    long stopSearching = Math.max(0L, this.archive.length() - 65557L);
    if (off >= 0L)
    {
      byte[] sig = ZipOutputStream.EOCD_SIG;
      for (; off >= stopSearching; off -= 1L)
      {
        this.archive.seek(off);
        int curr = this.archive.read();
        if (curr == -1) {
          break;
        }
        if (curr == sig[0])
        {
          curr = this.archive.read();
          if (curr == sig[1])
          {
            curr = this.archive.read();
            if (curr == sig[2])
            {
              curr = this.archive.read();
              if (curr == sig[3])
              {
                found = true;
                break;
              }
            }
          }
        }
      }
    }
    if (!found) {
      throw new ZipException("archive is not a ZIP archive");
    }
    this.archive.seek(off + 16L);
    byte[] cfdOffset = new byte[4];
    this.archive.readFully(cfdOffset);
    this.archive.seek(ZipLong.getValue(cfdOffset));
  }
  
  private void resolveLocalFileHeaderData(Map entriesWithoutUTF8Flag)
    throws IOException
  {
    Enumeration e = Collections.enumeration(new HashSet(this.entries.keySet()));
    while (e.hasMoreElements())
    {
      ZipEntry ze = (ZipEntry)e.nextElement();
      OffsetEntry offsetEntry = (OffsetEntry)this.entries.get(ze);
      long offset = offsetEntry.headerOffset;
      this.archive.seek(offset + 26L);
      byte[] b = new byte[2];
      this.archive.readFully(b);
      int fileNameLen = ZipShort.getValue(b);
      this.archive.readFully(b);
      int extraFieldLen = ZipShort.getValue(b);
      int lenToSkip = fileNameLen;
      while (lenToSkip > 0)
      {
        int skipped = this.archive.skipBytes(lenToSkip);
        if (skipped <= 0) {
          throw new RuntimeException("failed to skip file name in local file header");
        }
        lenToSkip -= skipped;
      }
      byte[] localExtraData = new byte[extraFieldLen];
      this.archive.readFully(localExtraData);
      ze.setExtra(localExtraData);
      
      offsetEntry.dataOffset = (offset + 26L + 2L + 2L + fileNameLen + extraFieldLen);
      if (entriesWithoutUTF8Flag.containsKey(ze))
      {
        this.entries.remove(ze);
        setNameAndCommentFromExtraFields(ze, (NameAndComment)entriesWithoutUTF8Flag.get(ze));
        this.entries.put(ze, offsetEntry);
      }
    }
  }
  
  protected static Date fromDosTime(ZipLong zipDosTime)
  {
    long dosTime = zipDosTime.getValue();
    return new Date(dosToJavaTime(dosTime));
  }
  
  private static long dosToJavaTime(long dosTime)
  {
    Calendar cal = Calendar.getInstance();
    
    cal.set(1, (int)(dosTime >> 25 & 0x7F) + 1980);
    cal.set(2, (int)(dosTime >> 21 & 0xF) - 1);
    cal.set(5, (int)(dosTime >> 16) & 0x1F);
    cal.set(11, (int)(dosTime >> 11) & 0x1F);
    cal.set(12, (int)(dosTime >> 5) & 0x3F);
    cal.set(13, (int)(dosTime << 1) & 0x3E);
    
    return cal.getTime().getTime();
  }
  
  protected String getString(byte[] bytes)
    throws ZipException
  {
    try
    {
      return ZipEncodingHelper.getZipEncoding(this.encoding).decode(bytes);
    }
    catch (IOException ex)
    {
      throw new ZipException("Failed to decode name: " + ex.getMessage());
    }
  }
  
  private boolean startsWithLocalFileHeader()
    throws IOException
  {
    this.archive.seek(0L);
    byte[] start = new byte[4];
    this.archive.readFully(start);
    for (int i = 0; i < start.length; i++) {
      if (start[i] != ZipOutputStream.LFH_SIG[i]) {
        return false;
      }
    }
    return true;
  }
  
  private void setNameAndCommentFromExtraFields(ZipEntry ze, NameAndComment nc)
  {
    UnicodePathExtraField name = (UnicodePathExtraField)ze.getExtraField(UnicodePathExtraField.UPATH_ID);
    String originalName = ze.getName();
    String newName = getUnicodeStringIfOriginalMatches(name, nc.name);
    if ((newName != null) && (!originalName.equals(newName)))
    {
      ze.setName(newName);
      this.nameMap.remove(originalName);
      this.nameMap.put(newName, ze);
    }
    if ((nc.comment != null) && (nc.comment.length > 0))
    {
      UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
      String newComment = getUnicodeStringIfOriginalMatches(cmt, nc.comment);
      if (newComment != null) {
        ze.setComment(newComment);
      }
    }
  }
  
  private String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, byte[] orig)
  {
    if (f != null)
    {
      CRC32 crc32 = new CRC32();
      crc32.update(orig);
      long origCRC32 = crc32.getValue();
      if (origCRC32 == f.getNameCRC32()) {
        try
        {
          return ZipEncodingHelper.UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
        }
        catch (IOException ex)
        {
          return null;
        }
      }
    }
    return null;
  }
  
  private class BoundedInputStream
    extends InputStream
  {
    private long remaining;
    private long loc;
    private boolean addDummyByte = false;
    
    BoundedInputStream(long start, long remaining)
    {
      this.remaining = remaining;
      this.loc = start;
    }
    
    public int read()
      throws IOException
    {
      if (this.remaining-- <= 0L)
      {
        if (this.addDummyByte)
        {
          this.addDummyByte = false;
          return 0;
        }
        return -1;
      }
      synchronized (ZipFile.this.archive)
      {
        ZipFile.this.archive.seek(this.loc++);
        return ZipFile.this.archive.read();
      }
    }
    
    public int read(byte[] b, int off, int len)
      throws IOException
    {
      if (this.remaining <= 0L)
      {
        if (this.addDummyByte)
        {
          this.addDummyByte = false;
          b[off] = 0;
          return 1;
        }
        return -1;
      }
      if (len <= 0) {
        return 0;
      }
      if (len > this.remaining) {
        len = (int)this.remaining;
      }
      int ret = -1;
      synchronized (ZipFile.this.archive)
      {
        ZipFile.this.archive.seek(this.loc);
        ret = ZipFile.this.archive.read(b, off, len);
      }
      if (ret > 0)
      {
        this.loc += ret;
        this.remaining -= ret;
      }
      return ret;
    }
    
    void addDummy()
    {
      this.addDummyByte = true;
    }
  }
  
  private static final class NameAndComment
  {
    private final byte[] name;
    private final byte[] comment;
    
    private NameAndComment(byte[] name, byte[] comment)
    {
      this.name = name;
      this.comment = comment;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy