~ubuntu-branches/ubuntu/utopic/libcommons-compress-java/utopic

« back to all changes in this revision

Viewing changes to src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java

  • Committer: Package Import Robot
  • Author(s): Emmanuel Bourg
  • Date: 2013-11-02 13:55:47 UTC
  • mfrom: (1.1.5)
  • Revision ID: package-import@ubuntu.com-20131102135547-nhcubj3ae7rv6t13
Tags: 1.6-1
* New upstream release
* Updated Standards-Version to 3.9.5 (no changes)
* Build depend on debhelper >= 9
* Removed the unused debian/orig-tar.sh script

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 
3
 *  contributor license agreements.  See the NOTICE file distributed with
 
4
 *  this work for additional information regarding copyright ownership.
 
5
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 
6
 *  (the "License"); you may not use this file except in compliance with
 
7
 *  the License.  You may obtain a copy of the License at
 
8
 *
 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
 
10
 *
 
11
 *  Unless required by applicable law or agreed to in writing, software
 
12
 *  distributed under the License is distributed on an "AS IS" BASIS,
 
13
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
14
 *  See the License for the specific language governing permissions and
 
15
 *  limitations under the License.
 
16
 *
 
17
 */
 
18
package org.apache.commons.compress.archivers.sevenz;
 
19
 
 
20
import java.io.ByteArrayInputStream;
 
21
import java.io.DataInput;
 
22
import java.io.DataInputStream;
 
23
import java.io.File;
 
24
import java.io.IOException;
 
25
import java.io.InputStream;
 
26
import java.io.RandomAccessFile;
 
27
import java.util.Arrays;
 
28
import java.util.BitSet;
 
29
import java.util.zip.CRC32;
 
30
 
 
31
import org.apache.commons.compress.utils.BoundedInputStream;
 
32
import org.apache.commons.compress.utils.CRC32VerifyingInputStream;
 
33
import org.apache.commons.compress.utils.CharsetNames;
 
34
 
 
35
/**
 
36
 * Reads a 7z file, using RandomAccessFile under
 
37
 * the covers.
 
38
 * <p>
 
39
 * The 7z file format is a flexible container
 
40
 * that can contain many compression and
 
41
 * encryption types, but at the moment only
 
42
 * only Copy, LZMA, LZMA2, BZIP2, Deflate and AES-256 + SHA-256
 
43
 * are supported.
 
44
 * <p>
 
45
 * The format is very Windows/Intel specific,
 
46
 * so it uses little-endian byte order,
 
47
 * doesn't store user/group or permission bits,
 
48
 * and represents times using NTFS timestamps
 
49
 * (100 nanosecond units since 1 January 1601).
 
50
 * Hence the official tools recommend against
 
51
 * using it for backup purposes on *nix, and
 
52
 * recommend .tar.7z or .tar.lzma or .tar.xz
 
53
 * instead.  
 
54
 * <p>
 
55
 * Both the header and file contents may be
 
56
 * compressed and/or encrypted. With both
 
57
 * encrypted, neither file names nor file
 
58
 * contents can be read, but the use of
 
59
 * encryption isn't plausibly deniable.
 
60
 * 
 
61
 * @NotThreadSafe
 
62
 * @since 1.6
 
63
 */
 
