Home | History | Annotate | Download | only in tar
      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         // test with "content" that is an exact multiple of record length
    663         contents = new byte[2048];
    664         java.util.Arrays.fill(contents, (byte) 42);
    665         testPadding(TarConstants.DEFAULT_BLKSIZE, fileName, contents);
    666     }
    667 
    668     private void testPadding(int blockSize, String fileName, byte[] contents) throws IOException {
    669         final File f = File.createTempFile("commons-compress-padding", ".tar");
    670         f.deleteOnExit();
    671         final FileOutputStream fos = new FileOutputStream(f);
    672         final TarArchiveOutputStream tos;
    673         if (blockSize != -2) {
    674             tos = new TarArchiveOutputStream(fos, blockSize);
    675         } else {
    676             blockSize = 512;
    677             tos = new TarArchiveOutputStream(fos);
    678         }
    679         TarArchiveEntry sEntry;
    680         sEntry = new TarArchiveEntry(fileName);
    681         sEntry.setSize(contents.length);
    682         tos.putArchiveEntry(sEntry);
    683         tos.write(contents);
    684         tos.closeArchiveEntry();
    685         tos.close();
    686         int fileRecordsSize = (int) Math.ceil((double) contents.length / 512) * 512;
    687         final int headerSize = 512;
    688         final int endOfArchiveSize = 1024;
    689         int unpaddedSize = headerSize + fileRecordsSize + endOfArchiveSize;
    690         int paddedSize = (int) Math.ceil((double)unpaddedSize/blockSize)*blockSize;
    691         assertEquals(paddedSize, f.length());
    692     }
    693 
    694     private byte[] getResourceContents(String name) throws IOException {
    695         ByteArrayOutputStream bos;
    696         try (InputStream resourceAsStream = getClass().getResourceAsStream(name)) {
    697             bos = new ByteArrayOutputStream();
    698             IOUtils.copy(resourceAsStream, bos);
    699         }
    700         return bos.toByteArray();
    701     }
    702     @Test public void testPutGlobalPaxHeaderEntry() throws IOException {
    703         ByteArrayOutputStream bos = new ByteArrayOutputStream();
    704         TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
    705         int pid = 73;
    706         int globCount = 1;
    707         byte lfPaxGlobalExtendedHeader = TarConstants.LF_PAX_GLOBAL_EXTENDED_HEADER;
    708         TarArchiveEntry globalHeader = new TarArchiveEntry("/tmp/GlobalHead." + pid + "." + globCount,
    709             lfPaxGlobalExtendedHeader);
    710         globalHeader.addPaxHeader("SCHILLY.xattr.user.org.apache.weasels","global-weasels");
    711         tos.putArchiveEntry(globalHeader);
    712         TarArchiveEntry entry = new TarArchiveEntry("message");
    713         String x = "If at first you don't succeed, give up";
    714         entry.setSize(x.length());
    715         tos.putArchiveEntry(entry);
    716         tos.write(x.getBytes());
    717         tos.closeArchiveEntry();
    718         entry = new TarArchiveEntry("counter-message");
    719         String y = "Nothing succeeds like excess";
    720         entry.setSize(y.length());
    721         entry.addPaxHeader("SCHILLY.xattr.user.org.apache.weasels.species","unknown");
    722         tos.putArchiveEntry(entry);
    723         tos.write(y.getBytes());
    724         tos.closeArchiveEntry();
    725         tos.close();
    726         TarArchiveInputStream in = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray()));
    727         TarArchiveEntry entryIn = in.getNextTarEntry();
    728         assertNotNull(entryIn);
    729         assertEquals("message",entryIn.getName());
    730         assertEquals("global-weasels",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels"));
    731         Reader reader = new InputStreamReader(in);
    732         for(int i=0;i<x.length();i++) {
    733             assertEquals(x.charAt(i),reader.read());
    734         }
    735         assertEquals(-1,reader.read());
    736         entryIn = in.getNextTarEntry();
    737         assertEquals("counter-message",entryIn.getName());
    738         assertEquals("global-weasels",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels"));
    739         assertEquals("unknown",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels.species"));
    740         assertNull(in.getNextTarEntry());
    741     }
    742 
    743     /**
    744      * When using long file names the longLinkEntry included the current timestamp as the Entry
    745      * modification date. This was never exposed to the client but it caused identical archives to
    746      * have different MD5 hashes.
    747      */
    748     @Test
    749     public void testLongNameMd5Hash() throws Exception {
    750         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";
    751         final String fname = longFileName;
    752         final Date modificationDate = new Date();
    753 
    754         final byte[] archive1 = createTarArchiveContainingOneDirectory(fname, modificationDate);
    755         final byte[] digest1 = MessageDigest.getInstance("MD5").digest(archive1);
    756 
    757         // let a second elapse otherwise the modification dates will be equal
    758         Thread.sleep(1000L);
    759 
    760         // now recreate exactly the same tar file
    761         final byte[] archive2 = createTarArchiveContainingOneDirectory(fname, modificationDate);
    762         // and I would expect the MD5 hash to be the same, but for long names it isn't
    763         final byte[] digest2 = MessageDigest.getInstance("MD5").digest(archive2);
    764 
    765         Assert.assertArrayEquals(digest1, digest2);
    766 
    767         // do I still have the correct modification date?
    768         // let a second elapse so we don't get the current time
    769         Thread.sleep(1000);
    770         final TarArchiveInputStream tarIn = new TarArchiveInputStream(
    771             new ByteArrayInputStream(archive2));
    772         final ArchiveEntry nextEntry = tarIn.getNextEntry();
    773         assertEquals(longFileName, nextEntry.getName());
    774         // tar archive stores modification time to second granularity only (floored)
    775         assertEquals(modificationDate.getTime() / 1000,
    776             nextEntry.getLastModifiedDate().getTime() / 1000);
    777         tarIn.close();
    778     }
    779 
    780     private static byte[] createTarArchiveContainingOneDirectory(final String fname,
    781         final Date modificationDate) throws IOException {
    782         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    783         final TarArchiveOutputStream tarOut = new TarArchiveOutputStream(baos, 1024);
    784         tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
    785         final TarArchiveEntry tarEntry = new TarArchiveEntry("d");
    786         tarEntry.setModTime(modificationDate);
    787         tarEntry.setMode(TarArchiveEntry.DEFAULT_DIR_MODE);
    788         tarEntry.setModTime(modificationDate.getTime());
    789         tarEntry.setName(fname);
    790         tarOut.putArchiveEntry(tarEntry);
    791         tarOut.closeArchiveEntry();
    792         tarOut.close();
    793 
    794         return baos.toByteArray();
    795     }
    796 
    797 }
    798