View Javadoc
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  package org.apache.commons.compress.archivers.sevenz;
18  
19  import static java.nio.charset.StandardCharsets.UTF_16LE;
20  import static java.nio.charset.StandardCharsets.UTF_8;
21  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotEquals;
25  import static org.junit.jupiter.api.Assertions.assertNotNull;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertThrows;
28  import static org.junit.jupiter.api.Assertions.assertTrue;
29  
30  import java.io.ByteArrayOutputStream;
31  import java.io.File;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.nio.file.Files;
35  import java.nio.file.Path;
36  import java.nio.file.attribute.FileTime;
37  import java.security.NoSuchAlgorithmException;
38  import java.time.Instant;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Collections;
42  import java.util.Date;
43  import java.util.HashMap;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Random;
48  import java.util.function.Function;
49  
50  import javax.crypto.Cipher;
51  
52  import org.apache.commons.compress.AbstractTest;
53  import org.apache.commons.compress.MemoryLimitException;
54  import org.apache.commons.compress.PasswordRequiredException;
55  import org.apache.commons.compress.utils.MultiReadOnlySeekableByteChannel;
56  import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
57  import org.apache.commons.io.IOUtils;
58  import org.junit.jupiter.api.Test;
59  
60  public class SevenZFileTest extends AbstractTest {
61      private static final String TEST2_CONTENT = "<?xml version = '1.0'?>\r\n<!DOCTYPE" + " connections>\r\n<meinxml>\r\n\t<leer />\r\n</meinxml>\n";
62  
63      private static boolean isStrongCryptoAvailable() throws NoSuchAlgorithmException {
64          return Cipher.getMaxAllowedKeyLength("AES/ECB/PKCS5Padding") >= 256;
65      }
66  
67      private void assertDate(final SevenZArchiveEntry entry, final String value, final Function<SevenZArchiveEntry, Boolean> hasValue,
68              final Function<SevenZArchiveEntry, FileTime> timeFunction, final Function<SevenZArchiveEntry, Date> dateFunction) {
69          if (value != null) {
70              assertTrue(hasValue.apply(entry));
71              final Instant parsedInstant = Instant.parse(value);
72              final FileTime parsedFileTime = FileTime.from(parsedInstant);
73              assertEquals(parsedFileTime, timeFunction.apply(entry));
74              assertEquals(Date.from(parsedInstant), dateFunction.apply(entry));
75          } else {
76              assertFalse(hasValue.apply(entry));
77              assertThrows(UnsupportedOperationException.class, () -> timeFunction.apply(entry));
78              assertThrows(UnsupportedOperationException.class, () -> dateFunction.apply(entry));
79          }
80      }
81  
82      private void assertDates(final SevenZArchiveEntry entry, final String modified, final String access, final String creation) {
83          assertDate(entry, modified, SevenZArchiveEntry::getHasLastModifiedDate, SevenZArchiveEntry::getLastModifiedTime,
84                  SevenZArchiveEntry::getLastModifiedDate);
85          assertDate(entry, access, SevenZArchiveEntry::getHasAccessDate, SevenZArchiveEntry::getAccessTime, SevenZArchiveEntry::getAccessDate);
86          assertDate(entry, creation, SevenZArchiveEntry::getHasCreationDate, SevenZArchiveEntry::getCreationTime, SevenZArchiveEntry::getCreationDate);
87      }
88  
89      private void checkHelloWorld(final String fileName) throws Exception {
90          try (SevenZFile sevenZFile = getSevenZFile(fileName)) {
91              final SevenZArchiveEntry entry = sevenZFile.getNextEntry();
92              assertEquals("Hello world.txt", entry.getName());
93              assertDates(entry, "2013-05-07T19:40:48Z", null, null);
94              final byte[] contents = new byte[(int) entry.getSize()];
95              int off = 0;
96              while (off < contents.length) {
97                  final int bytesRead = sevenZFile.read(contents, off, contents.length - off);
98                  assert bytesRead >= 0;
99                  off += bytesRead;
100             }
101             assertEquals("Hello, world!\n", new String(contents, UTF_8));
102             assertNull(sevenZFile.getNextEntry());
103         }
104     }
105 
106     private SevenZFile getSevenZFile(final String specialPath) throws IOException {
107         return SevenZFile.builder().setFile(getFile(specialPath)).get();
108     }
109 
110     private byte[] read(final SevenZFile sevenZFile, final SevenZArchiveEntry entry) throws IOException {
111         try (InputStream inputStream = sevenZFile.getInputStream(entry)) {
112             return IOUtils.toByteArray(inputStream);
113         }
114     }
115 
116     private byte[] readFully(final SevenZFile archive) throws IOException {
117         final byte[] buf = new byte[1024];
118         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
119         for (int len = 0; (len = archive.read(buf)) > 0;) {
120             baos.write(buf, 0, len);
121         }
122         return baos.toByteArray();
123     }
124 
125     @Test
126     public void test7zDecryptUnarchive() throws Exception {
127         if (isStrongCryptoAvailable()) {
128             test7zUnarchive(getFile("bla.encrypted.7z"), SevenZMethod.LZMA, // stack LZMA + AES
129                     "foo".getBytes(UTF_16LE));
130         }
131     }
132 
133     @Test
134     public void test7zDecryptUnarchiveUsingCharArrayPassword() throws Exception {
135         if (isStrongCryptoAvailable()) {
136             test7zUnarchive(getFile("bla.encrypted.7z"), SevenZMethod.LZMA, // stack LZMA + AES
137                     "foo".toCharArray());
138         }
139     }
140 
141     @Test
142     public void test7zDeflate64Unarchive() throws Exception {
143         test7zUnarchive(getFile("bla.deflate64.7z"), SevenZMethod.DEFLATE64);
144     }
145 
146     @Test
147     public void test7zDeflateUnarchive() throws Exception {
148         test7zUnarchive(getFile("bla.deflate.7z"), SevenZMethod.DEFLATE);
149     }
150 
151     @Test
152     public void test7zMultiVolumeUnarchive() throws Exception {
153         try (@SuppressWarnings("deprecation")
154         SevenZFile sevenZFile = new SevenZFile(MultiReadOnlySeekableByteChannel.forFiles(getFile("bla-multi.7z.001"), getFile("bla-multi.7z.002")))) {
155             test7zUnarchive(sevenZFile, SevenZMethod.LZMA2);
156         }
157         try (SevenZFile sevenZFile = SevenZFile.builder()
158                 .setSeekableByteChannel(MultiReadOnlySeekableByteChannel.forFiles(getFile("bla-multi.7z.001"), getFile("bla-multi.7z.002"))).get()) {
159             test7zUnarchive(sevenZFile, SevenZMethod.LZMA2);
160         }
161     }
162 
163     @Test
164     public void test7zUnarchive() throws Exception {
165         test7zUnarchive(getFile("bla.7z"), SevenZMethod.LZMA);
166     }
167 
168     private void test7zUnarchive(final File file, final SevenZMethod method) throws Exception {
169         test7zUnarchive(file, method, false);
170     }
171 
172     private void test7zUnarchive(final File file, final SevenZMethod method, final boolean tryToRecoverBrokenArchives) throws Exception {
173         test7zUnarchive(file, method, (char[]) null, tryToRecoverBrokenArchives);
174     }
175 
176     private void test7zUnarchive(final File file, final SevenZMethod method, final byte[] password) throws Exception {
177         try (@SuppressWarnings("deprecation")
178         SevenZFile sevenZFile = new SevenZFile(file, password)) {
179             test7zUnarchive(sevenZFile, method);
180         }
181         try (SevenZFile sevenZFile = SevenZFile.builder().setFile(file).setPassword(password).get()) {
182             test7zUnarchive(sevenZFile, method);
183         }
184     }
185 
186     private void test7zUnarchive(final File file, final SevenZMethod m, final char[] password) throws Exception {
187         test7zUnarchive(file, m, password, false);
188     }
189 
190     private void test7zUnarchive(final File file, final SevenZMethod m, final char[] password, final boolean tryToRecoverBrokenArchives) throws Exception {
191         try (@SuppressWarnings("deprecation")
192         SevenZFile sevenZFile = new SevenZFile(file, password,
193                 SevenZFileOptions.builder().withTryToRecoverBrokenArchives(tryToRecoverBrokenArchives).build())) {
194             test7zUnarchive(sevenZFile, m);
195         }
196         try (SevenZFile sevenZFile = SevenZFile.builder().setFile(file).setPassword(password).setTryToRecoverBrokenArchives(tryToRecoverBrokenArchives).get()) {
197             test7zUnarchive(sevenZFile, m);
198         }
199     }
200 
201     private void test7zUnarchive(final SevenZFile sevenZFile, final SevenZMethod m) throws Exception {
202         SevenZArchiveEntry entry = sevenZFile.getNextEntry();
203         assertEquals("test1.xml", entry.getName());
204         assertDates(entry, "2007-11-14T10:19:02Z", null, null);
205         assertEquals(m, entry.getContentMethods().iterator().next().getMethod());
206         entry = sevenZFile.getNextEntry();
207         assertEquals("test2.xml", entry.getName());
208         assertDates(entry, "2007-11-14T10:19:02Z", null, null);
209         assertEquals(m, entry.getContentMethods().iterator().next().getMethod());
210         final byte[] contents = new byte[(int) entry.getSize()];
211         int off = 0;
212         while (off < contents.length) {
213             final int bytesRead = sevenZFile.read(contents, off, contents.length - off);
214             assert bytesRead >= 0;
215             off += bytesRead;
216         }
217         assertEquals(TEST2_CONTENT, new String(contents, UTF_8));
218         assertNull(sevenZFile.getNextEntry());
219     }
220 
221     @Test
222     public void test7zUnarchiveWithDefectHeader() throws Exception {
223         test7zUnarchive(getFile("bla.noendheaderoffset.7z"), SevenZMethod.LZMA, true);
224     }
225 
226     @Test
227     public void test7zUnarchiveWithDefectHeaderFailsByDefault() throws Exception {
228         assertThrows(IOException.class, () -> test7zUnarchive(getFile("bla.noendheaderoffset.7z"), SevenZMethod.LZMA));
229     }
230 
231     @Test
232     public void testAllEmptyFilesArchive() throws Exception {
233         try (SevenZFile archive = getSevenZFile("7z-empty-mhc-off.7z")) {
234             final SevenZArchiveEntry e = archive.getNextEntry();
235             assertNotNull(e);
236             assertEquals("empty", e.getName());
237             assertDates(e, "2013-05-14T17:50:19Z", null, null);
238             assertNull(archive.getNextEntry());
239         }
240     }
241 
242     /**
243      * @see "https://issues.apache.org/jira/browse/COMPRESS-256"
244      */
245     @Test
246     public void testCompressedHeaderWithNonDefaultDictionarySize() throws Exception {
247         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z")) {
248             int count = 0;
249             while (sevenZFile.getNextEntry() != null) {
250                 count++;
251             }
252             assertEquals(446, count);
253         }
254     }
255 
256     @Test
257     public void testEncryptedArchiveRequiresPassword() throws Exception {
258         final PasswordRequiredException ex = assertThrows(PasswordRequiredException.class, () -> getSevenZFile("bla.encrypted.7z").close(),
259                 "shouldn't decrypt without a password");
260         final String msg = ex.getMessage();
261         assertTrue(msg.startsWith("Cannot read encrypted content from "), "Should start with whining about being unable to decrypt");
262         assertTrue(msg.endsWith(" without a password."), "Should finish the sentence properly");
263         assertTrue(msg.contains("bla.encrypted.7z"), "Should contain archive's name");
264     }
265 
266     @Test
267     public void testExtractNonExistSpecifiedFile() throws Exception {
268         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z");
269                 SevenZFile anotherSevenZFile = getSevenZFile("bla.7z")) {
270             for (final SevenZArchiveEntry nonExistEntry : anotherSevenZFile.getEntries()) {
271                 assertThrows(IllegalArgumentException.class, () -> sevenZFile.getInputStream(nonExistEntry));
272             }
273         }
274     }
275 
276     @Test
277     public void testExtractSpecifiedFile() throws Exception {
278         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z")) {
279             final String testTxtContents = "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
280                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
281                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
282                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011";
283 
284             for (final SevenZArchiveEntry entry : sevenZFile.getEntries()) {
285                 if (entry.getName().equals("commons-compress-1.7-src/src/test/resources/test.txt")) {
286                     final byte[] contents = new byte[(int) entry.getSize()];
287                     int off = 0;
288                     final InputStream inputStream = sevenZFile.getInputStream(entry);
289                     while (off < contents.length) {
290                         final int bytesRead = inputStream.read(contents, off, contents.length - off);
291                         assert bytesRead >= 0;
292                         off += bytesRead;
293                     }
294                     assertEquals(testTxtContents, new String(contents, UTF_8));
295                     break;
296                 }
297             }
298         }
299     }
300 
301     @Test
302     public void testExtractSpecifiedFileDeprecated() throws Exception {
303         try (@SuppressWarnings("deprecation")
304         SevenZFile sevenZFile = new SevenZFile(getFile("COMPRESS-256.7z"))) {
305             final String testTxtContents = "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
306                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
307                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
308                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011";
309 
310             for (final SevenZArchiveEntry entry : sevenZFile.getEntries()) {
311                 if (entry.getName().equals("commons-compress-1.7-src/src/test/resources/test.txt")) {
312                     final byte[] contents = new byte[(int) entry.getSize()];
313                     int off = 0;
314                     final InputStream inputStream = sevenZFile.getInputStream(entry);
315                     while (off < contents.length) {
316                         final int bytesRead = inputStream.read(contents, off, contents.length - off);
317                         assert bytesRead >= 0;
318                         off += bytesRead;
319                     }
320                     assertEquals(testTxtContents, new String(contents, UTF_8));
321                     break;
322                 }
323             }
324         }
325     }
326 
327     @Test
328     public void testGetDefaultName() throws Exception {
329         try (SevenZFile sevenZFile = getSevenZFile("bla.deflate64.7z")) {
330             assertEquals("bla.deflate64", sevenZFile.getDefaultName());
331         }
332         try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(Files.newByteChannel(getFile("bla.deflate64.7z").toPath())).get()) {
333             assertNull(sevenZFile.getDefaultName());
334         }
335         try (@SuppressWarnings("deprecation")
336         SevenZFile sevenZFile = new SevenZFile(Files.newByteChannel(getFile("bla.deflate64.7z").toPath()), "foo")) {
337             assertEquals("foo~", sevenZFile.getDefaultName());
338         }
339         try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(Files.newByteChannel(getFile("bla.deflate64.7z").toPath()))
340                 .setDefaultName("foo").get()) {
341             assertEquals("foo~", sevenZFile.getDefaultName());
342         }
343         try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(Files.newByteChannel(getFile("bla.deflate64.7z").toPath()))
344                 .setDefaultName(".foo").get()) {
345             assertEquals(".foo~", sevenZFile.getDefaultName());
346         }
347     }
348 
349     @Test
350     public void testGetEntriesOfUnarchiveInMemoryTest() throws IOException {
351         final byte[] data = readAllBytes("bla.7z");
352         try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(new SeekableInMemoryByteChannel(data)).get()) {
353             final Iterable<SevenZArchiveEntry> entries = sevenZFile.getEntries();
354             final Iterator<SevenZArchiveEntry> iter = entries.iterator();
355             SevenZArchiveEntry entry = iter.next();
356             assertEquals("test1.xml", entry.getName());
357             entry = iter.next();
358             assertEquals("test2.xml", entry.getName());
359             assertFalse(iter.hasNext());
360         }
361     }
362 
363     @Test
364     public void testGetEntriesOfUnarchiveTest() throws IOException {
365         try (SevenZFile sevenZFile = getSevenZFile("bla.7z")) {
366             final Iterable<SevenZArchiveEntry> entries = sevenZFile.getEntries();
367             final Iterator<SevenZArchiveEntry> iter = entries.iterator();
368             SevenZArchiveEntry entry = iter.next();
369             assertEquals("test1.xml", entry.getName());
370             entry = iter.next();
371             assertEquals("test2.xml", entry.getName());
372             assertFalse(iter.hasNext());
373         }
374     }
375 
376     @Test
377     public void testGivenNameWinsOverDefaultName() throws Exception {
378         try (@SuppressWarnings("deprecation")
379         SevenZFile sevenZFile = new SevenZFile(getFile("bla.7z"), SevenZFileOptions.builder().withUseDefaultNameForUnnamedEntries(true).build())) {
380             SevenZArchiveEntry ae = sevenZFile.getNextEntry();
381             assertNotNull(ae);
382             assertEquals("test1.xml", ae.getName());
383             ae = sevenZFile.getNextEntry();
384             assertNotNull(ae);
385             assertEquals("test2.xml", ae.getName());
386             assertNull(sevenZFile.getNextEntry());
387         }
388         try (SevenZFile sevenZFile = SevenZFile.builder().setFile(getFile("bla.7z")).setUseDefaultNameForUnnamedEntries(true).get()) {
389             SevenZArchiveEntry ae = sevenZFile.getNextEntry();
390             assertNotNull(ae);
391             assertEquals("test1.xml", ae.getName());
392             ae = sevenZFile.getNextEntry();
393             assertNotNull(ae);
394             assertEquals("test2.xml", ae.getName());
395             assertNull(sevenZFile.getNextEntry());
396         }
397     }
398 
399     /**
400      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-492">COMPRESS-492</a>
401      */
402     @Test
403     public void testHandlesEmptyArchiveWithFilesInfo() throws Exception {
404         final File file = newTempFile("empty.7z");
405         try (SevenZOutputFile s = new SevenZOutputFile(file)) {
406         }
407         try (SevenZFile z = SevenZFile.builder().setFile(file).get()) {
408             assertFalse(z.getEntries().iterator().hasNext());
409             assertNull(z.getNextEntry());
410         }
411     }
412 
413     /**
414      * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-492">COMPRESS-492</a>
415      */
416     @Test
417     public void testHandlesEmptyArchiveWithoutFilesInfo() throws Exception {
418         try (SevenZFile z = getSevenZFile("COMPRESS-492.7z")) {
419             assertFalse(z.getEntries().iterator().hasNext());
420             assertNull(z.getNextEntry());
421         }
422     }
423 
424     @Test
425     public void testHelloWorldHeaderCompressionOffCopy() throws Exception {
426         checkHelloWorld("7z-hello-mhc-off-copy.7z");
427     }
428 
429     @Test
430     public void testHelloWorldHeaderCompressionOffLZMA2() throws Exception {
431         checkHelloWorld("7z-hello-mhc-off-lzma2.7z");
432     }
433 
434     @Test
435     public void testLimitExtractionMemory() {
436         assertThrows(MemoryLimitException.class, () -> {
437             try (SevenZFile sevenZFile = SevenZFile.builder().setFile(getFile("bla.7z")).setMaxMemoryLimitKb(1).get()) {
438                 // Do nothing. Exception should be thrown
439             }
440         });
441     }
442 
443     @Test
444     public void testNoNameCanBeReplacedByDefaultName() throws Exception {
445         try (SevenZFile sevenZFile = SevenZFile.builder().setFile(getFile("bla-nonames.7z")).setUseDefaultNameForUnnamedEntries(true).get()) {
446             SevenZArchiveEntry ae = sevenZFile.getNextEntry();
447             assertNotNull(ae);
448             assertEquals("bla-nonames", ae.getName());
449             ae = sevenZFile.getNextEntry();
450             assertNotNull(ae);
451             assertEquals("bla-nonames", ae.getName());
452             assertNull(sevenZFile.getNextEntry());
453         }
454     }
455 
456     @Test
457     public void testNoNameMeansNoNameByDefault() throws Exception {
458         try (SevenZFile sevenZFile = getSevenZFile("bla-nonames.7z")) {
459             SevenZArchiveEntry ae = sevenZFile.getNextEntry();
460             assertNotNull(ae);
461             assertNull(ae.getName());
462             ae = sevenZFile.getNextEntry();
463             assertNotNull(ae);
464             assertNull(ae.getName());
465             assertNull(sevenZFile.getNextEntry());
466         }
467     }
468 
469     @Test
470     public void testNoOOMOnCorruptedHeader() throws IOException {
471         final List<Path> testFiles = new ArrayList<>();
472         testFiles.add(getPath("COMPRESS-542-1.7z"));
473         testFiles.add(getPath("COMPRESS-542-2.7z"));
474         testFiles.add(getPath("COMPRESS-542-endheadercorrupted.7z"));
475         testFiles.add(getPath("COMPRESS-542-endheadercorrupted2.7z"));
476 
477         for (final Path file : testFiles) {
478             {
479                 final IOException e = assertThrows(IOException.class, () -> {
480                     try (@SuppressWarnings("deprecation")
481                     SevenZFile sevenZFile = new SevenZFile(Files.newByteChannel(file),
482                             SevenZFileOptions.builder().withTryToRecoverBrokenArchives(true).build())) {
483                     }
484                 }, "Expected IOException: start header corrupt and unable to guess end header");
485                 assertEquals("Start header corrupt and unable to guess end header", e.getMessage());
486             }
487             {
488                 final IOException e = assertThrows(IOException.class, () -> {
489                     try (SevenZFile sevenZFile = SevenZFile.builder().setSeekableByteChannel(Files.newByteChannel(file)).setTryToRecoverBrokenArchives(true)
490                             .get()) {
491                     }
492                 }, "Expected IOException: start header corrupt and unable to guess end header");
493                 assertEquals("Start header corrupt and unable to guess end header", e.getMessage());
494             }
495         }
496     }
497 
498     @Test
499     public void testRandomAccessMultipleReadSameFile() throws Exception {
500         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z")) {
501             final String testTxtContents = "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
502                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
503                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
504                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011";
505 
506             SevenZArchiveEntry entry;
507             SevenZArchiveEntry testTxtEntry = null;
508             while ((entry = sevenZFile.getNextEntry()) != null) {
509                 if (entry.getName().equals("commons-compress-1.7-src/src/test/resources/test.txt")) {
510                     testTxtEntry = entry;
511                     break;
512                 }
513             }
514 
515             assertNotNull(testTxtEntry, "testTxtEntry");
516             final byte[] contents = new byte[(int) testTxtEntry.getSize()];
517             int numberOfReads = 10;
518             while (numberOfReads-- > 0) {
519                 try (InputStream inputStream = sevenZFile.getInputStream(testTxtEntry)) {
520                     int off = 0;
521                     while (off < contents.length) {
522                         final int bytesRead = inputStream.read(contents, off, contents.length - off);
523                         assert bytesRead >= 0;
524                         off += bytesRead;
525                     }
526                     assertEquals(SevenZMethod.LZMA2, testTxtEntry.getContentMethods().iterator().next().getMethod());
527                     assertEquals(testTxtContents, new String(contents, UTF_8));
528                 }
529             }
530         }
531     }
532 
533     @Test
534     public void testRandomAccessTogetherWithSequentialAccess() throws Exception {
535         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z")) {
536             // @formatter:off
537             final String testTxtContents = "111111111111111111111111111000101011\n"
538                     + "111111111111111111111111111000101011\n"
539                     + "111111111111111111111111111000101011\n"
540                     + "111111111111111111111111111000101011\n"
541                     + "111111111111111111111111111000101011\n"
542                     + "111111111111111111111111111000101011\n"
543                     + "111111111111111111111111111000101011\n"
544                     + "111111111111111111111111111000101011\n"
545                     + "111111111111111111111111111000101011\n"
546                     + "111111111111111111111111111000101011";
547             final String filesTxtContents = "0xxxxxxxxx10xxxxxxxx20xxxxxxxx30xxxxxxxx40xxxxxxxx50xxxxxxxx60xxxxxxxx70xxxxxxxx80xxxxxxxx90xxxxxxxx100"
548                     + "xxxxxxx110xxxxxxx120xxxxxxx130xxxxxxx -> 0yyyyyyyyy10yyyyyyyy20yyyyyyyy30yyyyyyyy40yyyyyyyy50yyyyyyyy60yyyyyyyy70yyyyyyyy80"
549                     + "yyyyyyyy90yyyyyyyy100yyyyyyy110yyyyyyy120yyyyyyy130yyyyyyy\n";
550             // @formatter:off
551             int off;
552             byte[] contents;
553 
554             // call getNextEntry and read before calling getInputStream
555             sevenZFile.getNextEntry();
556             SevenZArchiveEntry nextEntry = sevenZFile.getNextEntry();
557             contents = new byte[(int) nextEntry.getSize()];
558             off = 0;
559 
560             assertEquals(SevenZMethod.LZMA2, nextEntry.getContentMethods().iterator().next().getMethod());
561 
562             // just read them
563             while (off < contents.length) {
564                 final int bytesRead = sevenZFile.read(contents, off, contents.length - off);
565                 assert bytesRead >= 0;
566                 off += bytesRead;
567             }
568 
569             sevenZFile.getNextEntry();
570             sevenZFile.getNextEntry();
571 
572             for (final SevenZArchiveEntry entry : sevenZFile.getEntries()) {
573                 // commons-compress-1.7-src/src/test/resources/test.txt
574                 if (entry.getName().equals("commons-compress-1.7-src/src/test/resources/longsymlink/files.txt")) {
575                     contents = new byte[(int) entry.getSize()];
576                     off = 0;
577                     final InputStream inputStream = sevenZFile.getInputStream(entry);
578                     while (off < contents.length) {
579                         final int bytesRead = inputStream.read(contents, off, contents.length - off);
580                         assert bytesRead >= 0;
581                         off += bytesRead;
582                     }
583                     assertEquals(SevenZMethod.LZMA2, entry.getContentMethods().iterator().next().getMethod());
584                     assertEquals(filesTxtContents, new String(contents, UTF_8));
585                     break;
586                 }
587             }
588 
589             // call getNextEntry after getInputStream
590             nextEntry = sevenZFile.getNextEntry();
591             while (!nextEntry.getName().equals("commons-compress-1.7-src/src/test/resources/test.txt")) {
592                 nextEntry = sevenZFile.getNextEntry();
593             }
594 
595             contents = new byte[(int) nextEntry.getSize()];
596             off = 0;
597             while (off < contents.length) {
598                 final int bytesRead = sevenZFile.read(contents, off, contents.length - off);
599                 assert bytesRead >= 0;
600                 off += bytesRead;
601             }
602             assertEquals(SevenZMethod.LZMA2, nextEntry.getContentMethods().iterator().next().getMethod());
603             assertEquals(testTxtContents, new String(contents, UTF_8));
604         }
605     }
606 
607     @Test
608     public void testRandomAccessWhenJumpingBackwards() throws Exception {
609         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z")) {
610             final String testTxtContents = "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
611                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
612                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
613                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011";
614 
615             SevenZArchiveEntry entry;
616             SevenZArchiveEntry testTxtEntry = null;
617             while ((entry = sevenZFile.getNextEntry()) != null) {
618                 if (entry.getName().equals("commons-compress-1.7-src/src/test/resources/test.txt")) {
619                     testTxtEntry = entry;
620                     break;
621                 }
622             }
623 
624             // read the next entry and jump back using random access
625             final SevenZArchiveEntry entryAfterTestTxtEntry = sevenZFile.getNextEntry();
626             final byte[] entryAfterTestTxtEntryContents = new byte[(int) entryAfterTestTxtEntry.getSize()];
627             int off = 0;
628             while (off < entryAfterTestTxtEntryContents.length) {
629                 final int bytesRead = sevenZFile.read(entryAfterTestTxtEntryContents, off, entryAfterTestTxtEntryContents.length - off);
630                 assert bytesRead >= 0;
631                 off += bytesRead;
632             }
633 
634             // jump backwards
635             assertNotNull(testTxtEntry, "testTxtEntry");
636             final byte[] contents = new byte[(int) testTxtEntry.getSize()];
637             try (InputStream inputStream = sevenZFile.getInputStream(testTxtEntry)) {
638                 off = 0;
639                 while (off < contents.length) {
640                     final int bytesRead = inputStream.read(contents, off, contents.length - off);
641                     assert bytesRead >= 0;
642                     off += bytesRead;
643                 }
644                 assertEquals(SevenZMethod.LZMA2, testTxtEntry.getContentMethods().iterator().next().getMethod());
645                 assertEquals(testTxtContents, new String(contents, UTF_8));
646             }
647 
648             // then read the next entry using getNextEntry
649             final SevenZArchiveEntry nextTestTxtEntry = sevenZFile.getNextEntry();
650             final byte[] nextTestContents = new byte[(int) nextTestTxtEntry.getSize()];
651             off = 0;
652             while (off < nextTestContents.length) {
653                 final int bytesRead = sevenZFile.read(nextTestContents, off, nextTestContents.length - off);
654                 assert bytesRead >= 0;
655                 off += bytesRead;
656             }
657 
658             assertEquals(nextTestTxtEntry.getName(), entryAfterTestTxtEntry.getName());
659             assertEquals(nextTestTxtEntry.getSize(), entryAfterTestTxtEntry.getSize());
660             assertArrayEquals(nextTestContents, entryAfterTestTxtEntryContents);
661         }
662     }
663 
664     @Test
665     public void testRandomAccessWhenJumpingForwards() throws Exception {
666         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-256.7z")) {
667             final String testTxtContents = "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
668                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
669                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011\n"
670                     + "111111111111111111111111111000101011\n" + "111111111111111111111111111000101011";
671 
672             SevenZArchiveEntry testTxtEntry = null;
673             final Iterable<SevenZArchiveEntry> entries = sevenZFile.getEntries();
674             for (final SevenZArchiveEntry Entry : entries) {
675                 testTxtEntry = Entry;
676                 if (testTxtEntry.getName().equals("commons-compress-1.7-src/src/test/resources/test.txt")) {
677                     break;
678                 }
679             }
680             final SevenZArchiveEntry firstEntry = sevenZFile.getNextEntry();
681             // only read some of the data of the first entry
682             byte[] contents = new byte[(int) firstEntry.getSize() / 2];
683             sevenZFile.read(contents);
684 
685             // and the third entry
686             sevenZFile.getNextEntry();
687             final SevenZArchiveEntry thirdEntry = sevenZFile.getNextEntry();
688             contents = new byte[(int) thirdEntry.getSize() / 2];
689             sevenZFile.read(contents);
690 
691             // and then read a file after the first entry using random access
692             assertNotNull(testTxtEntry, "testTxtEntry");
693             contents = new byte[(int) testTxtEntry.getSize()];
694             int numberOfReads = 10;
695             while (numberOfReads-- > 0) {
696                 try (InputStream inputStream = sevenZFile.getInputStream(testTxtEntry)) {
697                     int off = 0;
698                     while (off < contents.length) {
699                         final int bytesRead = inputStream.read(contents, off, contents.length - off);
700                         assert bytesRead >= 0;
701                         off += bytesRead;
702                     }
703                     assertEquals(SevenZMethod.LZMA2, testTxtEntry.getContentMethods().iterator().next().getMethod());
704                     assertEquals(testTxtContents, new String(contents, UTF_8));
705                 }
706             }
707         }
708     }
709 
710     // https://issues.apache.org/jira/browse/COMPRESS-320
711     @Test
712     public void testRandomlySkippingEntries() throws Exception {
713         // Read sequential reference.
714         final Map<String, byte[]> entriesByName = new HashMap<>();
715         try (SevenZFile archive = getSevenZFile("COMPRESS-320/Copy.7z")) {
716             SevenZArchiveEntry entry;
717             while ((entry = archive.getNextEntry()) != null) {
718                 if (entry.hasStream()) {
719                     entriesByName.put(entry.getName(), readFully(archive));
720                 }
721             }
722         }
723 
724         final String[] variants = { "BZip2-solid.7z", "BZip2.7z", "Copy-solid.7z", "Copy.7z", "Deflate-solid.7z", "Deflate.7z", "LZMA-solid.7z", "LZMA.7z",
725                 "LZMA2-solid.7z", "LZMA2.7z",
726                 // TODO: unsupported compression method.
727                 // "PPMd-solid.7z",
728                 // "PPMd.7z",
729         };
730 
731         // TODO: use randomized testing for predictable, but different, randomness.
732         final Random rnd = new Random(0xdeadbeef);
733         for (final String fileName : variants) {
734             try (SevenZFile archive = getSevenZFile("COMPRESS-320/" + fileName)) {
735 
736                 SevenZArchiveEntry entry;
737                 while ((entry = archive.getNextEntry()) != null) {
738                     // Sometimes skip reading entries.
739                     if (rnd.nextBoolean()) {
740                         continue;
741                     }
742 
743                     if (entry.hasStream()) {
744                         assertTrue(entriesByName.containsKey(entry.getName()));
745                         final byte[] content = readFully(archive);
746                         assertArrayEquals(content, entriesByName.get(entry.getName()), "Content mismatch on: " + fileName + "!" + entry.getName());
747                     }
748                 }
749 
750             }
751         }
752     }
753 
754     @Test
755     public void testReadBigSevenZipFile() throws IOException {
756         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-592.7z")) {
757             SevenZArchiveEntry entry = sevenZFile.getNextEntry();
758             while (entry != null) {
759                 if (entry.hasStream()) {
760                     final byte[] content = new byte[(int) entry.getSize()];
761                     sevenZFile.read(content);
762                 }
763                 entry = sevenZFile.getNextEntry();
764             }
765         }
766     }
767 
768     /**
769      * @see "https://issues.apache.org/jira/browse/COMPRESS-348"
770      */
771     @Test
772     public void testReadEntriesOfSize0() throws IOException {
773         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-348.7z")) {
774             int entries = 0;
775             SevenZArchiveEntry entry = sevenZFile.getNextEntry();
776             while (entry != null) {
777                 entries++;
778                 final int b = sevenZFile.read();
779                 if ("2.txt".equals(entry.getName()) || "5.txt".equals(entry.getName())) {
780                     assertEquals(-1, b);
781                 } else {
782                     assertNotEquals(-1, b);
783                 }
784                 entry = sevenZFile.getNextEntry();
785             }
786             assertEquals(5, entries);
787         }
788     }
789 
790     @Test
791     public void testReadingBackDeltaDistance() throws Exception {
792         final File output = newTempFile("delta-distance.7z");
793         try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
794             outArchive.setContentMethods(
795                     Arrays.asList(new SevenZMethodConfiguration(SevenZMethod.DELTA_FILTER, 32), new SevenZMethodConfiguration(SevenZMethod.LZMA2)));
796             final SevenZArchiveEntry entry = new SevenZArchiveEntry();
797             entry.setName("foo.txt");
798             outArchive.putArchiveEntry(entry);
799             outArchive.write(new byte[] { 'A' });
800             outArchive.closeArchiveEntry();
801         }
802 
803         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
804             final SevenZArchiveEntry entry = archive.getNextEntry();
805             final SevenZMethodConfiguration m = entry.getContentMethods().iterator().next();
806             assertEquals(SevenZMethod.DELTA_FILTER, m.getMethod());
807             assertEquals(32, m.getOptions());
808         }
809     }
810 
811     @Test
812     public void testReadingBackLZMA2DictSize() throws Exception {
813         final File output = newTempFile("lzma2-dictsize.7z");
814         try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
815             outArchive.setContentMethods(Arrays.asList(new SevenZMethodConfiguration(SevenZMethod.LZMA2, 1 << 20)));
816             final SevenZArchiveEntry entry = new SevenZArchiveEntry();
817             entry.setName("foo.txt");
818             outArchive.putArchiveEntry(entry);
819             outArchive.write(new byte[] { 'A' });
820             outArchive.closeArchiveEntry();
821         }
822 
823         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
824             final SevenZArchiveEntry entry = archive.getNextEntry();
825             final SevenZMethodConfiguration m = entry.getContentMethods().iterator().next();
826             assertEquals(SevenZMethod.LZMA2, m.getMethod());
827             assertEquals(1 << 20, m.getOptions());
828         }
829     }
830 
831     @Test
832     public void testReadTimesFromFile() throws IOException {
833         try (SevenZFile sevenZFile = getSevenZFile("times.7z")) {
834             SevenZArchiveEntry entry = sevenZFile.getNextEntry();
835             assertNotNull(entry);
836             assertEquals("test", entry.getName());
837             assertTrue(entry.isDirectory());
838             assertDates(entry, "2022-03-21T14:50:46.2099751Z", "2022-03-21T14:50:46.2099751Z", "2022-03-16T10:19:24.1051115Z");
839 
840             entry = sevenZFile.getNextEntry();
841             assertNotNull(entry);
842             assertEquals("test/test-times.txt", entry.getName());
843             assertFalse(entry.isDirectory());
844             assertDates(entry, "2022-03-18T10:00:15Z", "2022-03-18T10:14:37.8130002Z", "2022-03-18T10:14:37.8110032Z");
845 
846             entry = sevenZFile.getNextEntry();
847             assertNotNull(entry);
848             assertEquals("test/test-times2.txt", entry.getName());
849             assertFalse(entry.isDirectory());
850             assertDates(entry, "2022-03-18T10:00:19Z", "2022-03-18T10:14:37.8170038Z", "2022-03-18T10:14:37.8140004Z");
851 
852             entry = sevenZFile.getNextEntry();
853             assertNull(entry);
854         }
855     }
856 
857     @Test
858     public void testRetrieveInputStreamForAllEntriesMultipleTimes() throws IOException {
859         try (SevenZFile sevenZFile = getSevenZFile("bla.7z")) {
860             for (final SevenZArchiveEntry entry : sevenZFile.getEntries()) {
861                 final byte[] firstRead = read(sevenZFile, entry);
862                 final byte[] secondRead = read(sevenZFile, entry);
863                 assertArrayEquals(firstRead, secondRead);
864             }
865         }
866     }
867 
868     @Test
869     public void testRetrieveInputStreamForAllEntriesWithoutCRCMultipleTimes() throws IOException {
870         try (SevenZOutputFile out = new SevenZOutputFile(newTempFile("test.7z"))) {
871             final Path inputFile = Files.createTempFile("SevenZTestTemp", "");
872 
873             final SevenZArchiveEntry entry = out.createArchiveEntry(inputFile.toFile(), "test.txt");
874             out.putArchiveEntry(entry);
875             out.write("Test".getBytes(UTF_8));
876             out.closeArchiveEntry();
877 
878             Files.deleteIfExists(inputFile);
879         }
880 
881         try (SevenZFile sevenZFile = SevenZFile.builder().setFile(newTempFile("test.7z")).get()) {
882             for (final SevenZArchiveEntry entry : sevenZFile.getEntries()) {
883                 final byte[] firstRead = read(sevenZFile, entry);
884                 final byte[] secondRead = read(sevenZFile, entry);
885                 assertArrayEquals(firstRead, secondRead);
886             }
887         }
888     }
889 
890     @Test
891     public void testRetrieveInputStreamForShuffledEntries() throws IOException {
892         try (SevenZFile sevenZFile = getSevenZFile("COMPRESS-348.7z")) {
893             final List<SevenZArchiveEntry> entries = (List<SevenZArchiveEntry>) sevenZFile.getEntries();
894             Collections.shuffle(entries);
895             for (final SevenZArchiveEntry entry : entries) {
896                 read(sevenZFile, entry);
897             }
898         }
899     }
900 
901     @Test
902     public void testSevenZWithEOS() throws IOException {
903         try (SevenZFile sevenZFile = getSevenZFile("lzma-with-eos.7z")) {
904             final List<SevenZArchiveEntry> entries = (List<SevenZArchiveEntry>) sevenZFile.getEntries();
905             for (final SevenZArchiveEntry entry : entries) {
906                 read(sevenZFile, entry);
907             }
908         }
909     }
910 
911     @Test
912     public void testSignatureCheck() {
913         assertTrue(SevenZFile.matches(SevenZFile.sevenZSignature, SevenZFile.sevenZSignature.length));
914         assertTrue(SevenZFile.matches(SevenZFile.sevenZSignature, SevenZFile.sevenZSignature.length + 1));
915         assertFalse(SevenZFile.matches(SevenZFile.sevenZSignature, SevenZFile.sevenZSignature.length - 1));
916         assertFalse(SevenZFile.matches(new byte[] { 1, 2, 3, 4, 5, 6 }, 6));
917         assertTrue(SevenZFile.matches(new byte[] { '7', 'z', (byte) 0xBC, (byte) 0xAF, 0x27, 0x1C }, 6));
918         assertFalse(SevenZFile.matches(new byte[] { '7', 'z', (byte) 0xBC, (byte) 0xAF, 0x27, 0x1D }, 6));
919     }
920 }