64
public class SevenZFile {
 
65
    static final int SIGNATURE_HEADER_SIZE = 32;
 
66
 
 
67
    private static final int DRAIN_BUF_SIZE = 64 * 1024;
 
68
 
 
69
    private RandomAccessFile file;
 
70
    private final Archive archive;
 
71
    private int currentEntryIndex = -1;
 
72
    private int currentFolderIndex = -1;
 
73
    private InputStream currentFolderInputStream = null;
 
74
    private InputStream currentEntryInputStream = null;
 
75
    private byte[] password;
 
76
        
 
77
    static final byte[] sevenZSignature = {
 
78
        (byte)'7', (byte)'z', (byte)0xBC, (byte)0xAF, (byte)0x27, (byte)0x1C
 
79
    };
 
80
    
 
81
    /**
 
82
     * Reads a file as 7z archive
 
83
     *
 
84
     * @param filename the file to read
 
85
     * @param password optional password if the archive is encrypted -
 
86
     * the byte array is supposed to be the UTF16-LE encoded
 
87
     * representation of the password.
 
88
     * @throws IOException if reading the archive fails
 
89
     */
 
90
    public SevenZFile(final File filename, final byte[] password) throws IOException {
 
91
        boolean succeeded = false;
 
92
        this.file = new RandomAccessFile(filename, "r");
 
93
        try {
 
94
            archive = readHeaders(password);
 
95
            if (password != null) {
 
96
                this.password = new byte[password.length];
 
97
                System.arraycopy(password, 0, this.password, 0, password.length);
 
98
            } else {
 
99
                this.password = null;
 
100
            }
 
101
            succeeded = true;
 
102
        } finally {
 
103
            if (!succeeded) {
 
104
                this.file.close();
 
105
            }
 
106
        }
 
107
    }
 
108
    
 
109
    /**
 
110
     * Reads a file as unecrypted 7z archive
 
111
     *
 
112
     * @param filename the file to read
 
113
     * @throws IOException if reading the archive fails
 
114
     */
 
115
    public SevenZFile(final File filename) throws IOException {
 
116
        this(filename, null);
 
117
    }
 
118
 
 
119
    /**
 
120
     * Closes the archive.
 
121
     * @throws IOException if closing the file fails
 
122
     */
 
123
    public void close() throws IOException {
 
124
        if (file != null) {
 
125
            try {
 
126
                file.close();
 
127
            } finally {
 
128
                file = null;
 
129
                if (password != null) {
 
130
                    Arrays.fill(password, (byte) 0);
 
131
                }
 
132
                password = null;
 
133
            }
 
134
        }
 
135
    }
 
136
    
 
137
    /**
 
138
     * Returns the next Archive Entry in this archive.
 
139
     *
 
140
     * @return the next entry,
 
141
     *         or {@code null} if there are no more entries
 
142
     * @throws IOException if the next entry could not be read
 
143
     */
 
144
    public SevenZArchiveEntry getNextEntry() throws IOException {
 
145
        if (currentEntryIndex >= (archive.files.length - 1)) {
 
146
            return null;
 
147
        }
 
148
        ++currentEntryIndex;
 
149
        final SevenZArchiveEntry entry = archive.files[currentEntryIndex];
 
150
        buildDecodingStream();
 
151
        return entry;
 
152
    }
 
153
    
 
154
    private Archive readHeaders(byte[] password) throws IOException {
 
155
        final byte[] signature = new byte[6];
 
156
        file.readFully(signature);
 
157
        if (!Arrays.equals(signature, sevenZSignature)) {
 
158
            throw new IOException("Bad 7z signature");
 
159
        }
 
160
        // 7zFormat.txt has it wrong - it's first major then minor
 
161
        final byte archiveVersionMajor = file.readByte();
 
162
        final byte archiveVersionMinor = file.readByte();
 
163
        if (archiveVersionMajor != 0) {
 
164
            throw new IOException(String.format("Unsupported 7z version (%d,%d)",
 
165
                    archiveVersionMajor, archiveVersionMinor));
 
166
        }
 
167
 
 
168
        final int startHeaderCrc = Integer.reverseBytes(file.readInt());
 
169
        final StartHeader startHeader = readStartHeader(startHeaderCrc);
 
170
        
 
171
        final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
 
172
        if (nextHeaderSizeInt != startHeader.nextHeaderSize) {
 
173
            throw new IOException("cannot handle nextHeaderSize " + startHeader.nextHeaderSize);
 
174
        }
 
175
        file.seek(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
 
176
        final byte[] nextHeader = new byte[nextHeaderSizeInt];
 
177
        file.readFully(nextHeader);
 
178
        final CRC32 crc = new CRC32();
 
179
        crc.update(nextHeader);
 
180
        if (startHeader.nextHeaderCrc != (int) crc.getValue()) {
 
181
            throw new IOException("NextHeader CRC mismatch");
 
182
        }
 
183
        
 
184
        final ByteArrayInputStream byteStream = new ByteArrayInputStream(nextHeader);
 
185
        DataInputStream nextHeaderInputStream = new DataInputStream(
 
186
                byteStream);
 
187
        Archive archive = new Archive();
 
188
        int nid = nextHeaderInputStream.readUnsignedByte();
 
189
        if (nid == NID.kEncodedHeader) {
 
190
            nextHeaderInputStream =
 
191
                readEncodedHeader(nextHeaderInputStream, archive, password);
 
192
            // Archive gets rebuilt with the new header
 
193
            archive = new Archive();
 
194
            nid = nextHeaderInputStream.readUnsignedByte();
 
195
        }
 
196
        if (nid == NID.kHeader) {
 
197
            readHeader(nextHeaderInputStream, archive);
 
198
            nextHeaderInputStream.close();
 
199
        } else {
 
200
            throw new IOException("Broken or unsupported archive: no Header");
 
201
        }
 
202
        return archive;
 
203
    }
 
204
    
 
205
    private StartHeader readStartHeader(final int startHeaderCrc) throws IOException {
 
206
        final StartHeader startHeader = new StartHeader();
 
207
        DataInputStream dataInputStream = null;
 
208
        try {
 
209
             dataInputStream = new DataInputStream(new CRC32VerifyingInputStream(
 
210
                    new BoundedRandomAccessFileInputStream(file, 20), 20, startHeaderCrc));
 
211
             startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
 
212
             startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
 
213
             startHeader.nextHeaderCrc = Integer.reverseBytes(dataInputStream.readInt());
 
214
             return startHeader;
 
215
        } finally {
 
216
            if (dataInputStream != null) {
 
217
                dataInputStream.close();
 
218
            }
 
219
        }
 
220
    }
 
221
    
 
222
    private void readHeader(final DataInput header, final Archive archive) throws IOException {
 
223
        int nid = header.readUnsignedByte();
 
224
        
 
225
        if (nid == NID.kArchiveProperties) {
 
226
            readArchiveProperties(header);
 
227
            nid = header.readUnsignedByte();
 
228
        }
 
229
        
 
230
        if (nid == NID.kAdditionalStreamsInfo) {
 
231
            throw new IOException("Additional streams unsupported");
 
232
            //nid = header.readUnsignedByte();
 
233
        }
 
234
        
 
235
        if (nid == NID.kMainStreamsInfo) {
 
236
            readStreamsInfo(header, archive);
 
237
            nid = header.readUnsignedByte();
 
238
        }
 
239
        
 
240
        if (nid == NID.kFilesInfo) {
 
241
            readFilesInfo(header, archive);
 
242
            nid = header.readUnsignedByte();
 
243
        }
 
244
        
 
245
        if (nid != NID.kEnd) {
 
246
            throw new IOException("Badly terminated header");
 
247
        }
 
248
    }
 
249
    
 
250
    private void readArchiveProperties(final DataInput input) throws IOException {
 
251
        // FIXME: the reference implementation just throws them away?
 
252
        int nid =  input.readUnsignedByte();
 
253
        while (nid != NID.kEnd) {
 
254
            final long propertySize = readUint64(input);
 
255
            final byte[] property = new byte[(int)propertySize];
 
256
            input.readFully(property);
 
257
            nid = input.readUnsignedByte();
 
258
        }
 
259
    }
 
260
    
 
261
    private DataInputStream readEncodedHeader(final DataInputStream header, final Archive archive,
 
262
                                              byte[] password) throws IOException {
 
263
        readStreamsInfo(header, archive);
 
264
        
 
265
        // FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage?
 
266
        final Folder folder = archive.folders[0];
 
267
        final int firstPackStreamIndex = 0;
 
268
        final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos +
 
269
                0;
 
270
        
 
271
        file.seek(folderOffset);
 
272
        InputStream inputStreamStack = new BoundedRandomAccessFileInputStream(file,
 
273
                archive.packSizes[firstPackStreamIndex]);
 
274
        for (final Coder coder : folder.coders) {
 
275
            if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
 
276
                throw new IOException("Multi input/output stream coders are not yet supported");
 
277
            }
 
278
            inputStreamStack = Coders.addDecoder(inputStreamStack, coder, password);
 
279
        }
 
280
        if (folder.hasCrc) {
 
281
            inputStreamStack = new CRC32VerifyingInputStream(inputStreamStack,
 
282
                    folder.getUnpackSize(), folder.crc);
 
283
        }
 
284
        final byte[] nextHeader = new byte[(int)folder.getUnpackSize()];
 
285
        final DataInputStream nextHeaderInputStream = new DataInputStream(inputStreamStack);
 
286
        try {
 
287
            nextHeaderInputStream.readFully(nextHeader);
 
288
        } finally {
 
289
            nextHeaderInputStream.close();
 
290
        }
 
291
        return new DataInputStream(new ByteArrayInputStream(nextHeader));
 
292
    }
 
293
    
 
294
    private void readStreamsInfo(final DataInput header, final Archive archive) throws IOException {
 
295
        int nid = header.readUnsignedByte();
 
296
        
 
297
        if (nid == NID.kPackInfo) {
 
298
            readPackInfo(header, archive);
 
299
            nid = header.readUnsignedByte();
 
300
        }
 
301
        
 
302
        if (nid == NID.kUnpackInfo) {
 
303
            readUnpackInfo(header, archive);
 
304
            nid = header.readUnsignedByte();
 
305
        } else {
 
306
            // archive without unpack/coders info
 
307
            archive.folders = new Folder[0];
 
308
        }
 
309
        
 
310
        if (nid == NID.kSubStreamsInfo) {
 
311
            readSubStreamsInfo(header, archive);
 
312
            nid = header.readUnsignedByte();
 
313
        }
 
314
        
 
315
        if (nid != NID.kEnd) {
 
316
            throw new IOException("Badly terminated StreamsInfo");
 
317
        }
 
318
    }
 
319
    
 
320
    private void readPackInfo(final DataInput header, final Archive archive) throws IOException {
 
321
        archive.packPos = readUint64(header);
 
322
        final long numPackStreams = readUint64(header);
 
323
        int nid = header.readUnsignedByte();
 
324
        if (nid == NID.kSize) {
 
325
            archive.packSizes = new long[(int)numPackStreams];
 
326
            for (int i = 0; i < archive.packSizes.length; i++) {
 
327
                archive.packSizes[i] = readUint64(header);
 
328
            }
 
329
            nid = header.readUnsignedByte();
 
330
        }
 
331
        
 
332
        if (nid == NID.kCRC) {
 
333
            archive.packCrcsDefined = readAllOrBits(header, (int)numPackStreams);
 
334
            archive.packCrcs = new int[(int)numPackStreams];
 
335
            for (int i = 0; i < (int)numPackStreams; i++) {
 
336
                if (archive.packCrcsDefined.get(i)) {
 
337
                    archive.packCrcs[i] = Integer.reverseBytes(header.readInt());
 
338
                }
 
339
            }
 
340
            
 
341
            nid = header.readUnsignedByte();
 
342
        }
 
343
        
 
344
        if (nid != NID.kEnd) {
 
345
            throw new IOException("Badly terminated PackInfo (" + nid + ")");
 
346
        }
 
347
    }
 
348
    
 
349
    private void readUnpackInfo(final DataInput header, final Archive archive) throws IOException {
 
350
        int nid = header.readUnsignedByte();
 
351
        if (nid != NID.kFolder) {
 
352
            throw new IOException("Expected kFolder, got " + nid);
 
353
        }
 
354
        final long numFolders = readUint64(header);
 
355
        final Folder[] folders = new Folder[(int)numFolders];
 
356
        archive.folders = folders;
 
357
        final int external = header.readUnsignedByte();
 
358
        if (external != 0) {
 
359
            throw new IOException("External unsupported");
 
360
        } else {
 
361
            for (int i = 0; i < (int)numFolders; i++) {
 
362
                folders[i] = readFolder(header);
 
363
            }
 
364
        }
 
365
        
 
366
        nid = header.readUnsignedByte();
 
367
        if (nid != NID.kCodersUnpackSize) {
 
368
            throw new IOException("Expected kCodersUnpackSize, got " + nid);
 
369
        }
 
370
        for (final Folder folder : folders) {
 
371
            folder.unpackSizes = new long[(int)folder.totalOutputStreams];
 
372
            for (int i = 0; i < folder.totalOutputStreams; i++) {
 
373
                folder.unpackSizes[i] = readUint64(header);
 
374
            }
 
375
        }
 
376
        
 
377
        nid = header.readUnsignedByte();
 
378
        if (nid == NID.kCRC) {
 
379
            final BitSet crcsDefined = readAllOrBits(header, (int)numFolders);
 
380
            for (int i = 0; i < (int)numFolders; i++) {
 
381
                if (crcsDefined.get(i)) {
 
382
                    folders[i].hasCrc = true;
 
383
                    folders[i].crc = Integer.reverseBytes(header.readInt());
 
384
                } else {
 
385
                    folders[i].hasCrc = false;
 
386
                }
 
387
            }
 
388
            
 
389
            nid = header.readUnsignedByte();
 
390
        }
 
391
        
 
392
        if (nid != NID.kEnd) {
 
393
            throw new IOException("Badly terminated UnpackInfo");
 
394
        }
 
395
    }
 
396
    
 
397
    private void readSubStreamsInfo(final DataInput header, final Archive archive) throws IOException {
 
398
        for (final Folder folder : archive.folders) {
 
399
            folder.numUnpackSubStreams = 1;
 
400
        }
 
401
        int totalUnpackStreams = archive.folders.length;
 
402
        
 
403
        int nid = header.readUnsignedByte();
 
404
        if (nid == NID.kNumUnpackStream) {
 
405
            totalUnpackStreams = 0;
 
406
            for (final Folder folder : archive.folders) {
 
407
                final long numStreams = readUint64(header);
 
408
                folder.numUnpackSubStreams = (int)numStreams;
 
409
                totalUnpackStreams += numStreams;
 
410
            }
 
411
            nid = header.readUnsignedByte();
 
412
        }
 
413
        
 
414
        final SubStreamsInfo subStreamsInfo = new SubStreamsInfo();
 
415
        subStreamsInfo.unpackSizes = new long[totalUnpackStreams];
 
416
        subStreamsInfo.hasCrc = new BitSet(totalUnpackStreams);
 
417
        subStreamsInfo.crcs = new int[totalUnpackStreams];
 
418
        
 
419
        int nextUnpackStream = 0;
 
420
        for (final Folder folder : archive.folders) {
 
421
            if (folder.numUnpackSubStreams == 0) {
 
422
                continue;
 
423
            }
 
424
            long sum = 0;
 
425
            if (nid == NID.kSize) {
 
426
                for (int i = 0; i < (folder.numUnpackSubStreams - 1); i++) {
 
427
                    final long size = readUint64(header);
 
428
                    subStreamsInfo.unpackSizes[nextUnpackStream++] = size;
 
429
                    sum += size;
 
430
                }
 
431
            }
 
432
            subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
 
433
        }
 
434
        if (nid == NID.kSize) {
 
435
            nid = header.readUnsignedByte();
 
436
        }
 
437
        
 
438
        int numDigests = 0;
 
439
        for (final Folder folder : archive.folders) {
 
440
            if (folder.numUnpackSubStreams != 1 || !folder.hasCrc) {
 
441
                numDigests += folder.numUnpackSubStreams;
 
442
            }
 
443
        }
 
444
        
 
445
        if (nid == NID.kCRC) {
 
446
            final BitSet hasMissingCrc = readAllOrBits(header, numDigests);
 
447
            final int[] missingCrcs = new int[numDigests];
 
448
            for (int i = 0; i < numDigests; i++) {
 
449
                if (hasMissingCrc.get(i)) {
 
450
                    missingCrcs[i] = Integer.reverseBytes(header.readInt());
 
451
                }
 
452
            }
 
453
            int nextCrc = 0;
 
454
            int nextMissingCrc = 0;
 
455
            for (final Folder folder: archive.folders) {
 
456
                if (folder.numUnpackSubStreams == 1 && folder.hasCrc) {
 
457
                    subStreamsInfo.hasCrc.set(nextCrc, true);
 
458
                    subStreamsInfo.crcs[nextCrc] = folder.crc;
 
459
                    ++nextCrc;
 
460
                } else {
 
461
                    for (int i = 0; i < folder.numUnpackSubStreams; i++) {
 
462
                        subStreamsInfo.hasCrc.set(nextCrc, hasMissingCrc.get(nextMissingCrc));
 
463
                        subStreamsInfo.crcs[nextCrc] = missingCrcs[nextMissingCrc];
 
464
                        ++nextCrc;
 
465
                        ++nextMissingCrc;
 
466
                    }
 
467
                }
 
468
            }
 
469
            
 
470
            nid = header.readUnsignedByte();
 
471
        }
 
472
        
 
473
        if (nid != NID.kEnd) {
 
474
            throw new IOException("Badly terminated SubStreamsInfo");
 
475
        }
 
476
        
 
477
        archive.subStreamsInfo = subStreamsInfo;
 
478
    }
 
479
    
 
480
    private Folder readFolder(final DataInput header) throws IOException {
 
481
        final Folder folder = new Folder();
 
482
        
 
483
        final long numCoders = readUint64(header);
 
484
        final Coder[] coders = new Coder[(int)numCoders];
 
485
        long totalInStreams = 0;
 
486
        long totalOutStreams = 0;
 
487
        for (int i = 0; i < coders.length; i++) {
 
488
            coders[i] = new Coder();
 
489
            int bits = header.readUnsignedByte();
 
490
            final int idSize = bits & 0xf;
 
491
            final boolean isSimple = ((bits & 0x10) == 0);
 
492
            final boolean hasAttributes = ((bits & 0x20) != 0);
 
493
            final boolean moreAlternativeMethods = ((bits & 0x80) != 0);
 
494
            
 
495
            coders[i].decompressionMethodId = new byte[idSize];
 
496
            header.readFully(coders[i].decompressionMethodId);
 
497
            if (isSimple) {
 
498
                coders[i].numInStreams = 1;
 
499
                coders[i].numOutStreams = 1;
 
500
            } else {
 
501
                coders[i].numInStreams = readUint64(header);
 
502
                coders[i].numOutStreams = readUint64(header);
 
503
            }
 
504
            totalInStreams += coders[i].numInStreams;
 
505
            totalOutStreams += coders[i].numOutStreams;
 
506
            if (hasAttributes) {
 
507
                final long propertiesSize = readUint64(header);
 
508
                coders[i].properties = new byte[(int)propertiesSize];
 
509
                header.readFully(coders[i].properties);
 
510
            }
 
511
            // would need to keep looping as above:
 
512
            while (moreAlternativeMethods) {
 
513
                throw new IOException("Alternative methods are unsupported, please report. " +
 
514
                        "The reference implementation doesn't support them either.");
 
515
            }
 
516
        }
 
517
        folder.coders = coders;
 
518
        folder.totalInputStreams = totalInStreams;
 
519
        folder.totalOutputStreams = totalOutStreams;
 
520
        
 
521
        if (totalOutStreams == 0) {
 
522
            throw new IOException("Total output streams can't be 0");
 
523
        }
 
524
        final long numBindPairs = totalOutStreams - 1;
 
525
        final BindPair[] bindPairs = new BindPair[(int)numBindPairs];
 
526
        for (int i = 0; i < bindPairs.length; i++) {
 
527
            bindPairs[i] = new BindPair();
 
528
            bindPairs[i].inIndex = readUint64(header);
 
529
            bindPairs[i].outIndex = readUint64(header);
 
530
        }
 
531
        folder.bindPairs = bindPairs;
 
532
        
 
533
        if (totalInStreams < numBindPairs) {
 
534
            throw new IOException("Total input streams can't be less than the number of bind pairs");
 
535
        }
 
536
        final long numPackedStreams = totalInStreams - numBindPairs;
 
537
        final long packedStreams[] = new long[(int)numPackedStreams];
 
538
        if (numPackedStreams == 1) {
 
539
            int i;
 
540
            for (i = 0; i < (int)totalInStreams; i++) {
 
541
                if (folder.findBindPairForInStream(i) < 0) {
 
542
                    break;
 
543
                }
 
544
            }
 
545
            if (i == (int)totalInStreams) {
 
546
                throw new IOException("Couldn't find stream's bind pair index");
 
547
            }
 
548
            packedStreams[0] = i;
 
549
        } else {
 
550
            for (int i = 0; i < (int)numPackedStreams; i++) {
 
551
                packedStreams[i] = readUint64(header);
 
552
            }
 
553
        }
 
554
        folder.packedStreams = packedStreams;
 
555
        
 
556
        return folder;
 
557
    }
 
558
    
 
559
    private BitSet readAllOrBits(final DataInput header, final int size) throws IOException {
 
560
        final int areAllDefined = header.readUnsignedByte();
 
561
        final BitSet bits;
 
562
        if (areAllDefined != 0) {
 
563
            bits = new BitSet(size);
 
564
            for (int i = 0; i < size; i++) {
 
565
                bits.set(i, true);
 
566
            }
 
567
        } else {
 
568
            bits = readBits(header, size);
 
569
        }
 
570
        return bits;
 
571
    }
 
572
    
 
573
    private BitSet readBits(final DataInput header, final int size) throws IOException {
 
574
        final BitSet bits = new BitSet(size);
 
575
        int mask = 0;
 
576
        int cache = 0;
 
577
        for (int i = 0; i < size; i++) {
 
578
            if (mask == 0) {
 
579
                mask = 0x80;
 
580
                cache = header.readUnsignedByte();
 
581
            }
 
582
            bits.set(i, (cache & mask) != 0);
 
583
            mask >>>= 1;
 
584
        }
 
585
        return bits;
 
586
    }
 
587
    
 
588
    private void readFilesInfo(final DataInput header, final Archive archive) throws IOException {
 
589
        final long numFiles = readUint64(header);
 
590
        final SevenZArchiveEntry[] files = new SevenZArchiveEntry[(int)numFiles];
 
591
        for (int i = 0; i < files.length; i++) {
 
592
            files[i] = new SevenZArchiveEntry();
 
593
        }
 
594
        BitSet isEmptyStream = null;
 
595
        BitSet isEmptyFile = null; 
 
596
        BitSet isAnti = null;
 
597
        while (true) {
 
598
            final int propertyType = header.readUnsignedByte();
 
599
            if (propertyType == 0) {
 
600
                break;
 
601
            }
 
602
            long size = readUint64(header);
 
603
            switch (propertyType) {
 
604
                case NID.kEmptyStream: {
 
605
                    isEmptyStream = readBits(header, files.length);
 
606
                    break;
 
607
                }
 
608
                case NID.kEmptyFile: {
 
609
                    if (isEmptyStream == null) { // protect against NPE
 
610
                        throw new IOException("Header format error: kEmptyStream must appear before kEmptyFile");
 
611
                    }
 
612
                    isEmptyFile = readBits(header, isEmptyStream.cardinality());
 
613
                    break;
 
614
                }
 
615
                case NID.kAnti: {
 
616
                    if (isEmptyStream == null) { // protect against NPE
 
617
                        throw new IOException("Header format error: kEmptyStream must appear before kAnti");
 
618
                    }
 
619
                    isAnti = readBits(header, isEmptyStream.cardinality());
 
620
                    break;
 
621
                }
 
622
                case NID.kName: {
 
623
                    final int external = header.readUnsignedByte();
 
624
                    if (external != 0) {
 
625
                        throw new IOException("Not implemented");
 
626
                    } else {
 
627
                        if (((size - 1) & 1) != 0) {
 
628
                            throw new IOException("File names length invalid");
 
629
                        }
 
630
                        final byte[] names = new byte[(int)(size - 1)];
 
631
                        header.readFully(names);
 
632
                        int nextFile = 0;
 
633
                        int nextName = 0;
 
634
                        for (int i = 0; i < names.length; i += 2) {
 
635
                            if (names[i] == 0 && names[i+1] == 0) {
 
636
                                files[nextFile++].setName(new String(names, nextName, i-nextName, CharsetNames.UTF_16LE));
 
637
                                nextName = i + 2;
 
638
                            }
 
639
                        }
 
640
                        if (nextName != names.length || nextFile != files.length) {
 
641
                            throw new IOException("Error parsing file names");
 
642
                        }
 
643
                    }
 
644
                    break;
 
645
                }
 
646
                case NID.kCTime: {
 
647
                    final BitSet timesDefined = readAllOrBits(header, files.length);
 
648
                    final int external = header.readUnsignedByte();
 
649
                    if (external != 0) {
 
650
                        throw new IOException("Unimplemented");
 
651
                    } else {
 
652
                        for (int i = 0; i < files.length; i++) {
 
653
                            files[i].setHasCreationDate(timesDefined.get(i));
 
654
                            if (files[i].getHasCreationDate()) {
 
655
                                files[i].setCreationDate(Long.reverseBytes(header.readLong()));
 
656
                            }
 
657
                        }
 
658
                    }
 
659
                    break;
 
660
                }
 
661
                case NID.kATime: {
 
662
                    final BitSet timesDefined = readAllOrBits(header, files.length);
 
663
                    final int external = header.readUnsignedByte();
 
664
                    if (external != 0) {
 
665
                        throw new IOException("Unimplemented");
 
666
                    } else {
 
667
                        for (int i = 0; i < files.length; i++) {
 
668
                            files[i].setHasAccessDate(timesDefined.get(i));
 
669
                            if (files[i].getHasAccessDate()) {
 
670
                                files[i].setAccessDate(Long.reverseBytes(header.readLong()));
 
671
                            }
 
672
                        }
 
673
                    }
 
674
                    break;
 
675
                }
 
676
                case NID.kMTime: {
 
677
                    final BitSet timesDefined = readAllOrBits(header, files.length);
 
678
                    final int external = header.readUnsignedByte();
 
679
                    if (external != 0) {
 
680
                        throw new IOException("Unimplemented");
 
681
                    } else {
 
682
                        for (int i = 0; i < files.length; i++) {
 
683
                            files[i].setHasLastModifiedDate(timesDefined.get(i));
 
684
                            if (files[i].getHasLastModifiedDate()) {
 
685
                                files[i].setLastModifiedDate(Long.reverseBytes(header.readLong()));
 
686
                            }
 
687
                        }
 
688
                    }
 
689
                    break;
 
690
                }
 
691
                case NID.kWinAttributes: {
 
692
                    final BitSet attributesDefined = readAllOrBits(header, files.length);
 
693
                    final int external = header.readUnsignedByte();
 
694
                    if (external != 0) {
 
695
                        throw new IOException("Unimplemented");
 
696
                    } else {
 
697
                        for (int i = 0; i < files.length; i++) {
 
698
                            files[i].setHasWindowsAttributes(attributesDefined.get(i));
 
699
                            if (files[i].getHasWindowsAttributes()) {
 
700
                                files[i].setWindowsAttributes(Integer.reverseBytes(header.readInt()));
 
701
                            }
 
702
                        }
 
703
                    }
 
704
                    break;
 
705
                }
 
706
                case NID.kStartPos: {
 
707
                    throw new IOException("kStartPos is unsupported, please report");
 
708
                }
 
709
                case NID.kDummy: {
 
710
                    throw new IOException("kDummy is unsupported, please report");
 
711
                }
 
712
                
 
713
                default: {
 
714
                    throw new IOException("Unknown property " + propertyType);
 
715
                    // FIXME: Should actually:
 
716
                    //header.skipBytes((int)size);
 
717
                }
 
718
            }
 
719
        }
 
720
        int nonEmptyFileCounter = 0;
 
721
        int emptyFileCounter = 0;
 
722
        for (int i = 0; i < files.length; i++) {
 
723
            files[i].setHasStream((isEmptyStream == null) ? true : !isEmptyStream.get(i));
 
724
            if (files[i].hasStream()) {
 
725
                files[i].setDirectory(false);
 
726
                files[i].setAntiItem(false);
 
727
                files[i].setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter));
 
728
                files[i].setCrc(archive.subStreamsInfo.crcs[nonEmptyFileCounter]);
 
729
                files[i].setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]);
 
