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