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   */
18  
19  package org.apache.commons.compress.archivers.tar;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertNotNull;
24  import static org.junit.Assert.assertNull;
25  import static org.junit.Assert.assertTrue;
26  import static org.junit.Assert.fail;
27  
28  import java.io.ByteArrayInputStream;
29  import java.io.ByteArrayOutputStream;
30  import java.io.File;
31  import java.io.FileInputStream;
32  import java.io.FileOutputStream;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.InputStreamReader;
36  import java.io.Reader;
37  import java.security.MessageDigest;
38  import java.util.Calendar;
39  import java.util.Date;
40  import java.util.HashMap;
41  import java.util.Map;
42  import java.util.TimeZone;
43  import org.apache.commons.compress.AbstractTestCase;
44  import org.apache.commons.compress.archivers.ArchiveEntry;
45  import org.apache.commons.compress.archivers.ArchiveOutputStream;
46  import org.apache.commons.compress.archivers.ArchiveStreamFactory;
47  import org.apache.commons.compress.utils.CharsetNames;
48  import org.apache.commons.compress.utils.IOUtils;
49  import org.junit.Assert;
50  import org.junit.Test;
51  
52  public class TarArchiveOutputStreamTest extends AbstractTestCase {
53  
54      @Test
55      public void testCount() throws Exception {
56          final File f = File.createTempFile("commons-compress-tarcount", ".tar");
57          f.deleteOnExit();
58          final FileOutputStream fos = new FileOutputStream(f);
59  
60          final ArchiveOutputStream tarOut = new ArchiveStreamFactory()
61              .createArchiveOutputStream(ArchiveStreamFactory.TAR, fos);
62  
63          final File file1 = getFile("test1.xml");
64          final TarArchiveEntry sEntry = new TarArchiveEntry(file1, file1.getName());
65          tarOut.putArchiveEntry(sEntry);
66  
67          final FileInputStream in = new FileInputStream(file1);
68          final byte[] buf = new byte[8192];
69  
70          int read = 0;
71          while ((read = in.read(buf)) > 0) {
72              tarOut.write(buf, 0, read);
73          }
74  
75          in.close();
76          tarOut.closeArchiveEntry();
77          tarOut.close();
78  
79          assertEquals(f.length(), tarOut.getBytesWritten());
80      }
81  
82      @Test
83      public void testMaxFileSizeError() throws Exception {
84          final TarArchiveEntry t = new TarArchiveEntry("foo");
85          t.setSize(077777777777L);
86          TarArchiveOutputStream tos =
87              new TarArchiveOutputStream(new ByteArrayOutputStream());
88          tos.putArchiveEntry(t);
89          t.setSize(0100000000000L);
90          tos = new TarArchiveOutputStream(new ByteArrayOutputStream());
91          try {
92              tos.putArchiveEntry(t);
93              fail("Should have generated RuntimeException");
94          } catch (final RuntimeException expected) {
95          }
96      }
97  
98      @Test
99      public void testBigNumberStarMode() throws Exception {
100         final TarArchiveEntry t = new TarArchiveEntry("foo");
101         t.setSize(0100000000000L);
102         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
103         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
104         tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
105         tos.putArchiveEntry(t);
106         // make sure header is written to byte array
107         tos.write(new byte[10 * 1024]);
108         final byte[] data = bos.toByteArray();
109         assertEquals(0x80,
110             data[TarConstants.NAMELEN
111                 + TarConstants.MODELEN
112                 + TarConstants.UIDLEN
113                 + TarConstants.GIDLEN] & 0x80);
114         final TarArchiveInputStream tin =
115             new TarArchiveInputStream(new ByteArrayInputStream(data));
116         final TarArchiveEntry e = tin.getNextTarEntry();
117         assertEquals(0100000000000L, e.getSize());
118         tin.close();
119         // generates IOE because of unclosed entries.
120         // However we don't really want to create such large entries.
121         closeQuietly(tos);
122     }
123 
124     @Test
125     public void testBigNumberPosixMode() throws Exception {
126         final TarArchiveEntry t = new TarArchiveEntry("foo");
127         t.setSize(0100000000000L);
128         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
129         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
130         tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
131         tos.putArchiveEntry(t);
132         // make sure header is written to byte array
133         tos.write(new byte[10 * 1024]);
134         final byte[] data = bos.toByteArray();
135         assertEquals("00000000000 ",
136             new String(data,
137                 1024 + TarConstants.NAMELEN
138                     + TarConstants.MODELEN
139                     + TarConstants.UIDLEN
140                     + TarConstants.GIDLEN, 12,
141                 CharsetNames.UTF_8));
142         final TarArchiveInputStream tin =
143             new TarArchiveInputStream(new ByteArrayInputStream(data));
144         final TarArchiveEntry e = tin.getNextTarEntry();
145         assertEquals(0100000000000L, e.getSize());
146         tin.close();
147         // generates IOE because of unclosed entries.
148         // However we don't really want to create such large entries.
149         closeQuietly(tos);
150     }
151 
152     @Test
153     public void testWriteSimplePaxHeaders() throws Exception {
154         final Map<String, String> m = new HashMap<>();
155         m.put("a", "b");
156         final byte[] data = writePaxHeader(m);
157         assertEquals("00000000006 ",
158             new String(data, TarConstants.NAMELEN
159                 + TarConstants.MODELEN
160                 + TarConstants.UIDLEN
161                 + TarConstants.GIDLEN, 12,
162                 CharsetNames.UTF_8));
163         assertEquals("6 a=b\n", new String(data, 512, 6, CharsetNames.UTF_8));
164     }
165 
166     @Test
167     public void testPaxHeadersWithLength99() throws Exception {
168         final Map<String, String> m = new HashMap<>();
169         m.put("a",
170             "0123456789012345678901234567890123456789"
171                 + "01234567890123456789012345678901234567890123456789"
172                 + "012");
173         final byte[] data = writePaxHeader(m);
174         assertEquals("00000000143 ",
175             new String(data, TarConstants.NAMELEN
176                 + TarConstants.MODELEN
177                 + TarConstants.UIDLEN
178                 + TarConstants.GIDLEN, 12,
179                 CharsetNames.UTF_8));
180         assertEquals("99 a=0123456789012345678901234567890123456789"
181             + "01234567890123456789012345678901234567890123456789"
182             + "012\n", new String(data, 512, 99, CharsetNames.UTF_8));
183     }
184 
185     @Test
186     public void testPaxHeadersWithLength101() throws Exception {
187         final Map<String, String> m = new HashMap<>();
188         m.put("a",
189             "0123456789012345678901234567890123456789"
190                 + "01234567890123456789012345678901234567890123456789"
191                 + "0123");
192         final byte[] data = writePaxHeader(m);
193         assertEquals("00000000145 ",
194             new String(data, TarConstants.NAMELEN
195                 + TarConstants.MODELEN
196                 + TarConstants.UIDLEN
197                 + TarConstants.GIDLEN, 12,
198                 CharsetNames.UTF_8));
199         assertEquals("101 a=0123456789012345678901234567890123456789"
200             + "01234567890123456789012345678901234567890123456789"
201             + "0123\n", new String(data, 512, 101, CharsetNames.UTF_8));
202     }
203 
204     private byte[] writePaxHeader(final Map<String, String> m) throws Exception {
205         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
206         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
207         tos.writePaxHeaders(new TarArchiveEntry("x"), "foo", m);
208 
209         // add a dummy entry so data gets written
210         final TarArchiveEntry t = new TarArchiveEntry("foo");
211         t.setSize(10 * 1024);
212         tos.putArchiveEntry(t);
213         tos.write(new byte[10 * 1024]);
214         tos.closeArchiveEntry();
215         tos.close();
216 
217         return bos.toByteArray();
218     }
219 
220     @Test
221     public void testWriteLongFileNamePosixMode() throws Exception {
222         final String n = "01234567890123456789012345678901234567890123456789"
223             + "01234567890123456789012345678901234567890123456789"
224             + "01234567890123456789012345678901234567890123456789";
225         final TarArchiveEntry t =
226             new TarArchiveEntry(n);
227         t.setSize(10 * 1024);
228         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
229         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
230         tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
231         tos.putArchiveEntry(t);
232         tos.write(new byte[10 * 1024]);
233         tos.closeArchiveEntry();
234         final byte[] data = bos.toByteArray();
235         assertEquals("160 path=" + n + "\n",
236             new String(data, 512, 160, CharsetNames.UTF_8));
237         final TarArchiveInputStream tin =
238             new TarArchiveInputStream(new ByteArrayInputStream(data));
239         final TarArchiveEntry e = tin.getNextTarEntry();
240         assertEquals(n, e.getName());
241         tin.close();
242         tos.close();
243     }
244 
245     @Test
246     public void testOldEntryStarMode() throws Exception {
247         final TarArchiveEntry t = new TarArchiveEntry("foo");
248         t.setSize(Integer.MAX_VALUE);
249         t.setModTime(-1000);
250         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
251         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
252         tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
253         tos.putArchiveEntry(t);
254         // make sure header is written to byte array
255         tos.write(new byte[10 * 1024]);
256         final byte[] data = bos.toByteArray();
257         assertEquals((byte) 0xff,
258             data[TarConstants.NAMELEN
259                 + TarConstants.MODELEN
260                 + TarConstants.UIDLEN
261                 + TarConstants.GIDLEN
262                 + TarConstants.SIZELEN]);
263         final TarArchiveInputStream tin =
264             new TarArchiveInputStream(new ByteArrayInputStream(data));
265         final TarArchiveEntry e = tin.getNextTarEntry();
266         final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
267         cal.set(1969, 11, 31, 23, 59, 59);
268         cal.set(Calendar.MILLISECOND, 0);
269         assertEquals(cal.getTime(), e.getLastModifiedDate());
270         tin.close();
271         // generates IOE because of unclosed entries.
272         // However we don't really want to create such large entries.
273         closeQuietly(tos);
274     }
275 
276     @Test
277     public void testOldEntryPosixMode() throws Exception {
278         final TarArchiveEntry t = new TarArchiveEntry("foo");
279         t.setSize(Integer.MAX_VALUE);
280         t.setModTime(-1000);
281         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
282         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
283         tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
284         tos.putArchiveEntry(t);
285         // make sure header is written to byte array
286         tos.write(new byte[10 * 1024]);
287         final byte[] data = bos.toByteArray();
288         assertEquals("00000000000 ",
289             new String(data,
290                 1024 + TarConstants.NAMELEN
291                     + TarConstants.MODELEN
292                     + TarConstants.UIDLEN
293                     + TarConstants.GIDLEN
294                     + TarConstants.SIZELEN, 12,
295                 CharsetNames.UTF_8));
296         final TarArchiveInputStream tin =
297             new TarArchiveInputStream(new ByteArrayInputStream(data));
298         final TarArchiveEntry e = tin.getNextTarEntry();
299         final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
300         cal.set(1969, 11, 31, 23, 59, 59);
301         cal.set(Calendar.MILLISECOND, 0);
302         assertEquals(cal.getTime(), e.getLastModifiedDate());
303         tin.close();
304         // generates IOE because of unclosed entries.
305         // However we don't really want to create such large entries.
306         closeQuietly(tos);
307     }
308 
309     @Test
310     public void testOldEntryError() throws Exception {
311         final TarArchiveEntry t = new TarArchiveEntry("foo");
312         t.setSize(Integer.MAX_VALUE);
313         t.setModTime(-1000);
314         final TarArchiveOutputStream tos =
315             new TarArchiveOutputStream(new ByteArrayOutputStream());
316         try {
317             tos.putArchiveEntry(t);
318             fail("Should have generated RuntimeException");
319         } catch (final RuntimeException expected) {
320         }
321         tos.close();
322     }
323 
324     @Test
325     public void testWriteNonAsciiPathNamePaxHeader() throws Exception {
326         final String n = "\u00e4";
327         final TarArchiveEntry t = new TarArchiveEntry(n);
328         t.setSize(10 * 1024);
329         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
330         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
331         tos.setAddPaxHeadersForNonAsciiNames(true);
332         tos.putArchiveEntry(t);
333         tos.write(new byte[10 * 1024]);
334         tos.closeArchiveEntry();
335         tos.close();
336         final byte[] data = bos.toByteArray();
337         assertEquals("11 path=" + n + "\n",
338             new String(data, 512, 11, CharsetNames.UTF_8));
339         final TarArchiveInputStream tin =
340             new TarArchiveInputStream(new ByteArrayInputStream(data));
341         final TarArchiveEntry e = tin.getNextTarEntry();
342         assertEquals(n, e.getName());
343         tin.close();
344     }
345 
346     @Test
347     public void testWriteNonAsciiLinkPathNamePaxHeader() throws Exception {
348         final String n = "\u00e4";
349         final TarArchiveEntry t = new TarArchiveEntry("a", TarConstants.LF_LINK);
350         t.setSize(10 * 1024);
351         t.setLinkName(n);
352         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
353         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
354         tos.setAddPaxHeadersForNonAsciiNames(true);
355         tos.putArchiveEntry(t);
356         tos.write(new byte[10 * 1024]);
357         tos.closeArchiveEntry();
358         tos.close();
359         final byte[] data = bos.toByteArray();
360         assertEquals("15 linkpath=" + n + "\n",
361             new String(data, 512, 15, CharsetNames.UTF_8));
362         final TarArchiveInputStream tin =
363             new TarArchiveInputStream(new ByteArrayInputStream(data));
364         final TarArchiveEntry e = tin.getNextTarEntry();
365         assertEquals(n, e.getLinkName());
366         tin.close();
367     }
368 
369     /**
370      * @see "https://issues.apache.org/jira/browse/COMPRESS-200"
371      */
372     @Test
373     public void testRoundtripWith67CharFileNameGnu() throws Exception {
374         testRoundtripWith67CharFileName(TarArchiveOutputStream.LONGFILE_GNU);
375     }
376 
377     /**
378      * @see "https://issues.apache.org/jira/browse/COMPRESS-200"
379      */
380     @Test
381     public void testRoundtripWith67CharFileNamePosix() throws Exception {
382         testRoundtripWith67CharFileName(TarArchiveOutputStream.LONGFILE_POSIX);
383     }
384 
385     private void testRoundtripWith67CharFileName(final int mode) throws Exception {
386         final String n = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
387             + "AAAAAAA";
388         assertEquals(67, n.length());
389         final TarArchiveEntry t = new TarArchiveEntry(n);
390         t.setSize(10 * 1024);
391         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
392         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
393         tos.setLongFileMode(mode);
394         tos.putArchiveEntry(t);
395         tos.write(new byte[10 * 1024]);
396         tos.closeArchiveEntry();
397         tos.close();
398         final byte[] data = bos.toByteArray();
399         final TarArchiveInputStream tin =
400             new TarArchiveInputStream(new ByteArrayInputStream(data));
401         final TarArchiveEntry e = tin.getNextTarEntry();
402         assertEquals(n, e.getName());
403         tin.close();
404     }
405 
406     @Test
407     public void testWriteLongDirectoryNameErrorMode() throws Exception {
408         final String n = "01234567890123456789012345678901234567890123456789"
409             + "01234567890123456789012345678901234567890123456789"
410             + "01234567890123456789012345678901234567890123456789/";
411 
412         try {
413             final TarArchiveEntry t = new TarArchiveEntry(n);
414             final ByteArrayOutputStream bos = new ByteArrayOutputStream();
415             final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
416             tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_ERROR);
417             tos.putArchiveEntry(t);
418             tos.closeArchiveEntry();
419             tos.close();
420 
421             fail("Truncated name didn't throw an exception");
422         } catch (final RuntimeException e) {
423             // expected
424         }
425     }
426 
427     @Test
428     public void testWriteLongDirectoryNameTruncateMode() throws Exception {
429         final String n = "01234567890123456789012345678901234567890123456789"
430             + "01234567890123456789012345678901234567890123456789"
431             + "01234567890123456789012345678901234567890123456789/";
432         final TarArchiveEntry t = new TarArchiveEntry(n);
433         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
434         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
435         tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE);
436         tos.putArchiveEntry(t);
437         tos.closeArchiveEntry();
438         tos.close();
439         final byte[] data = bos.toByteArray();
440         final TarArchiveInputStream tin =
441             new TarArchiveInputStream(new ByteArrayInputStream(data));
442         final TarArchiveEntry e = tin.getNextTarEntry();
443         assertEquals("Entry name", n.substring(0, TarConstants.NAMELEN) + "/", e.getName());
444         assertTrue("The entry is not a directory", e.isDirectory());
445         tin.close();
446     }
447 
448     /**
449      * @see "https://issues.apache.org/jira/browse/COMPRESS-203"
450      */
451     @Test
452     public void testWriteLongDirectoryNameGnuMode() throws Exception {
453         testWriteLongDirectoryName(TarArchiveOutputStream.LONGFILE_GNU);
454     }
455 
456     /**
457      * @see "https://issues.apache.org/jira/browse/COMPRESS-203"
458      */
459     @Test
460     public void testWriteLongDirectoryNamePosixMode() throws Exception {
461         testWriteLongDirectoryName(TarArchiveOutputStream.LONGFILE_POSIX);
462     }
463 
464     private void testWriteLongDirectoryName(final int mode) throws Exception {
465         final String n = "01234567890123456789012345678901234567890123456789"
466             + "01234567890123456789012345678901234567890123456789"
467             + "01234567890123456789012345678901234567890123456789/";
468         final TarArchiveEntry t = new TarArchiveEntry(n);
469         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
470         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
471         tos.setLongFileMode(mode);
472         tos.putArchiveEntry(t);
473         tos.closeArchiveEntry();
474         tos.close();
475         final byte[] data = bos.toByteArray();
476         final TarArchiveInputStream tin =
477             new TarArchiveInputStream(new ByteArrayInputStream(data));
478         final TarArchiveEntry e = tin.getNextTarEntry();
479         assertEquals(n, e.getName());
480         assertTrue(e.isDirectory());
481         tin.close();
482     }
483 
484     /**
485      * @see "https://issues.apache.org/jira/browse/COMPRESS-203"
486      */
487     @Test
488     public void testWriteNonAsciiDirectoryNamePosixMode() throws Exception {
489         final String n = "f\u00f6\u00f6/";
490         final TarArchiveEntry t = new TarArchiveEntry(n);
491         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
492         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
493         tos.setAddPaxHeadersForNonAsciiNames(true);
494         tos.putArchiveEntry(t);
495         tos.closeArchiveEntry();
496         tos.close();
497         final byte[] data = bos.toByteArray();
498         final TarArchiveInputStream tin =
499             new TarArchiveInputStream(new ByteArrayInputStream(data));
500         final TarArchiveEntry e = tin.getNextTarEntry();
501         assertEquals(n, e.getName());
502         assertTrue(e.isDirectory());
503         tin.close();
504     }
505 
506     /**
507      * @see "https://issues.apache.org/jira/browse/COMPRESS-265"
508      */
509     @Test
510     public void testWriteNonAsciiNameWithUnfortunateNamePosixMode() throws Exception {
511         final String n = "f\u00f6\u00f6\u00dc";
512         final TarArchiveEntry t = new TarArchiveEntry(n);
513         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
514         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
515         tos.setAddPaxHeadersForNonAsciiNames(true);
516         tos.putArchiveEntry(t);
517         tos.closeArchiveEntry();
518         tos.close();
519         final byte[] data = bos.toByteArray();
520         final TarArchiveInputStream tin =
521             new TarArchiveInputStream(new ByteArrayInputStream(data));
522         final TarArchiveEntry e = tin.getNextTarEntry();
523         assertEquals(n, e.getName());
524         assertFalse(e.isDirectory());
525         tin.close();
526     }
527 
528     /**
529      * @see "https://issues.apache.org/jira/browse/COMPRESS-237"
530      */
531     @Test
532     public void testWriteLongLinkNameErrorMode() throws Exception {
533         final String linkname = "01234567890123456789012345678901234567890123456789"
534             + "01234567890123456789012345678901234567890123456789"
535             + "01234567890123456789012345678901234567890123456789/test";
536         final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK);
537         entry.setLinkName(linkname);
538 
539         try {
540             final ByteArrayOutputStream bos = new ByteArrayOutputStream();
541             final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
542             tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_ERROR);
543             tos.putArchiveEntry(entry);
544             tos.closeArchiveEntry();
545             tos.close();
546 
547             fail("Truncated link name didn't throw an exception");
548         } catch (final RuntimeException e) {
549             // expected
550         }
551     }
552 
553     @Test
554     public void testWriteLongLinkNameTruncateMode() throws Exception {
555         final String linkname = "01234567890123456789012345678901234567890123456789"
556             + "01234567890123456789012345678901234567890123456789"
557             + "01234567890123456789012345678901234567890123456789/";
558         final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK);
559         entry.setLinkName(linkname);
560 
561         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
562         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
563         tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE);
564         tos.putArchiveEntry(entry);
565         tos.closeArchiveEntry();
566         tos.close();
567 
568         final byte[] data = bos.toByteArray();
569         final TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data));
570         final TarArchiveEntry e = tin.getNextTarEntry();
571         assertEquals("Link name", linkname.substring(0, TarConstants.NAMELEN), e.getLinkName());
572         tin.close();
573     }
574 
575     /**
576      * @see "https://issues.apache.org/jira/browse/COMPRESS-237"
577      */
578     @Test
579     public void testWriteLongLinkNameGnuMode() throws Exception {
580         testWriteLongLinkName(TarArchiveOutputStream.LONGFILE_GNU);
581     }
582 
583     /**
584      * @see "https://issues.apache.org/jira/browse/COMPRESS-237"
585      */
586     @Test
587     public void testWriteLongLinkNamePosixMode() throws Exception {
588         testWriteLongLinkName(TarArchiveOutputStream.LONGFILE_POSIX);
589     }
590 
591     /**
592      * @see "https://issues.apache.org/jira/browse/COMPRESS-237"
593      */
594     private void testWriteLongLinkName(final int mode) throws Exception {
595         final String linkname = "01234567890123456789012345678901234567890123456789"
596             + "01234567890123456789012345678901234567890123456789"
597             + "01234567890123456789012345678901234567890123456789/test";
598         final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK);
599         entry.setLinkName(linkname);
600 
601         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
602         final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
603         tos.setLongFileMode(mode);
604         tos.putArchiveEntry(entry);
605         tos.closeArchiveEntry();
606         tos.close();
607 
608         final byte[] data = bos.toByteArray();
609         final TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data));
610         final TarArchiveEntry e = tin.getNextTarEntry();
611         assertEquals("Entry name", "test", e.getName());
612         assertEquals("Link name", linkname, e.getLinkName());
613         assertTrue("The entry is not a symbolic link", e.isSymbolicLink());
614         tin.close();
615     }
616 
617     @SuppressWarnings("deprecation")
618     @Test public void testRecordSize() throws IOException {
619         try {
620             TarArchiveOutputStream tos =
621                 new TarArchiveOutputStream(new ByteArrayOutputStream(),512,511);
622             fail("should have rejected recordSize of 511");
623         } catch(IllegalArgumentException e) {
624             // expected;
625         }
626         try {
627             TarArchiveOutputStream tos =
628                 new TarArchiveOutputStream(new ByteArrayOutputStream(),512,511,null);
629             fail("should have rejected recordSize of 511");
630         } catch(IllegalArgumentException e) {
631             // expected;
632         }
633         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(new ByteArrayOutputStream(),
634             512, 512)) {
635             assertEquals("recordSize",512,tos.getRecordSize());
636         }
637         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(new ByteArrayOutputStream(),
638             512, 512, null)) {
639             assertEquals("recordSize",512,tos.getRecordSize());
640         }
641     }
642     @Test
643     public void testBlockSizes() throws Exception {
644         String fileName = "/test1.xml";
645         byte[] contents = getResourceContents(fileName);
646         testPadding(TarConstants.DEFAULT_BLKSIZE, fileName, contents); // USTAR / pre-pax
647         testPadding(5120, fileName, contents); // PAX default
648         testPadding(1<<15, fileName, contents); //PAX max
649         testPadding(-2, fileName, contents);    // don't specify a block size -> use minimum length
650         try {
651             testPadding(511, fileName, contents);    // don't specify a block size -> use minimum length
652             fail("should have thrown an illegal argument exception");
653         } catch (IllegalArgumentException e) {
654             //expected
655        }
656         try {
657             testPadding(0, fileName, contents);    // don't specify a block size -> use minimum length
658             fail("should have thrown an illegal argument exception");
659         } catch (IllegalArgumentException e) {
660             //expected
661        }
662     }
663 
664     private void testPadding(int blockSize, String fileName, byte[] contents) throws IOException {
665         final File f = File.createTempFile("commons-compress-padding", ".tar");
666         f.deleteOnExit();
667         final FileOutputStream fos = new FileOutputStream(f);
668         final TarArchiveOutputStream tos;
669         if(blockSize != -2) {
670             tos = new TarArchiveOutputStream(fos, blockSize);
671         } else {
672             blockSize = 512;
673             tos = new TarArchiveOutputStream(fos);
674         }
675         TarArchiveEntry sEntry;
676         sEntry = new TarArchiveEntry(fileName);
677         sEntry.setSize(contents.length);
678         tos.putArchiveEntry(sEntry);
679         tos.write(contents);
680         tos.closeArchiveEntry();
681         tos.close();
682         int fileRecordsSize = (int)Math.ceil((double) contents.length / 512)*512;
683         final int headerSize = 512;
684         final int endOfArchiveSize = 1024;
685         int unpaddedSize = headerSize + fileRecordsSize + endOfArchiveSize;
686         int paddedSize = (int)Math.ceil((double)unpaddedSize/blockSize)*blockSize;
687         assertEquals(paddedSize, f.length());
688     }
689 
690     private byte[] getResourceContents(String name) throws IOException {
691         ByteArrayOutputStream bos;
692         try (InputStream resourceAsStream = getClass().getResourceAsStream(name)) {
693             bos = new ByteArrayOutputStream();
694             IOUtils.copy(resourceAsStream, bos);
695         }
696         return bos.toByteArray();
697     }
698     @Test public void testPutGlobalPaxHeaderEntry() throws IOException {
699         ByteArrayOutputStream bos = new ByteArrayOutputStream();
700         TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
701         int pid = 73;
702         int globCount = 1;
703         byte lfPaxGlobalExtendedHeader = TarConstants.LF_PAX_GLOBAL_EXTENDED_HEADER;
704         TarArchiveEntry globalHeader = new TarArchiveEntry("/tmp/GlobalHead." + pid + "." + globCount,
705             lfPaxGlobalExtendedHeader);
706         globalHeader.addPaxHeader("SCHILLY.xattr.user.org.apache.weasels","global-weasels");
707         tos.putArchiveEntry(globalHeader);
708         TarArchiveEntry entry = new TarArchiveEntry("message");
709         String x = "If at first you don't succeed, give up";
710         entry.setSize(x.length());
711         tos.putArchiveEntry(entry);
712         tos.write(x.getBytes());
713         tos.closeArchiveEntry();
714         entry = new TarArchiveEntry("counter-message");
715         String y = "Nothing succeeds like excess";
716         entry.setSize(y.length());
717         entry.addPaxHeader("SCHILLY.xattr.user.org.apache.weasels.species","unknown");
718         tos.putArchiveEntry(entry);
719         tos.write(y.getBytes());
720         tos.closeArchiveEntry();
721         tos.close();
722         TarArchiveInputStream in = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray()));
723         TarArchiveEntry entryIn = in.getNextTarEntry();
724         assertNotNull(entryIn);
725         assertEquals("message",entryIn.getName());
726         assertEquals("global-weasels",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels"));
727         Reader reader = new InputStreamReader(in);
728         for(int i=0;i<x.length();i++) {
729             assertEquals(x.charAt(i),reader.read());
730         }
731         assertEquals(-1,reader.read());
732         entryIn = in.getNextTarEntry();
733         assertEquals("counter-message",entryIn.getName());
734         assertEquals("global-weasels",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels"));
735         assertEquals("unknown",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels.species"));
736         assertNull(in.getNextTarEntry());
737     }
738 
739     /**
740      * When using long file names the longLinkEntry included the current timestamp as the Entry
741      * modification date. This was never exposed to the client but it caused identical archives to
742      * have different MD5 hashes.
743      */
744     @Test
745     public void testLongNameMd5Hash() throws Exception {
746         final String longFileName = "a/considerably/longer/file/name/which/forces/use/of/the/long/link/header/which/appears/to/always/use/the/current/time/as/modification/date";
747         final String fname = longFileName;
748         final Date modificationDate = new Date();
749 
750         final byte[] archive1 = createTarArchiveContainingOneDirectory(fname, modificationDate);
751         final byte[] digest1 = MessageDigest.getInstance("MD5").digest(archive1);
752 
753         // let a second elapse otherwise the modification dates will be equal
754         Thread.sleep(1000L);
755 
756         // now recreate exactly the same tar file
757         final byte[] archive2 = createTarArchiveContainingOneDirectory(fname, modificationDate);
758         // and I would expect the MD5 hash to be the same, but for long names it isn't
759         final byte[] digest2 = MessageDigest.getInstance("MD5").digest(archive2);
760 
761         Assert.assertArrayEquals(digest1, digest2);
762 
763         // do I still have the correct modification date?
764         // let a second elapse so we don't get the current time
765         Thread.sleep(1000);
766         final TarArchiveInputStream tarIn = new TarArchiveInputStream(
767             new ByteArrayInputStream(archive2));
768         final ArchiveEntry nextEntry = tarIn.getNextEntry();
769         assertEquals(longFileName, nextEntry.getName());
770         // tar archive stores modification time to second granularity only (floored)
771         assertEquals(modificationDate.getTime() / 1000,
772             nextEntry.getLastModifiedDate().getTime() / 1000);
773         tarIn.close();
774     }
775 
776     private static byte[] createTarArchiveContainingOneDirectory(final String fname,
777         final Date modificationDate) throws IOException {
778         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
779         final TarArchiveOutputStream tarOut = new TarArchiveOutputStream(baos, 1024);
780         tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
781         final TarArchiveEntry tarEntry = new TarArchiveEntry("d");
782         tarEntry.setModTime(modificationDate);
783         tarEntry.setMode(TarArchiveEntry.DEFAULT_DIR_MODE);
784         tarEntry.setModTime(modificationDate.getTime());
785         tarEntry.setName(fname);
786         tarOut.putArchiveEntry(tarEntry);
787         tarOut.closeArchiveEntry();
788         tarOut.close();
789 
790         return baos.toByteArray();
791     }
792 
793 }