730
                ++nonEmptyFileCounter;
 
731
            } else {
 
732
                files[i].setDirectory((isEmptyFile == null) ? true : !isEmptyFile.get(emptyFileCounter));
 
733
                files[i].setAntiItem((isAnti == null) ? false : isAnti.get(emptyFileCounter));
 
734
                files[i].setHasCrc(false);
 
735
                files[i].setSize(0);
 
736
                ++emptyFileCounter;
 
737
            }
 
738
        }
 
739
        archive.files = files;
 
740
        calculateStreamMap(archive);
 
741
    }
 
742
    
 
743
    private void calculateStreamMap(final Archive archive) throws IOException {
 
744
        final StreamMap streamMap = new StreamMap();
 
745
        
 
746
        int nextFolderPackStreamIndex = 0;
 
747
        final int numFolders = (archive.folders != null) ? archive.folders.length : 0;
 
748
        streamMap.folderFirstPackStreamIndex = new int[numFolders];
 
749
        for (int i = 0; i < numFolders; i++) {
 
750
            streamMap.folderFirstPackStreamIndex[i] = nextFolderPackStreamIndex;
 
751
            nextFolderPackStreamIndex += archive.folders[i].packedStreams.length;
 
752
        }
 
753
        
 
754
        long nextPackStreamOffset = 0;
 
755
        final int numPackSizes = (archive.packSizes != null) ? archive.packSizes.length : 0;
 
756
        streamMap.packStreamOffsets = new long[numPackSizes];
 
757
        for (int i = 0; i < numPackSizes; i++) {
 
758
            streamMap.packStreamOffsets[i] = nextPackStreamOffset;
 
759
            nextPackStreamOffset += archive.packSizes[i]; 
 
760
        }
 
761
        
 
762
        streamMap.folderFirstFileIndex = new int[numFolders];
 
763
        streamMap.fileFolderIndex = new int[archive.files.length];
 
764
        int nextFolderIndex = 0;
 
765
        int nextFolderUnpackStreamIndex = 0;
 
766
        for (int i = 0; i < archive.files.length; i++) {
 
767
            if (!archive.files[i].hasStream() && nextFolderUnpackStreamIndex == 0) {
 
768
                streamMap.fileFolderIndex[i] = -1;
 
769
                continue;
 
770
            }
 
771
            if (nextFolderUnpackStreamIndex == 0) {
 
772
                for (; nextFolderIndex < archive.folders.length; ++nextFolderIndex) {
 
773
                    streamMap.folderFirstFileIndex[nextFolderIndex] = i;
 
774
                    if (archive.folders[nextFolderIndex].numUnpackSubStreams > 0) {
 
775
                        break;
 
776
                    }
 
777
                }
 
778
                if (nextFolderIndex >= archive.folders.length) {
 
779
                    throw new IOException("Too few folders in archive");
 
780
                }
 
781
            }
 
782
            streamMap.fileFolderIndex[i] = nextFolderIndex;
 
783
            if (!archive.files[i].hasStream()) {
 
784
                continue;
 
785
            }
 
786
            ++nextFolderUnpackStreamIndex;
 
787
            if (nextFolderUnpackStreamIndex >= archive.folders[nextFolderIndex].numUnpackSubStreams) {
 
788
                ++nextFolderIndex;
 
789
                nextFolderUnpackStreamIndex = 0;
 
790
            }
 
791
        }
 
792
        
 
793
        archive.streamMap = streamMap;
 
794
    }
 
