001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.commons.compress.archivers.zip;
020    
021    import java.util.zip.ZipException;
022    
023    /**
024     * Holds size and other extended information for entries that use Zip64
025     * features.
026     *
027     * <p>From {@link http://www.pkware.com/documents/casestudies/APPNOTE.TXT PKWARE's APPNOTE.TXT}
028     * <pre>
029     * Zip64 Extended Information Extra Field (0x0001):
030     *
031     *          The following is the layout of the zip64 extended 
032     *          information "extra" block. If one of the size or
033     *          offset fields in the Local or Central directory
034     *          record is too small to hold the required data,
035     *          a Zip64 extended information record is created.
036     *          The order of the fields in the zip64 extended 
037     *          information record is fixed, but the fields will
038     *          only appear if the corresponding Local or Central
039     *          directory record field is set to 0xFFFF or 0xFFFFFFFF.
040     *
041     *          Note: all fields stored in Intel low-byte/high-byte order.
042     *
043     *          Value      Size       Description
044     *          -----      ----       -----------
045     *  (ZIP64) 0x0001     2 bytes    Tag for this "extra" block type
046     *          Size       2 bytes    Size of this "extra" block
047     *          Original 
048     *          Size       8 bytes    Original uncompressed file size
049     *          Compressed
050     *          Size       8 bytes    Size of compressed data
051     *          Relative Header
052     *          Offset     8 bytes    Offset of local header record
053     *          Disk Start
054     *          Number     4 bytes    Number of the disk on which
055     *                                this file starts 
056     *
057     *          This entry in the Local header must include BOTH original
058     *          and compressed file size fields. If encrypting the 
059     *          central directory and bit 13 of the general purpose bit
060     *          flag is set indicating masking, the value stored in the
061     *          Local Header for the original file size will be zero.
062     * </pre></p>
063     *
064     * <p>Currently Commons Compress doesn't support encrypting the
065     * central directory so the not about masking doesn't apply.</p>
066     *
067     * <p>The implementation relies on data being read from the local file
068     * header and assumes that both size values are always present.</p>
069     *
070     * @since Apache Commons Compress 1.2
071     * @NotThreadSafe
072     */
073    public class Zip64ExtendedInformationExtraField implements ZipExtraField {
074        // TODO: the LFH should probably not contain relativeHeaderOffset
075        // and diskStart but then ZipArchivePOutputStream won't write it to
076        // the CD either - need to test interop with other implementations
077        // to see whether they do have a problem with the extraneous
078        // information inside the LFH
079    
080        private static final ZipShort HEADER_ID = new ZipShort(0x0001);
081    
082        private static final int WORD = 4, DWORD = 8;
083    
084        private ZipEightByteInteger size, compressedSize, relativeHeaderOffset;
085        private ZipLong diskStart;
086    
087        /**
088         * This constructor should only be used by the code that reads
089         * archives inside of Commons Compress.
090         */
091        public Zip64ExtendedInformationExtraField() { }
092    
093        /**
094         * Creates an extra field based on the original and compressed size.
095         *
096         * @param size the entry's original size
097         * @param compressedSize the entry's compressed size
098         *
099         * @throws IllegalArgumentException if size or compressedSize is null
100         */
101        public Zip64ExtendedInformationExtraField(ZipEightByteInteger size,
102                                                  ZipEightByteInteger compressedSize) {
103            this(size, compressedSize, null, null);
104        }
105    
106        /**
107         * Creates an extra field based on all four possible values.
108         *
109         * @param size the entry's original size
110         * @param compressedSize the entry's compressed size
111         *
112         * @throws IllegalArgumentException if size or compressedSize is null
113         */
114        public Zip64ExtendedInformationExtraField(ZipEightByteInteger size,
115                                                  ZipEightByteInteger compressedSize,
116                                                  ZipEightByteInteger relativeHeaderOffset,
117                                                  ZipLong diskStart) {
118            if (size == null) {
119                throw new IllegalArgumentException("size must not be null");
120            }
121            if (compressedSize == null) {
122                throw new IllegalArgumentException("compressedSize must not be null");
123            }
124            this.size = size;
125            this.compressedSize = compressedSize;
126            this.relativeHeaderOffset = relativeHeaderOffset;
127            this.diskStart = diskStart;
128        }
129    
130        /** {@inheritDoc} */
131        public ZipShort getHeaderId() {
132            return HEADER_ID;
133        }
134    
135        /** {@inheritDoc} */
136        public ZipShort getLocalFileDataLength() {
137            return getCentralDirectoryLength();
138        }
139    
140        /** {@inheritDoc} */
141        public ZipShort getCentralDirectoryLength() {
142            return new ZipShort(2 * DWORD  // both size fields
143                                + (relativeHeaderOffset != null ? DWORD : 0)
144                                + (diskStart != null ? WORD : 0));
145        }
146    
147        /** {@inheritDoc} */
148        public byte[] getLocalFileDataData() {
149            return getCentralDirectoryData();
150        }
151    
152        /** {@inheritDoc} */
153        public byte[] getCentralDirectoryData() {
154            byte[] data = new byte[getCentralDirectoryLength().getValue()];
155            addSizes(data);
156            int off = 2 * DWORD;
157            if (relativeHeaderOffset != null) {
158                System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD);
159                off += DWORD;
160            }
161            if (diskStart != null) {
162                System.arraycopy(diskStart.getBytes(), 0, data, off, WORD);
163                off += WORD;
164            }
165            return data;
166        }
167    
168        /** {@inheritDoc} */
169        public void parseFromLocalFileData(byte[] buffer, int offset, int length)
170            throws ZipException {
171            if (length < 2 * DWORD) {
172                throw new ZipException("Zip64 extended information must contain"
173                                       + " both size values in the local file"
174                                       + " header.");
175            }
176            size = new ZipEightByteInteger(buffer, offset);
177            offset += DWORD;
178            compressedSize = new ZipEightByteInteger(buffer, offset);
179            offset += DWORD;
180            int remaining = length - 2 * DWORD;
181            if (remaining >= DWORD) {
182                relativeHeaderOffset = new ZipEightByteInteger(buffer, offset);
183                offset += DWORD;
184                remaining -= DWORD;
185            }
186            if (remaining >= WORD) {
187                diskStart = new ZipLong(buffer, offset);
188                offset += WORD;
189                remaining -= WORD;
190            }
191        }
192    
193        /** {@inheritDoc} */
194        public void parseFromCentralDirectoryData(byte[] buffer, int offset,
195                                                  int length)
196            throws ZipException {
197            // if there is no size information in here, we are screwed and
198            // can only hope things will get resolved by LFH data later
199            // But there are some cases that can be detected
200            // * all data is there
201            // * length % 8 == 4 -> at least we can identify the diskStart field
202            if (length >= 3 * DWORD + WORD) {
203                parseFromLocalFileData(buffer, offset, length);
204            } else if (length % DWORD == WORD) {
205                diskStart = new ZipLong(buffer, offset + length - WORD);
206            }
207        }
208    
209        /**
210         * The uncompressed size stored in this extra field.
211         */
212        public ZipEightByteInteger getSize() {
213            return size;
214        }
215    
216        /**
217         * The compressed size stored in this extra field.
218         */
219        public ZipEightByteInteger getCompressedSize() {
220            return compressedSize;
221        }
222    
223        /**
224         * The relative header offset stored in this extra field.
225         */
226        public ZipEightByteInteger getRelativeHeaderOffset() {
227            return relativeHeaderOffset;
228        }
229    
230        /**
231         * The disk start number stored in this extra field.
232         */
233        public ZipLong getDiskStartNumber() {
234            return diskStart;
235        }
236    
237        private void addSizes(byte[] data) {
238            System.arraycopy(size.getBytes(), 0, data, 0, DWORD);
239            System.arraycopy(compressedSize.getBytes(), 0, data, DWORD, DWORD);
240        }
241    }