View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.harmony.pack200;
20  
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import java.io.BufferedReader;
29  import java.io.File;
30  import java.io.FileInputStream;
31  import java.io.FileNotFoundException;
32  import java.io.FileOutputStream;
33  import java.io.FileReader;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.InputStreamReader;
37  import java.net.URISyntaxException;
38  import java.util.Enumeration;
39  import java.util.jar.JarEntry;
40  import java.util.jar.JarFile;
41  import java.util.jar.JarOutputStream;
42  
43  import org.apache.commons.compress.AbstractTempDirTest;
44  import org.junit.jupiter.api.Test;
45  
46  /**
47   * Test different options for packing a Jar file.
48   */
49  class PackingOptionsTest extends AbstractTempDirTest {
50  
51      private void compareFiles(final JarFile jarFile, final JarFile jarFile2) throws IOException {
52          final Enumeration<JarEntry> entries = jarFile.entries();
53          while (entries.hasMoreElements()) {
54  
55              final JarEntry entry = entries.nextElement();
56              assertNotNull(entry);
57  
58              final String name = entry.getName();
59              final JarEntry entry2 = jarFile2.getJarEntry(name);
60              assertNotNull(entry2, "Missing Entry: " + name);
61              // assertEquals(entry.getTime(), entry2.getTime());
62              if (!name.equals("META-INF/MANIFEST.MF")) {
63                  // Manifests aren't necessarily byte-for-byte identical
64                  final InputStream ours = jarFile.getInputStream(entry);
65                  final InputStream expected = jarFile2.getInputStream(entry2);
66  
67                  try (BufferedReader reader1 = new BufferedReader(new InputStreamReader(ours));
68                          BufferedReader reader2 = new BufferedReader(new InputStreamReader(expected))) {
69                      String line1 = reader1.readLine();
70                      String line2 = reader2.readLine();
71                      int i = 1;
72                      while (line1 != null || line2 != null) {
73                          assertEquals(line2, line1, "Unpacked files differ for " + name + " at line " + i);
74                          line1 = reader1.readLine();
75                          line2 = reader2.readLine();
76                          i++;
77                      }
78                  }
79              }
80          }
81      }
82  
83      private void compareJarEntries(final JarFile jarFile, final JarFile jarFile2) {
84          final Enumeration<JarEntry> entries = jarFile.entries();
85          while (entries.hasMoreElements()) {
86  
87              final JarEntry entry = entries.nextElement();
88              assertNotNull(entry);
89  
90              final String name = entry.getName();
91              final JarEntry entry2 = jarFile2.getJarEntry(name);
92              assertNotNull(entry2, "Missing Entry: " + name);
93          }
94      }
95  
96      @Test
97      void testDeflateHint() {
98          // Test default first
99          final PackingOptions options = new PackingOptions();
100         assertEquals("keep", options.getDeflateHint());
101         options.setDeflateHint("true");
102         assertEquals("true", options.getDeflateHint());
103         options.setDeflateHint("false");
104         assertEquals("false", options.getDeflateHint());
105         assertThrows(IllegalArgumentException.class, () -> options.setDeflateHint("hello"), "Should throw IllegalArgumentException for incorrect deflate hint");
106     }
107 
108     @Test
109     void testErrorAttributes() throws Exception {
110         final File file = createTempFile("unknown", ".pack");
111         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI()));
112                 FileOutputStream out = new FileOutputStream(file)) {
113             final PackingOptions options = new PackingOptions();
114             options.addClassAttributeAction("Pack200", "error");
115             final Archive ar = new Archive(in, out, options);
116             final Error error = assertThrows(Error.class, () -> {
117                 ar.pack();
118                 in.close();
119                 out.close();
120             });
121             assertEquals("Attribute Pack200 was found", error.getMessage());
122         }
123     }
124 
125     @Test
126     void testKeepFileOrder() throws Exception {
127         // Test default first
128         PackingOptions options = new PackingOptions();
129         assertTrue(options.isKeepFileOrder());
130         options.setKeepFileOrder(false);
131         assertFalse(options.isKeepFileOrder());
132 
133         // Test option works correctly. Test 'True'.
134         File file = createTempFile("sql", ".pack");
135         try (JarFile jarFile = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
136                 FileOutputStream outputStream = new FileOutputStream(file)) {
137             options = new PackingOptions();
138             options.setGzip(false);
139             new Archive(jarFile, outputStream, options).pack();
140         }
141 
142         File file2 = createTempFile("sql", ".jar");
143         unpackJar(file, file2);
144 
145         File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
146         try (JarFile jarFile = new JarFile(file2);
147                 JarFile jarFile2 = new JarFile(compareFile)) {
148             // Check that both jars have the same entries in the same order
149             final Enumeration<JarEntry> entries = jarFile.entries();
150             final Enumeration<JarEntry> entries2 = jarFile2.entries();
151             while (entries.hasMoreElements()) {
152                 final JarEntry entry = entries.nextElement();
153                 assertNotNull(entry);
154                 final JarEntry entry2 = entries2.nextElement();
155                 final String name = entry.getName();
156                 final String name2 = entry2.getName();
157                 assertEquals(name, name2);
158             }
159         }
160         // Test 'false'
161         file = createTempFile("sql", ".pack");
162         try (JarFile jarFile = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
163                 FileOutputStream outputStream = new FileOutputStream(file);) {
164             options = new PackingOptions();
165             options.setKeepFileOrder(false);
166             options.setGzip(false);
167             new Archive(jarFile, outputStream, options).pack();
168         }
169 
170         file2 = createTempFile("sql", ".jar");
171         unpackJar(file, file2);
172 
173         compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
174         try (JarFile jarFile = new JarFile(file2);
175                 JarFile jarFile2 = new JarFile(compareFile)) {
176             // Check that both jars have the same entries (may be in a different order)
177             compareJarEntries(jarFile, jarFile2);
178             // Check files are not in order this time
179             final Enumeration<JarEntry> entries = jarFile.entries();
180             final Enumeration<JarEntry> entries2 = jarFile2.entries();
181             boolean inOrder = true;
182             while (entries.hasMoreElements()) {
183                 final JarEntry entry = entries.nextElement();
184                 assertNotNull(entry);
185                 final JarEntry entry2 = entries2.nextElement();
186                 final String name = entry.getName();
187                 final String name2 = entry2.getName();
188                 if (!name.equals(name2)) {
189                     inOrder = false;
190                     break;
191                 }
192             }
193             assertFalse(inOrder, "Files are not expected to be in order");
194         }
195     }
196 
197     // Test verbose, quiet and log file options.
198     @Test
199     void testLoggingOptions() throws Exception {
200         // Test defaults
201         final PackingOptions options = new PackingOptions();
202         assertFalse(options.isVerbose());
203         assertNull(options.getLogFile());
204         options.setVerbose(true);
205         assertTrue(options.isVerbose());
206         options.setQuiet(true);
207         assertFalse(options.isVerbose());
208 
209         final File logFile = createTempFile("logfile", ".txt");
210         options.setLogFile(logFile.getPath());
211         assertEquals(logFile.getPath(), options.getLogFile());
212 
213         File file = createTempFile("helloworld", ".pack.gz");
214         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
215                 FileOutputStream out = new FileOutputStream(file)) {
216             new Archive(in, out, options).pack();
217         }
218 
219         // log file should be empty
220         try (FileReader reader = new FileReader(logFile)) {
221             assertFalse(reader.ready());
222         }
223 
224         options.setVerbose(true);
225         file = createTempFile("helloworld", ".pack.gz");
226         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
227                 FileOutputStream out = new FileOutputStream(file)) {
228             new Archive(in, out, options).pack();
229         }
230 
231         // log file should not be empty
232         try (FileReader reader = new FileReader(logFile)) {
233             assertTrue(reader.ready());
234         }
235         PackingUtils.config(null);
236     }
237 
238     @Test
239     void testModificationTime() throws Exception {
240         // Test default first
241         PackingOptions options = new PackingOptions();
242         assertEquals("keep", options.getModificationTime());
243         options.setModificationTime("latest");
244         assertEquals("latest", options.getModificationTime());
245         assertThrows(IllegalArgumentException.class, () -> {
246             final PackingOptions illegalOption = new PackingOptions();
247             illegalOption.setModificationTime("true");
248         }, "Should throw IllegalArgumentException for incorrect mod time");
249 
250         // Test option works correctly. Test 'keep'.
251         File file = createTempFile("sql", ".pack");
252         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
253                 FileOutputStream out = new FileOutputStream(file)) {
254             options = new PackingOptions();
255             options.setGzip(false);
256             new Archive(in, out, options).pack();
257         }
258 
259         File file2 = createTempFile("sql", ".jar");
260         unpackJar(file, file2);
261 
262         File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
263         try (JarFile jarFile = new JarFile(file2);
264                 JarFile jarFile2 = new JarFile(compareFile)) {
265             // Check that both jars have the same entries in the same order
266             final Enumeration<JarEntry> entries = jarFile.entries();
267             final Enumeration<JarEntry> entries2 = jarFile2.entries();
268             while (entries.hasMoreElements()) {
269 
270                 final JarEntry entry = entries.nextElement();
271                 assertNotNull(entry);
272                 final JarEntry entry2 = entries2.nextElement();
273                 final String name = entry.getName();
274                 final String name2 = entry2.getName();
275                 assertEquals(name, name2);
276                 assertEquals(entry.getTime(), entry2.getTime());
277             }
278         }
279         // Test option works correctly. Test 'latest'.
280         file = createTempFile("sql", ".pack");
281         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
282                 FileOutputStream out = new FileOutputStream(file)) {
283             options = new PackingOptions();
284             options.setGzip(false);
285             options.setModificationTime("latest");
286             new Archive(in, out, options).pack();
287         }
288 
289         file2 = createTempFile("sql", ".jar");
290         unpackJar(file, file2);
291 
292         compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
293         try (JarFile jarFile = new JarFile(file2);
294                 JarFile jarFile2 = new JarFile(compareFile)) {
295             // Check that all mod times are the same and some are not the same as the original
296             final Enumeration<JarEntry> entries = jarFile.entries();
297             final Enumeration<JarEntry> entries2 = jarFile2.entries();
298             long modtime = -1;
299             boolean sameAsOriginal = true;
300             while (entries.hasMoreElements()) {
301                 final JarEntry entry = entries.nextElement();
302                 assertNotNull(entry);
303                 final JarEntry entry2 = entries2.nextElement();
304                 final String name = entry.getName();
305                 if (!name.startsWith("META-INF")) {
306                     if (modtime == -1) {
307                         modtime = entry.getTime();
308                     } else {
309                         assertEquals(modtime, entry.getTime());
310                     }
311                 }
312                 if (entry2.getTime() != entry.getTime()) {
313                     sameAsOriginal = false;
314                 }
315             }
316             assertFalse(sameAsOriginal, "Some modtimes should have changed");
317         }
318     }
319 
320     @Test
321     void testNewAttributes() throws Exception {
322         final File file = createTempFile("unknown", ".pack");
323         try (FileOutputStream out = new FileOutputStream(file);
324                 JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI()))) {
325             final PackingOptions options = new PackingOptions();
326             options.addClassAttributeAction("Pack200", "I");
327             new Archive(in, out, options).pack();
328         }
329 
330         // unpack and check this was done right
331         final File file2 = createTempFile("unknown", ".jar");
332         unpackJar(file, file2);
333         // compare with original
334         final File compareFile = new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI());
335         try (JarFile jarFile = new JarFile(file2);
336                 JarFile jarFile2 = new JarFile(compareFile)) {
337             assertEquals(jarFile2.size(), jarFile.size());
338             compareJarEntries(jarFile, jarFile2);
339 //        compareFiles(jarFile, jarFile2);
340         }
341     }
342 
343     @Test
344     void testNewAttributes2() throws Exception {
345         final File file = createTempFile("unknown", ".pack");
346         try (FileOutputStream out = new FileOutputStream(file);
347                 JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/p200WithUnknownAttributes.jar").toURI()))) {
348             final PackingOptions options = new PackingOptions();
349             options.addFieldAttributeAction("Pack200", "I");
350             options.addMethodAttributeAction("Pack200", "I");
351             options.addCodeAttributeAction("Pack200", "I");
352             final Archive ar = new Archive(in, out, options);
353             ar.pack();
354         }
355         // unpack and check this was done right
356         final File file2 = createTempFile("unknown", ".jar");
357         unpackJar(file, file2);
358 
359         // compare with original
360         final File compareFile = new File(Archive.class.getResource("/pack200/p200WithUnknownAttributes.jar").toURI());
361         try (JarFile jarFile = new JarFile(file2);
362                 JarFile jarFile2 = new JarFile(compareFile)) {
363             assertEquals(jarFile2.size(), jarFile.size());
364             compareJarEntries(jarFile, jarFile2);
365         }
366     }
367 
368     @Test
369     void testPackEffort0() throws Pack200Exception, IOException, URISyntaxException {
370         final File f1 = new File(Archive.class.getResource("/pack200/jndi.jar").toURI());
371         final File file = createTempFile("jndiE0", ".pack");
372         try (JarFile in = new JarFile(f1);
373                 FileOutputStream out = new FileOutputStream(file)) {
374             final PackingOptions options = new PackingOptions();
375             options.setGzip(false);
376             options.setEffort(0);
377             new Archive(in, out, options).pack();
378         }
379         try (JarFile jf1 = new JarFile(f1);
380                 JarFile jf2 = new JarFile(file)) {
381             compareFiles(jf1, jf2);
382         }
383     }
384 
385     @Test
386     void testPassAttributes() throws Exception {
387         final File file = createTempFile("unknown", ".pack");
388         try (FileOutputStream out = new FileOutputStream(file);
389                 JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI()))) {
390             final PackingOptions options = new PackingOptions();
391             options.addClassAttributeAction("Pack200", "pass");
392             new Archive(in, out, options).pack();
393         }
394 
395         // now unpack
396         final File file2 = createTempFile("unknown", ".jar");
397         unpackJar(file, file2);
398 
399         // compare with original
400         final File compareFile = new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI());
401         try (JarFile jarFile = new JarFile(file2);
402                 JarFile jarFile2 = new JarFile(compareFile)) {
403             assertEquals(jarFile2.size(), jarFile.size());
404             compareJarEntries(jarFile, jarFile2);
405         }
406     }
407 
408     @Test
409     void testPassFiles() throws IOException, URISyntaxException, Pack200Exception {
410         // Don't pass any
411         final File file0 = createTempFile("sql", ".pack");
412         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
413                 FileOutputStream out = new FileOutputStream(file0)) {
414             final PackingOptions options = new PackingOptions();
415             options.setGzip(false);
416             new Archive(in, out, options).pack();
417         }
418 
419         // Pass one file
420         final File file = createTempFile("sql", ".pack");
421         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
422                 FileOutputStream out = new FileOutputStream(file)) {
423             final PackingOptions options = new PackingOptions();
424             options.setGzip(false);
425             options.addPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class");
426             assertTrue(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class"));
427             new Archive(in, out, options).pack();
428         }
429 
430         // Pass a whole directory
431         final File file2 = createTempFile("sql", ".pack");
432         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
433                 FileOutputStream out = new FileOutputStream(file2)) {
434             final PackingOptions options = new PackingOptions();
435             options.setGzip(false);
436             options.addPassFile("bin/test/org/apache/harmony/sql/tests/java/sql");
437             assertTrue(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class"));
438             assertFalse(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sqldata/SqlData.class"));
439             new Archive(in, out, options).pack();
440         }
441 
442         assertTrue(file.length() > file0.length(), "If files are passed then the pack file should be larger");
443         assertTrue(file2.length() > file.length(), "If more files are passed then the pack file should be larger");
444 
445         // now unpack
446         final File file3 = createTempFile("sql", ".jar");
447         unpackJar(file, file3);
448 
449         final File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
450         try (JarFile jarFile = new JarFile(file3);
451                 JarFile jarFile2 = new JarFile(compareFile)) {
452             // Check that both jars have the same entries
453             compareJarEntries(jarFile, jarFile2);
454         }
455         // now unpack the file with lots of passed files
456         final File file4 = createTempFile("sql", ".jar");
457         unpackJar(file2, file4);
458 
459         try (JarFile jarFile = new JarFile(file4);
460                 JarFile jarFile2 = new JarFile(compareFile)) {
461             compareJarEntries(jarFile, jarFile2);
462         }
463     }
464 
465     @Test
466     void testSegmentLimits() throws IOException, Pack200Exception, URISyntaxException {
467         File file = createTempFile("helloworld", ".pack.gz");
468         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
469                 FileOutputStream out = new FileOutputStream(file)) {
470             final PackingOptions options = new PackingOptions();
471             options.setSegmentLimit(0);
472             final Archive archive = new Archive(in, out, options);
473             archive.pack();
474         }
475 
476         file = createTempFile("helloworld", ".pack.gz");
477         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
478                 FileOutputStream out = new FileOutputStream(file)) {
479             final PackingOptions options = new PackingOptions();
480             options.setSegmentLimit(-1);
481             new Archive(in, out, options).pack();
482         }
483 
484         file = createTempFile("helloworld", ".pack.gz");
485         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
486                 FileOutputStream out = new FileOutputStream(file)) {
487             final PackingOptions options = new PackingOptions();
488             options.setSegmentLimit(5000);
489             new Archive(in, out, options).pack();
490         }
491     }
492 
493     @Test
494     void testStripDebug() throws IOException, Pack200Exception, URISyntaxException {
495         final File file = createTempFile("sql", ".pack");
496         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
497                 FileOutputStream out = new FileOutputStream(file)) {
498             final PackingOptions options = new PackingOptions();
499             options.setGzip(false);
500             options.setStripDebug(true);
501             final Archive archive = new Archive(in, out, options);
502             archive.pack();
503         }
504 
505         // now unpack
506         final File file2 = createTempFile("sqloutNoDebug", ".jar");
507         unpackJar(file, file2);
508 
509         final File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpackedNoDebug.jar").toURI());
510         try (JarFile jarFile = new JarFile(file2);
511                 JarFile jarFile2 = new JarFile(compareFile)) {
512             assertTrue(file2.length() < 250000);
513             compareFiles(jarFile, jarFile2);
514         }
515     }
516 
517     // void testE0again() throws IOException, Pack200Exception,
518     // URISyntaxException {
519     // JarInputStream inputStream = new
520     // JarInputStream(Archive.class.getResourceAsStream("/pack200/jndi.jar"));
521     // file = File.createTempFile("jndiE0", ".pack");
522     // out = new FileOutputStream(file);
523     // Archive archive = new Archive(inputStream, out, false);
524     // archive.setEffort(0);
525     // archive.pack();
526     // inputStream.close();
527     // out.close();
528     // in = new JarFile(new File(Archive.class.getResource(
529     // "/pack200/jndi.jar").toURI()));
530     // compareFiles(in, new JarFile(file));
531     // }
532 
533     private void unpack(final InputStream inputStream, final JarOutputStream outputStream) throws Pack200Exception, IOException {
534         new org.apache.commons.compress.harmony.unpack200.Archive(inputStream, outputStream).unpack();
535     }
536 
537     private void unpackJar(final File sourceFile, final File destFile) throws Pack200Exception, IOException, FileNotFoundException {
538         try (InputStream in2 = new FileInputStream(sourceFile);
539                 JarOutputStream out2 = new JarOutputStream(new FileOutputStream(destFile))) {
540             unpack(in2, out2);
541         }
542     }
543 
544 }