795
    
 
796
    private void buildDecodingStream() throws IOException {
 
797
        final int folderIndex = archive.streamMap.fileFolderIndex[currentEntryIndex];
 
798
        if (folderIndex < 0) {
 
799
            currentEntryInputStream = new BoundedInputStream(
 
800
                    new ByteArrayInputStream(new byte[0]), 0);
 
801
            return;
 
802
        }
 
803
        if (currentFolderIndex == folderIndex) {
 
804
            // need to advance the folder input stream past the current file
 
805
            drainPreviousEntry();
 
806
        } else {
 
807
            currentFolderIndex = folderIndex;
 
808
            if (currentFolderInputStream != null) {
 
809
                currentFolderInputStream.close();
 
810
                currentFolderInputStream = null;
 
811
            }
 
812
            
 
813
            final Folder folder = archive.folders[folderIndex];
 
814
            final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex];
 
815
            final long folderOffset = SIGNATURE_HEADER_SIZE + archive.packPos +
 
816
                    archive.streamMap.packStreamOffsets[firstPackStreamIndex];
 
817
            currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex);
 
818
        }
 
819
        final SevenZArchiveEntry file = archive.files[currentEntryIndex];
 
820
        final InputStream fileStream = new BoundedInputStream(
 
821
                currentFolderInputStream, file.getSize());
 
