Home » openjdk-7 » java » util » zip » [javadoc | source]

    1   /*
    2    * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   
   26   package java.util.zip;
   27   
   28   import java.io.OutputStream;
   29   import java.io.IOException;
   30   import java.nio.charset.Charset;
   31   import java.nio.charset.StandardCharsets;
   32   import java.util.Vector;
   33   import java.util.HashSet;
   34   import static java.util.zip.ZipConstants64.*;
   35   
   36   /**
   37    * This class implements an output stream filter for writing files in the
   38    * ZIP file format. Includes support for both compressed and uncompressed
   39    * entries.
   40    *
   41    * @author      David Connelly
   42    */
   43   public
   44   class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
   45   
   46       private static class XEntry {
   47           public final ZipEntry entry;
   48           public final long offset;
   49           public XEntry(ZipEntry entry, long offset) {
   50               this.entry = entry;
   51               this.offset = offset;
   52           }
   53       }
   54   
   55       private XEntry current;
   56       private Vector<XEntry> xentries = new Vector<>();
   57       private HashSet<String> names = new HashSet<>();
   58       private CRC32 crc = new CRC32();
   59       private long written = 0;
   60       private long locoff = 0;
   61       private byte[] comment;
   62       private int method = DEFLATED;
   63       private boolean finished;
   64   
   65       private boolean closed = false;
   66   
   67       private final ZipCoder zc;
   68   
   69       private static int version(ZipEntry e) throws ZipException {
   70           switch (e.method) {
   71           case DEFLATED: return 20;
   72           case STORED:   return 10;
   73           default: throw new ZipException("unsupported compression method");
   74           }
   75       }
   76   
   77       /**
   78        * Checks to make sure that this stream has not been closed.
   79        */
   80       private void ensureOpen() throws IOException {
   81           if (closed) {
   82               throw new IOException("Stream closed");
   83           }
   84       }
   85       /**
   86        * Compression method for uncompressed (STORED) entries.
   87        */
   88       public static final int STORED = ZipEntry.STORED;
   89   
   90       /**
   91        * Compression method for compressed (DEFLATED) entries.
   92        */
   93       public static final int DEFLATED = ZipEntry.DEFLATED;
   94   
   95       /**
   96        * Creates a new ZIP output stream.
   97        *
   98        * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
   99        * to encode the entry names and comments.
  100        *
  101        * @param out the actual output stream
  102        */
  103       public ZipOutputStream(OutputStream out) {
  104           this(out, StandardCharsets.UTF_8);
  105       }
  106   
  107       /**
  108        * Creates a new ZIP output stream.
  109        *
  110        * @param out the actual output stream
  111        *
  112        * @param charset the {@linkplain java.nio.charset.Charset charset}
  113        *                to be used to encode the entry names and comments
  114        *
  115        * @since 1.7
  116        */
  117       public ZipOutputStream(OutputStream out, Charset charset) {
  118           super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
  119           if (charset == null)
  120               throw new NullPointerException("charset is null");
  121           this.zc = ZipCoder.get(charset);
  122           usesDefaultDeflater = true;
  123       }
  124   
  125       /**
  126        * Sets the ZIP file comment.
  127        * @param comment the comment string
  128        * @exception IllegalArgumentException if the length of the specified
  129        *            ZIP file comment is greater than 0xFFFF bytes
  130        */
  131       public void setComment(String comment) {
  132           if (comment != null) {
  133               this.comment = zc.getBytes(comment);
  134               if (this.comment.length > 0xffff)
  135                   throw new IllegalArgumentException("ZIP file comment too long.");
  136           }
  137       }
  138   
  139       /**
  140        * Sets the default compression method for subsequent entries. This
  141        * default will be used whenever the compression method is not specified
  142        * for an individual ZIP file entry, and is initially set to DEFLATED.
  143        * @param method the default compression method
  144        * @exception IllegalArgumentException if the specified compression method
  145        *            is invalid
  146        */
  147       public void setMethod(int method) {
  148           if (method != DEFLATED && method != STORED) {
  149               throw new IllegalArgumentException("invalid compression method");
  150           }
  151           this.method = method;
  152       }
  153   
  154       /**
  155        * Sets the compression level for subsequent entries which are DEFLATED.
  156        * The default setting is DEFAULT_COMPRESSION.
  157        * @param level the compression level (0-9)
  158        * @exception IllegalArgumentException if the compression level is invalid
  159        */
  160       public void setLevel(int level) {
  161           def.setLevel(level);
  162       }
  163   
  164       /**
  165        * Begins writing a new ZIP file entry and positions the stream to the
  166        * start of the entry data. Closes the current entry if still active.
  167        * The default compression method will be used if no compression method
  168        * was specified for the entry, and the current time will be used if
  169        * the entry has no set modification time.
  170        * @param e the ZIP entry to be written
  171        * @exception ZipException if a ZIP format error has occurred
  172        * @exception IOException if an I/O error has occurred
  173        */
  174       public void putNextEntry(ZipEntry e) throws IOException {
  175           ensureOpen();
  176           if (current != null) {
  177               closeEntry();       // close previous entry
  178           }
  179           if (e.time == -1) {
  180               e.setTime(System.currentTimeMillis());
  181           }
  182           if (e.method == -1) {
  183               e.method = method;  // use default method
  184           }
  185           // store size, compressed size, and crc-32 in LOC header
  186           e.flag = 0;
  187           switch (e.method) {
  188           case DEFLATED:
  189               // store size, compressed size, and crc-32 in data descriptor
  190               // immediately following the compressed entry data
  191               if (e.size  == -1 || e.csize == -1 || e.crc   == -1)
  192                   e.flag = 8;
  193   
  194               break;
  195           case STORED:
  196               // compressed size, uncompressed size, and crc-32 must all be
  197               // set for entries using STORED compression method
  198               if (e.size == -1) {
  199                   e.size = e.csize;
  200               } else if (e.csize == -1) {
  201                   e.csize = e.size;
  202               } else if (e.size != e.csize) {
  203                   throw new ZipException(
  204                       "STORED entry where compressed != uncompressed size");
  205               }
  206               if (e.size == -1 || e.crc == -1) {
  207                   throw new ZipException(
  208                       "STORED entry missing size, compressed size, or crc-32");
  209               }
  210               break;
  211           default:
  212               throw new ZipException("unsupported compression method");
  213           }
  214           if (! names.add(e.name)) {
  215               throw new ZipException("duplicate entry: " + e.name);
  216           }
  217           if (zc.isUTF8())
  218               e.flag |= EFS;
  219           current = new XEntry(e, written);
  220           xentries.add(current);
  221           writeLOC(current);
  222       }
  223   
  224       /**
  225        * Closes the current ZIP entry and positions the stream for writing
  226        * the next entry.
  227        * @exception ZipException if a ZIP format error has occurred
  228        * @exception IOException if an I/O error has occurred
  229        */
  230       public void closeEntry() throws IOException {
  231           ensureOpen();
  232           if (current != null) {
  233               ZipEntry e = current.entry;
  234               switch (e.method) {
  235               case DEFLATED:
  236                   def.finish();
  237                   while (!def.finished()) {
  238                       deflate();
  239                   }
  240                   if ((e.flag & 8) == 0) {
  241                       // verify size, compressed size, and crc-32 settings
  242                       if (e.size != def.getBytesRead()) {
  243                           throw new ZipException(
  244                               "invalid entry size (expected " + e.size +
  245                               " but got " + def.getBytesRead() + " bytes)");
  246                       }
  247                       if (e.csize != def.getBytesWritten()) {
  248                           throw new ZipException(
  249                               "invalid entry compressed size (expected " +
  250                               e.csize + " but got " + def.getBytesWritten() + " bytes)");
  251                       }
  252                       if (e.crc != crc.getValue()) {
  253                           throw new ZipException(
  254                               "invalid entry CRC-32 (expected 0x" +
  255                               Long.toHexString(e.crc) + " but got 0x" +
  256                               Long.toHexString(crc.getValue()) + ")");
  257                       }
  258                   } else {
  259                       e.size  = def.getBytesRead();
  260                       e.csize = def.getBytesWritten();
  261                       e.crc = crc.getValue();
  262                       writeEXT(e);
  263                   }
  264                   def.reset();
  265                   written += e.csize;
  266                   break;
  267               case STORED:
  268                   // we already know that both e.size and e.csize are the same
  269                   if (e.size != written - locoff) {
  270                       throw new ZipException(
  271                           "invalid entry size (expected " + e.size +
  272                           " but got " + (written - locoff) + " bytes)");
  273                   }
  274                   if (e.crc != crc.getValue()) {
  275                       throw new ZipException(
  276                            "invalid entry crc-32 (expected 0x" +
  277                            Long.toHexString(e.crc) + " but got 0x" +
  278                            Long.toHexString(crc.getValue()) + ")");
  279                   }
  280                   break;
  281               default:
  282                   throw new ZipException("invalid compression method");
  283               }
  284               crc.reset();
  285               current = null;
  286           }
  287       }
  288   
  289       /**
  290        * Writes an array of bytes to the current ZIP entry data. This method
  291        * will block until all the bytes are written.
  292        * @param b the data to be written
  293        * @param off the start offset in the data
  294        * @param len the number of bytes that are written
  295        * @exception ZipException if a ZIP file error has occurred
  296        * @exception IOException if an I/O error has occurred
  297        */
  298       public synchronized void write(byte[] b, int off, int len)
  299           throws IOException
  300       {
  301           ensureOpen();
  302           if (off < 0 || len < 0 || off > b.length - len) {
  303               throw new IndexOutOfBoundsException();
  304           } else if (len == 0) {
  305               return;
  306           }
  307   
  308           if (current == null) {
  309               throw new ZipException("no current ZIP entry");
  310           }
  311           ZipEntry entry = current.entry;
  312           switch (entry.method) {
  313           case DEFLATED:
  314               super.write(b, off, len);
  315               break;
  316           case STORED:
  317               written += len;
  318               if (written - locoff > entry.size) {
  319                   throw new ZipException(
  320                       "attempt to write past end of STORED entry");
  321               }
  322               out.write(b, off, len);
  323               break;
  324           default:
  325               throw new ZipException("invalid compression method");
  326           }
  327           crc.update(b, off, len);
  328       }
  329   
  330       /**
  331        * Finishes writing the contents of the ZIP output stream without closing
  332        * the underlying stream. Use this method when applying multiple filters
  333        * in succession to the same output stream.
  334        * @exception ZipException if a ZIP file error has occurred
  335        * @exception IOException if an I/O exception has occurred
  336        */
  337       public void finish() throws IOException {
  338           ensureOpen();
  339           if (finished) {
  340               return;
  341           }
  342           if (current != null) {
  343               closeEntry();
  344           }
  345           // write central directory
  346           long off = written;
  347           for (XEntry xentry : xentries)
  348               writeCEN(xentry);
  349           writeEND(off, written - off);
  350           finished = true;
  351       }
  352   
  353       /**
  354        * Closes the ZIP output stream as well as the stream being filtered.
  355        * @exception ZipException if a ZIP file error has occurred
  356        * @exception IOException if an I/O error has occurred
  357        */
  358       public void close() throws IOException {
  359           if (!closed) {
  360               super.close();
  361               closed = true;
  362           }
  363       }
  364   
  365       /*
  366        * Writes local file (LOC) header for specified entry.
  367        */
  368       private void writeLOC(XEntry xentry) throws IOException {
  369           ZipEntry e = xentry.entry;
  370           int flag = e.flag;
  371           int elen = (e.extra != null) ? e.extra.length : 0;
  372           boolean hasZip64 = false;
  373   
  374           writeInt(LOCSIG);               // LOC header signature
  375   
  376           if ((flag & 8) == 8) {
  377               writeShort(version(e));     // version needed to extract
  378               writeShort(flag);           // general purpose bit flag
  379               writeShort(e.method);       // compression method
  380               writeInt(e.time);           // last modification time
  381   
  382               // store size, uncompressed size, and crc-32 in data descriptor
  383               // immediately following compressed entry data
  384               writeInt(0);
  385               writeInt(0);
  386               writeInt(0);
  387           } else {
  388               if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
  389                   hasZip64 = true;
  390                   writeShort(45);         // ver 4.5 for zip64
  391               } else {
  392                   writeShort(version(e)); // version needed to extract
  393               }
  394               writeShort(flag);           // general purpose bit flag
  395               writeShort(e.method);       // compression method
  396               writeInt(e.time);           // last modification time
  397               writeInt(e.crc);            // crc-32
  398               if (hasZip64) {
  399                   writeInt(ZIP64_MAGICVAL);
  400                   writeInt(ZIP64_MAGICVAL);
  401                   elen += 20;        //headid(2) + size(2) + size(8) + csize(8)
  402               } else {
  403                   writeInt(e.csize);  // compressed size
  404                   writeInt(e.size);   // uncompressed size
  405               }
  406           }
  407           byte[] nameBytes = zc.getBytes(e.name);
  408           writeShort(nameBytes.length);
  409           writeShort(elen);
  410           writeBytes(nameBytes, 0, nameBytes.length);
  411           if (hasZip64) {
  412               writeShort(ZIP64_EXTID);
  413               writeShort(16);
  414               writeLong(e.size);
  415               writeLong(e.csize);
  416           }
  417           if (e.extra != null) {
  418               writeBytes(e.extra, 0, e.extra.length);
  419           }
  420           locoff = written;
  421       }
  422   
  423       /*
  424        * Writes extra data descriptor (EXT) for specified entry.
  425        */
  426       private void writeEXT(ZipEntry e) throws IOException {
  427           writeInt(EXTSIG);           // EXT header signature
  428           writeInt(e.crc);            // crc-32
  429           if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
  430               writeLong(e.csize);
  431               writeLong(e.size);
  432           } else {
  433               writeInt(e.csize);          // compressed size
  434               writeInt(e.size);           // uncompressed size
  435           }
  436       }
  437   
  438       /*
  439        * Write central directory (CEN) header for specified entry.
  440        * REMIND: add support for file attributes
  441        */
  442       private void writeCEN(XEntry xentry) throws IOException {
  443           ZipEntry e  = xentry.entry;
  444           int flag = e.flag;
  445           int version = version(e);
  446   
  447           long csize = e.csize;
  448           long size = e.size;
  449           long offset = xentry.offset;
  450           int e64len = 0;
  451           boolean hasZip64 = false;
  452           if (e.csize >= ZIP64_MAGICVAL) {
  453               csize = ZIP64_MAGICVAL;
  454               e64len += 8;              // csize(8)
  455               hasZip64 = true;
  456           }
  457           if (e.size >= ZIP64_MAGICVAL) {
  458               size = ZIP64_MAGICVAL;    // size(8)
  459               e64len += 8;
  460               hasZip64 = true;
  461           }
  462           if (xentry.offset >= ZIP64_MAGICVAL) {
  463               offset = ZIP64_MAGICVAL;
  464               e64len += 8;              // offset(8)
  465               hasZip64 = true;
  466           }
  467           writeInt(CENSIG);           // CEN header signature
  468           if (hasZip64) {
  469               writeShort(45);         // ver 4.5 for zip64
  470               writeShort(45);
  471           } else {
  472               writeShort(version);    // version made by
  473               writeShort(version);    // version needed to extract
  474           }
  475           writeShort(flag);           // general purpose bit flag
  476           writeShort(e.method);       // compression method
  477           writeInt(e.time);           // last modification time
  478           writeInt(e.crc);            // crc-32
  479           writeInt(csize);            // compressed size
  480           writeInt(size);             // uncompressed size
  481           byte[] nameBytes = zc.getBytes(e.name);
  482           writeShort(nameBytes.length);
  483           if (hasZip64) {
  484               // + headid(2) + datasize(2)
  485               writeShort(e64len + 4 + (e.extra != null ? e.extra.length : 0));
  486           } else {
  487               writeShort(e.extra != null ? e.extra.length : 0);
  488           }
  489           byte[] commentBytes;
  490           if (e.comment != null) {
  491               commentBytes = zc.getBytes(e.comment);
  492               writeShort(Math.min(commentBytes.length, 0xffff));
  493           } else {
  494               commentBytes = null;
  495               writeShort(0);
  496           }
  497           writeShort(0);              // starting disk number
  498           writeShort(0);              // internal file attributes (unused)
  499           writeInt(0);                // external file attributes (unused)
  500           writeInt(offset);           // relative offset of local header
  501           writeBytes(nameBytes, 0, nameBytes.length);
  502           if (hasZip64) {
  503               writeShort(ZIP64_EXTID);// Zip64 extra
  504               writeShort(e64len);
  505               if (size == ZIP64_MAGICVAL)
  506                   writeLong(e.size);
  507               if (csize == ZIP64_MAGICVAL)
  508                   writeLong(e.csize);
  509               if (offset == ZIP64_MAGICVAL)
  510                   writeLong(xentry.offset);
  511           }
  512           if (e.extra != null) {
  513               writeBytes(e.extra, 0, e.extra.length);
  514           }
  515           if (commentBytes != null) {
  516               writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
  517           }
  518       }
  519   
  520       /*
  521        * Writes end of central directory (END) header.
  522        */
  523       private void writeEND(long off, long len) throws IOException {
  524           boolean hasZip64 = false;
  525           long xlen = len;
  526           long xoff = off;
  527           if (xlen >= ZIP64_MAGICVAL) {
  528               xlen = ZIP64_MAGICVAL;
  529               hasZip64 = true;
  530           }
  531           if (xoff >= ZIP64_MAGICVAL) {
  532               xoff = ZIP64_MAGICVAL;
  533               hasZip64 = true;
  534           }
  535           int count = xentries.size();
  536           if (count >= ZIP64_MAGICCOUNT) {
  537               count = ZIP64_MAGICCOUNT;
  538               hasZip64 = true;
  539           }
  540           if (hasZip64) {
  541               long off64 = written;
  542               //zip64 end of central directory record
  543               writeInt(ZIP64_ENDSIG);        // zip64 END record signature
  544               writeLong(ZIP64_ENDHDR - 12);  // size of zip64 end
  545               writeShort(45);                // version made by
  546               writeShort(45);                // version needed to extract
  547               writeInt(0);                   // number of this disk
  548               writeInt(0);                   // central directory start disk
  549               writeLong(xentries.size());    // number of directory entires on disk
  550               writeLong(xentries.size());    // number of directory entires
  551               writeLong(len);                // length of central directory
  552               writeLong(off);                // offset of central directory
  553   
  554               //zip64 end of central directory locator
  555               writeInt(ZIP64_LOCSIG);        // zip64 END locator signature
  556               writeInt(0);                   // zip64 END start disk
  557               writeLong(off64);              // offset of zip64 END
  558               writeInt(1);                   // total number of disks (?)
  559           }
  560           writeInt(ENDSIG);                 // END record signature
  561           writeShort(0);                    // number of this disk
  562           writeShort(0);                    // central directory start disk
  563           writeShort(count);                // number of directory entries on disk
  564           writeShort(count);                // total number of directory entries
  565           writeInt(xlen);                   // length of central directory
  566           writeInt(xoff);                   // offset of central directory
  567           if (comment != null) {            // zip file comment
  568               writeShort(comment.length);
  569               writeBytes(comment, 0, comment.length);
  570           } else {
  571               writeShort(0);
  572           }
  573       }
  574   
  575       /*
  576        * Writes a 16-bit short to the output stream in little-endian byte order.
  577        */
  578       private void writeShort(int v) throws IOException {
  579           OutputStream out = this.out;
  580           out.write((v >>> 0) & 0xff);
  581           out.write((v >>> 8) & 0xff);
  582           written += 2;
  583       }
  584   
  585       /*
  586        * Writes a 32-bit int to the output stream in little-endian byte order.
  587        */
  588       private void writeInt(long v) throws IOException {
  589           OutputStream out = this.out;
  590           out.write((int)((v >>>  0) & 0xff));
  591           out.write((int)((v >>>  8) & 0xff));
  592           out.write((int)((v >>> 16) & 0xff));
  593           out.write((int)((v >>> 24) & 0xff));
  594           written += 4;
  595       }
  596   
  597       /*
  598        * Writes a 64-bit int to the output stream in little-endian byte order.
  599        */
  600       private void writeLong(long v) throws IOException {
  601           OutputStream out = this.out;
  602           out.write((int)((v >>>  0) & 0xff));
  603           out.write((int)((v >>>  8) & 0xff));
  604           out.write((int)((v >>> 16) & 0xff));
  605           out.write((int)((v >>> 24) & 0xff));
  606           out.write((int)((v >>> 32) & 0xff));
  607           out.write((int)((v >>> 40) & 0xff));
  608           out.write((int)((v >>> 48) & 0xff));
  609           out.write((int)((v >>> 56) & 0xff));
  610           written += 8;
  611       }
  612   
  613       /*
  614        * Writes an array of bytes to the output stream.
  615        */
  616       private void writeBytes(byte[] b, int off, int len) throws IOException {
  617           super.out.write(b, off, len);
  618           written += len;
  619       }
  620   }

Home » openjdk-7 » java » util » zip » [javadoc | source]