822
        if (file.getHasCrc()) {
 
823
            currentEntryInputStream = new CRC32VerifyingInputStream(
 
824
                    fileStream, file.getSize(), file.getCrc());
 
825
        } else {
 
826
            currentEntryInputStream = fileStream;
 
827
        }
 
828
        
 
829
    }
 
830
    
 
831
    private void drainPreviousEntry() throws IOException {
 
832
        if (currentEntryInputStream != null) {
 
833
            final byte[] buffer = new byte[DRAIN_BUF_SIZE];
 
834
            while (currentEntryInputStream.read(buffer) >= 0) { // NOPMD
 
835
            }
 
836
            currentEntryInputStream.close();
 
837
            currentEntryInputStream = null;
 
838
        }
 
839
    }
 
840
    
 
841
    private InputStream buildDecoderStack(final Folder folder, final long folderOffset,
 
842
            final int firstPackStreamIndex) throws IOException {
 
843
        file.seek(folderOffset);
 
844
        InputStream inputStreamStack = new BoundedRandomAccessFileInputStream(file,
 
845
                archive.packSizes[firstPackStreamIndex]);
 
846
        for (final Coder coder : folder.coders) {
 
847
            if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
 
848
                throw new IOException("Multi input/output stream coders are not yet supported");
 
849
            }
 
850
            inputStreamStack = Coders.addDecoder(inputStreamStack, coder, password);
 
851
        }
 
852
        if (folder.hasCrc) {
 
853
            return new CRC32VerifyingInputStream(inputStreamStack,
 
854
                    folder.getUnpackSize(), folder.crc);
 
855
        } else {
 
856
            return inputStreamStack;
 
857
        }
 
858
    }
 
859
    
 
860
    /**
 
861
     * Reads a byte of data.
 
862
     * 
 
863
     * @return the byte read, or -1 if end of input is reached
 
864
     * @throws IOException
 
865
     *             if an I/O error has occurred
 
866
     */
 
867
    public int read() throws IOException {
 
868
        return currentEntryInputStream.read();
 
869
    }
 
870
    
 
871
    /**
 
872
     * Reads data into an array of bytes.
 
873
     * 
 
874
     * @param b the array to write data to
 
875
     * @return the number of bytes read, or -1 if end of input is reached
 
876
     * @throws IOException
 
877
     *             if an I/O error has occurred
 
878
     */
 
879
    public int read(byte[] b) throws IOException {
 
880
        return read(b, 0, b.length);
 
881
    }
 
882
    
 
883
    /**
 
884
     * Reads data into an array of bytes.
 
885
     * 
 
886
     * @param b the array to write data to
 
887
     * @param off offset into the buffer to start filling at
 
888
     * @param len of bytes to read
 
889
     * @return the number of bytes read, or -1 if end of input is reached
 
890
     * @throws IOException
 
891
     *             if an I/O error has occurred
 
892
     */
 
893
    public int read(byte[] b, int off, int len) throws IOException {
 
894
        return currentEntryInputStream.read(b, off, len);
 
895
    }
 
896
    
 
897
    private static long readUint64(final DataInput in) throws IOException {
 
898
        int firstByte = in.readUnsignedByte();
 
899
        int mask = 0x80;
 
900
        long value = 0;
 
901
        for (int i = 0; i < 8; i++) {
 
902
            if ((firstByte & mask) == 0) {
 
903
                return value | ((firstByte & (mask - 1)) << (8 * i));
 
904
            }
 
905
            long nextByte = in.readUnsignedByte();
 
906
            value |= (nextByte << (8 * i));
 
907
            mask >>>= 1;
 
908
        }
 
909
        return value;
 
910
    }
 
911
}