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  package org.apache.commons.compress.archivers.tar;
19  
20  import static java.nio.charset.StandardCharsets.UTF_8;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.UncheckedIOException;
30  import java.nio.charset.StandardCharsets;
31  import java.util.ArrayList;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  
37  import org.apache.commons.compress.AbstractTest;
38  import org.apache.commons.compress.archivers.zip.ZipEncoding;
39  import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
40  import org.apache.commons.compress.utils.ByteUtils;
41  import org.junit.jupiter.api.Test;
42  
43  public class TarUtilsTest extends AbstractTest {
44  
45      private void checkName(final String string) {
46          final byte[] buff = new byte[100];
47          final int len = TarUtils.formatNameBytes(string, buff, 0, buff.length);
48          assertEquals(string, TarUtils.parseName(buff, 0, len));
49      }
50  
51      private void checkRoundTripOctal(final long value) {
52          checkRoundTripOctal(value, TarConstants.SIZELEN);
53      }
54  
55      private void checkRoundTripOctal(final long value, final int bufsize) {
56          final byte[] buffer = new byte[bufsize];
57          long parseValue;
58          TarUtils.formatLongOctalBytes(value, buffer, 0, buffer.length);
59          parseValue = TarUtils.parseOctal(buffer, 0, buffer.length);
60          assertEquals(value, parseValue);
61      }
62  
63      private void checkRoundTripOctalOrBinary(final long value, final int bufsize) {
64          final byte[] buffer = new byte[bufsize];
65          long parseValue;
66          TarUtils.formatLongOctalOrBinaryBytes(value, buffer, 0, buffer.length);
67          parseValue = TarUtils.parseOctalOrBinary(buffer, 0, buffer.length);
68          assertEquals(value, parseValue);
69      }
70  
71      @Test
72      public void testName() {
73          byte[] buff = new byte[20];
74          final String sb1 = "abcdefghijklmnopqrstuvwxyz";
75          int off = TarUtils.formatNameBytes(sb1, buff, 1, buff.length - 1);
76          assertEquals(off, 20);
77          String sb2 = TarUtils.parseName(buff, 1, 10);
78          assertEquals(sb2, sb1.substring(0, 10));
79          sb2 = TarUtils.parseName(buff, 1, 19);
80          assertEquals(sb2, sb1.substring(0, 19));
81          buff = new byte[30];
82          off = TarUtils.formatNameBytes(sb1, buff, 1, buff.length - 1);
83          assertEquals(off, 30);
84          sb2 = TarUtils.parseName(buff, 1, buff.length - 1);
85          assertEquals(sb1, sb2);
86          buff = new byte[] { 0, 1, 0 };
87          sb2 = TarUtils.parseName(buff, 0, 3);
88          assertEquals("", sb2);
89      }
90  
91      @Test
92      public void testNegative() {
93          final byte[] buffer = new byte[22];
94          TarUtils.formatUnsignedOctalString(-1, buffer, 0, buffer.length);
95          assertEquals("1777777777777777777777", new String(buffer, UTF_8));
96      }
97  
98      @Test
99      public void testOverflow() {
100         final byte[] buffer = new byte[8 - 1]; // a lot of the numbers have 8-byte buffers (nul term)
101         TarUtils.formatUnsignedOctalString(07777777L, buffer, 0, buffer.length);
102         assertEquals("7777777", new String(buffer, UTF_8));
103         assertThrows(IllegalArgumentException.class, () -> TarUtils.formatUnsignedOctalString(017777777L, buffer, 0, buffer.length),
104                 "Should have cause IllegalArgumentException");
105     }
106 
107     @Test
108     public void testParseFromPAX01SparseHeaders() throws Exception {
109         final String map = "0,10,20,0,20,5";
110         final List<TarArchiveStructSparse> sparse = TarUtils.parseFromPAX01SparseHeaders(map);
111         assertEquals(3, sparse.size());
112         assertEquals(0, sparse.get(0).getOffset());
113         assertEquals(10, sparse.get(0).getNumbytes());
114         assertEquals(20, sparse.get(1).getOffset());
115         assertEquals(0, sparse.get(1).getNumbytes());
116         assertEquals(20, sparse.get(2).getOffset());
117         assertEquals(5, sparse.get(2).getNumbytes());
118     }
119 
120     @Test
121     public void testParseFromPAX01SparseHeadersRejectsNegativeNumbytes() throws Exception {
122         assertThrows(IOException.class, () -> TarUtils.parseFromPAX01SparseHeaders("0,10,20,0,20,-5"));
123     }
124 
125     @Test
126     public void testParseFromPAX01SparseHeadersRejectsNegativeOffset() throws Exception {
127         assertThrows(IOException.class, () -> TarUtils.parseFromPAX01SparseHeaders("0,10,20,0,-2,5"));
128     }
129 
130     @Test
131     public void testParseFromPAX01SparseHeadersRejectsNonNumericNumbytes() throws Exception {
132         assertThrows(IOException.class, () -> TarUtils.parseFromPAX01SparseHeaders("0,10,20,0,20,b"));
133     }
134 
135     @Test
136     public void testParseFromPAX01SparseHeadersRejectsNonNumericOffset() throws Exception {
137         assertThrows(IOException.class, () -> TarUtils.parseFromPAX01SparseHeaders("0,10,20,0,2a,5"));
138     }
139 
140     @Test
141     public void testParseFromPAX01SparseHeadersRejectsOddNumberOfEntries() throws Exception {
142         final String map = "0,10,20,0,20";
143         assertThrows(IOException.class, () -> TarUtils.parseFromPAX01SparseHeaders(map));
144     }
145 
146     @Test
147     public void testParseOctal() {
148         long value;
149         byte[] buffer;
150         final long MAX_OCTAL = 077777777777L; // Allowed 11 digits
151         final long MAX_OCTAL_OVERFLOW = 0777777777777L; // in fact 12 for some implementations
152         final String maxOctal = "777777777777"; // Maximum valid octal
153         buffer = maxOctal.getBytes(UTF_8);
154         value = TarUtils.parseOctal(buffer, 0, buffer.length);
155         assertEquals(MAX_OCTAL_OVERFLOW, value);
156         buffer[buffer.length - 1] = ' ';
157         value = TarUtils.parseOctal(buffer, 0, buffer.length);
158         assertEquals(MAX_OCTAL, value);
159         buffer[buffer.length - 1] = 0;
160         value = TarUtils.parseOctal(buffer, 0, buffer.length);
161         assertEquals(MAX_OCTAL, value);
162         buffer = new byte[] { 0, 0 };
163         value = TarUtils.parseOctal(buffer, 0, buffer.length);
164         assertEquals(0, value);
165         buffer = new byte[] { 0, ' ' };
166         value = TarUtils.parseOctal(buffer, 0, buffer.length);
167         assertEquals(0, value);
168         buffer = new byte[] { ' ', 0 };
169         value = TarUtils.parseOctal(buffer, 0, buffer.length);
170         assertEquals(0, value);
171     }
172 
173     @Test
174     public void testParseOctalCompress330() {
175         final long expected = 0100000;
176         final byte[] buffer = { 32, 32, 32, 32, 32, 49, 48, 48, 48, 48, 48, 32 };
177         assertEquals(expected, TarUtils.parseOctalOrBinary(buffer, 0, buffer.length));
178     }
179 
180     @Test
181     public void testParseOctalEmbeddedSpace() {
182         final byte[] buffer4 = " 0 07 ".getBytes(UTF_8); // Invalid - embedded space
183         assertThrows(IllegalArgumentException.class, () -> TarUtils.parseOctal(buffer4, 0, buffer4.length),
184                 "Expected IllegalArgumentException - embedded space");
185     }
186 
187     @Test
188     public void testParseOctalInvalid() {
189         final byte[] buffer1 = ByteUtils.EMPTY_BYTE_ARRAY;
190         assertThrows(IllegalArgumentException.class, () -> TarUtils.parseOctal(buffer1, 0, buffer1.length),
191                 "Expected IllegalArgumentException - should be at least 2 bytes long");
192 
193         final byte[] buffer2 = { 0 }; // 1-byte array
194         assertThrows(IllegalArgumentException.class, () -> TarUtils.parseOctal(buffer2, 0, buffer2.length),
195                 "Expected IllegalArgumentException - should be at least 2 bytes long");
196 
197         final byte[] buffer3 = "abcdef ".getBytes(UTF_8); // Invalid input
198         assertThrows(IllegalArgumentException.class, () -> TarUtils.parseOctal(buffer3, 0, buffer3.length), "Expected IllegalArgumentException");
199 
200         final byte[] buffer5 = " 0\00007 ".getBytes(UTF_8); // Invalid - embedded NUL
201         assertThrows(IllegalArgumentException.class, () -> TarUtils.parseOctal(buffer5, 0, buffer5.length), "Expected IllegalArgumentException - embedded NUL");
202     }
203 
204     @Test
205     public void testParsePAX01SparseHeadersRejectsOddNumberOfEntries() {
206         final String map = "0,10,20,0,20";
207         assertThrows(UncheckedIOException.class, () -> TarUtils.parsePAX01SparseHeaders(map));
208     }
209 
210     @Test
211     public void testParsePAX1XSparseHeaders() throws Exception {
212         final byte[] header = ("1\n" + "0\n" + "20\n").getBytes();
213         final byte[] block = new byte[512];
214         System.arraycopy(header, 0, block, 0, header.length);
215         try (ByteArrayInputStream in = new ByteArrayInputStream(block)) {
216             final List<TarArchiveStructSparse> sparse = TarUtils.parsePAX1XSparseHeaders(in, 512);
217             assertEquals(1, sparse.size());
218             assertEquals(0, sparse.get(0).getOffset());
219             assertEquals(20, sparse.get(0).getNumbytes());
220             assertEquals(-1, in.read());
221         }
222     }
223 
224     @Test
225     public void testParsePAX1XSparseHeadersRejectsIncompleteLastLine() throws Exception {
226         final byte[] header = ("1\n" + "0\n" + "20").getBytes();
227         try (ByteArrayInputStream in = new ByteArrayInputStream(header)) {
228             assertThrows(IOException.class, () -> TarUtils.parsePAX1XSparseHeaders(in, 512));
229         }
230     }
231 
232     @Test
233     public void testParsePAX1XSparseHeadersRejectsNegativeNumberOfEntries() throws Exception {
234         final byte[] header = ("111111111111111111111111111111111111111111111111111111111111111\n" + "0\n" + "20\n").getBytes();
235         final byte[] block = new byte[512];
236         System.arraycopy(header, 0, block, 0, header.length);
237         try (ByteArrayInputStream in = new ByteArrayInputStream(block)) {
238             assertThrows(IOException.class, () -> TarUtils.parsePAX1XSparseHeaders(in, 512));
239         }
240     }
241 
242     @Test
243     public void testParsePAX1XSparseHeadersRejectsNegativeNumbytes() throws Exception {
244         final byte[] header = ("1\n" + "0\n" + "111111111111111111111111111111111111111111111111111111111111111\n").getBytes();
245         final byte[] block = new byte[512];
246         System.arraycopy(header, 0, block, 0, header.length);
247         try (ByteArrayInputStream in = new ByteArrayInputStream(block)) {
248             assertThrows(IOException.class, () -> TarUtils.parsePAX1XSparseHeaders(in, 512));
249         }
250     }
251 
252     @Test
253     public void testParsePAX1XSparseHeadersRejectsNegativeOffset() throws Exception {
254         final byte[] header = ("1\n" + "111111111111111111111111111111111111111111111111111111111111111\n" + "20\n").getBytes();
255         final byte[] block = new byte[512];
256         System.arraycopy(header, 0, block, 0, header.length);
257         try (ByteArrayInputStream in = new ByteArrayInputStream(block)) {
258             assertThrows(IOException.class, () -> TarUtils.parsePAX1XSparseHeaders(in, 512));
259         }
260     }
261 
262     @Test
263     public void testParsePAX1XSparseHeadersRejectsNonNumericNumberOfEntries() throws Exception {
264         final byte[] header = ("x\n" + "0\n" + "20\n").getBytes();
265         final byte[] block = new byte[512];
266         System.arraycopy(header, 0, block, 0, header.length);
267         try (ByteArrayInputStream in = new ByteArrayInputStream(block)) {
268             assertThrows(IOException.class, () -> TarUtils.parsePAX1XSparseHeaders(in, 512));
269         }
270     }
271 
272     @Test
273     public void testParsePAX1XSparseHeadersRejectsNonNumericNumbytes() throws Exception {
274         final byte[] header = ("1\n" + "0\n" + "2x\n").getBytes();
275         final byte[] block = new byte[512];
276         System.arraycopy(header, 0, block, 0, header.length);
277         try (ByteArrayInputStream in = new ByteArrayInputStream(block)) {
278             assertThrows(IOException.class, () -> TarUtils.parsePAX1XSparseHeaders(in, 512));
279         }
280     }
281 
282     @Test
283     public void testParsePAX1XSparseHeadersRejectsNonNumericOffset() throws Exception {
284         final byte[] header = ("1\n" + "x\n" + "20\n").getBytes();
285         final byte[] block = new byte[512];
286         System.arraycopy(header, 0, block, 0, header.length);
287         try (ByteArrayInputStream in = new ByteArrayInputStream(block)) {
288             assertThrows(IOException.class, () -> TarUtils.parsePAX1XSparseHeaders(in, 512));
289         }
290     }
291 
292     @Test
293     public void testParseSparse() {
294         final long expectedOffset = 0100000;
295         final long expectedNumbytes = 0111000;
296         final byte[] buffer = { ' ', ' ', ' ', ' ', ' ', '0', '1', '0', '0', '0', '0', '0', // sparseOffset
297                 ' ', ' ', ' ', ' ', ' ', '0', '1', '1', '1', '0', '0', '0' };
298         final TarArchiveStructSparse sparse = TarUtils.parseSparse(buffer, 0);
299         assertEquals(sparse.getOffset(), expectedOffset);
300         assertEquals(sparse.getNumbytes(), expectedNumbytes);
301     }
302 
303     @Test
304     public void testParseTarWithSpecialPaxHeaders() throws IOException {
305         try (InputStream in = newInputStream("COMPRESS-530-fail.tar");
306                 TarArchiveInputStream archive = new TarArchiveInputStream(in)) {
307             assertThrows(IOException.class, () -> archive.getNextEntry());
308             // IOUtils.toByteArray(archive);
309         }
310     }
311 
312     @Test
313     public void testPaxHeaderEntryWithEmptyValueRemovesKey() throws Exception {
314         final Map<String, String> headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("11 foo=bar\n7 foo=\n".getBytes(UTF_8)), null, new HashMap<>());
315         assertEquals(0, headers.size());
316     }
317 
318     @Test
319     public void testReadNegativeBinary12Byte() {
320         final byte[] b = { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
321                 (byte) 0xf1, (byte) 0xef, };
322         assertEquals(-3601L, TarUtils.parseOctalOrBinary(b, 0, 12));
323     }
324 
325     @Test
326     public void testReadNegativeBinary8Byte() {
327         final byte[] b = { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xf1, (byte) 0xef, };
328         assertEquals(-3601L, TarUtils.parseOctalOrBinary(b, 0, 8));
329     }
330 
331     @Test
332     public void testReadNonAsciiPaxHeader() throws Exception {
333         final String ae = "\u00e4";
334         final String line = "11 path=" + ae + "\n";
335         assertEquals(11, line.getBytes(UTF_8).length);
336         final Map<String, String> headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream(line.getBytes(UTF_8)), null, new HashMap<>());
337         assertEquals(1, headers.size());
338         assertEquals(ae, headers.get("path"));
339     }
340 
341     @Test
342     public void testReadPax00SparseHeader() throws Exception {
343         final String header = "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=10\n";
344         final List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
345         TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), sparseHeaders, Collections.emptyMap());
346         assertEquals(1, sparseHeaders.size());
347         assertEquals(0, sparseHeaders.get(0).getOffset());
348         assertEquals(10, sparseHeaders.get(0).getNumbytes());
349     }
350 
351     @Test
352     public void testReadPax00SparseHeaderMakesNumbytesOptional() throws Exception {
353         final String header = "23 GNU.sparse.offset=0\n24 GNU.sparse.offset=10\n";
354         final List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
355         TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), sparseHeaders, Collections.emptyMap());
356         assertEquals(2, sparseHeaders.size());
357         assertEquals(0, sparseHeaders.get(0).getOffset());
358         assertEquals(0, sparseHeaders.get(0).getNumbytes());
359         assertEquals(10, sparseHeaders.get(1).getOffset());
360         assertEquals(0, sparseHeaders.get(1).getNumbytes());
361     }
362 
363     @Test
364     public void testReadPax00SparseHeaderRejectsNegativeNumbytes() throws Exception {
365         final String header = "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=-1\n";
366         assertThrows(IOException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
367     }
368 
369     @Test
370     public void testReadPax00SparseHeaderRejectsNegativeOffset() throws Exception {
371         final String header = "24 GNU.sparse.offset=-1\n26 GNU.sparse.numbytes=10\n";
372         assertThrows(IOException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
373     }
374 
375     @Test
376     public void testReadPax00SparseHeaderRejectsNonNumericNumbytes() throws Exception {
377         final String header = "23 GNU.sparse.offset=0\n26 GNU.sparse.numbytes=1a\n";
378         assertThrows(IOException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
379     }
380 
381     @Test
382     public void testReadPax00SparseHeaderRejectsNonNumericOffset() throws Exception {
383         final String header = "23 GNU.sparse.offset=a\n26 GNU.sparse.numbytes=10\n";
384         assertThrows(IOException.class, () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream(header.getBytes(UTF_8)), null, Collections.emptyMap()));
385     }
386 
387     @Test
388     public void testReadPaxHeaderWithEmbeddedNewline() throws Exception {
389         final Map<String, String> headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("28 comment=line1\nline2\nand3\n".getBytes(UTF_8)), null,
390                 new HashMap<>());
391         assertEquals(1, headers.size());
392         assertEquals("line1\nline2\nand3", headers.get("comment"));
393     }
394 
395     @Test
396     public void testReadPaxHeaderWithoutTrailingNewline() throws Exception {
397         assertThrows(IOException.class,
398                 () -> TarUtils.parsePaxHeaders(new ByteArrayInputStream("30 atime=1321711775.9720594634".getBytes(UTF_8)), null, Collections.emptyMap()));
399     }
400 
401     @Test
402     public void testReadSimplePaxHeader() throws Exception {
403         final Map<String, String> headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("30 atime=1321711775.972059463\n".getBytes(UTF_8)), null,
404                 new HashMap<>());
405         assertEquals(1, headers.size());
406         assertEquals("1321711775.972059463", headers.get("atime"));
407     }
408 
409     @Test
410     public void testReadSparseStructsBinary() throws Exception {
411         final byte[] header = { (byte) 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, };
412         assertEquals(24, header.length);
413         final List<TarArchiveStructSparse> sparse = TarUtils.readSparseStructs(header, 0, 1);
414         assertEquals(1, sparse.size());
415         assertEquals(0, sparse.get(0).getOffset());
416         assertEquals(7, sparse.get(0).getNumbytes());
417     }
418 
419     @Test
420     public void testReadSparseStructsOctal() throws Exception {
421         final byte[] header = "00000000000 00000000007 ".getBytes();
422         assertEquals(24, header.length);
423         final List<TarArchiveStructSparse> sparse = TarUtils.readSparseStructs(header, 0, 1);
424         assertEquals(1, sparse.size());
425         assertEquals(0, sparse.get(0).getOffset());
426         assertEquals(7, sparse.get(0).getNumbytes());
427     }
428 
429     @Test
430     public void testReadSparseStructsRejectsNegativeNumbytes() throws Exception {
431         final byte[] header = { (byte) 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
432                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, };
433         assertThrows(IOException.class, () -> TarUtils.readSparseStructs(header, 0, 1));
434     }
435 
436     @Test
437     public void testReadSparseStructsRejectsNegativeOffset() throws Exception {
438         final byte[] header = { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
439                 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, };
440         assertThrows(IOException.class, () -> TarUtils.readSparseStructs(header, 0, 1));
441     }
442 
443     @Test
444     public void testReadSparseStructsRejectsNonNumericNumbytes() throws Exception {
445         final byte[] header = "00000000000 0000000000x ".getBytes();
446         assertThrows(IOException.class, () -> TarUtils.readSparseStructs(header, 0, 1));
447     }
448 
449     @Test
450     public void testReadSparseStructsRejectsNonNumericOffset() throws Exception {
451         final byte[] header = "0000000000x 00000000007 ".getBytes();
452         assertThrows(IOException.class, () -> TarUtils.readSparseStructs(header, 0, 1));
453     }
454 
455     @Test
456     public void testRoundEncoding() throws Exception {
457         // COMPRESS-114
458         final ZipEncoding enc = ZipEncodingHelper.getZipEncoding(StandardCharsets.ISO_8859_1.name());
459         // @formatter:off
460         final String s = "0302-0601-3\u00b1\u00b1\u00b1F06\u00b1W220\u00b1ZB\u00b1LALALA\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1\u00b1CAN"
461                 + "\u00b1\u00b1DC\u00b1\u00b1\u00b104\u00b1060302\u00b1MOE.model";
462         // @formatter:on
463         final byte[] buff = new byte[100];
464         final int len = TarUtils.formatNameBytes(s, buff, 0, buff.length, enc);
465         assertEquals(s, TarUtils.parseName(buff, 0, len, enc));
466     }
467 
468     @Test
469     public void testRoundTripNames() {
470         checkName("");
471         checkName("The quick brown fox\n");
472         checkName("\177");
473         // checkName("\0"); // does not work, because NUL is ignored
474     }
475 
476     @Test
477     public void testRoundTripOctal() {
478         checkRoundTripOctal(0);
479         checkRoundTripOctal(1);
480 //        checkRoundTripOctal(-1); // TODO What should this do?
481         checkRoundTripOctal(TarConstants.MAXSIZE);
482 //        checkRoundTripOctal(0100000000000L); // TODO What should this do?
483 
484         checkRoundTripOctal(0, TarConstants.UIDLEN);
485         checkRoundTripOctal(1, TarConstants.UIDLEN);
486         checkRoundTripOctal(TarConstants.MAXID, 8);
487     }
488 
489     private void testRoundTripOctalOrBinary(final int length) {
490         checkRoundTripOctalOrBinary(0, length);
491         checkRoundTripOctalOrBinary(1, length);
492         checkRoundTripOctalOrBinary(TarConstants.MAXSIZE, length); // will need binary format
493         checkRoundTripOctalOrBinary(-1, length); // will need binary format
494         checkRoundTripOctalOrBinary(0xffffffffffffffL, length);
495         checkRoundTripOctalOrBinary(-0xffffffffffffffL, length);
496     }
497 
498     @Test
499     public void testRoundTripOctalOrBinary12() {
500         testRoundTripOctalOrBinary(12);
501         checkRoundTripOctalOrBinary(Long.MAX_VALUE, 12);
502         checkRoundTripOctalOrBinary(Long.MIN_VALUE + 1, 12);
503     }
504 
505     @Test
506     public void testRoundTripOctalOrBinary8() {
507         testRoundTripOctalOrBinary(8);
508     }
509 
510     @Test
511     public void testRoundTripOctalOrBinary8_ValueTooBigForBinary() {
512         final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> checkRoundTripOctalOrBinary(Long.MAX_VALUE, 8),
513                 "Should throw exception - value is too long to fit buffer of this len");
514         assertEquals("Value 9223372036854775807 is too large for 8 byte field.", e.getMessage());
515     }
516 
517     @Test
518     public void testSecondEntryWinsWhenPaxHeaderContainsDuplicateKey() throws Exception {
519         final Map<String, String> headers = TarUtils.parsePaxHeaders(new ByteArrayInputStream("11 foo=bar\n11 foo=baz\n".getBytes(UTF_8)), null,
520                 new HashMap<>());
521         assertEquals(1, headers.size());
522         assertEquals("baz", headers.get("foo"));
523     }
524 
525     // Check correct trailing bytes are generated
526     @Test
527     public void testTrailers() {
528         final byte[] buffer = new byte[12];
529         TarUtils.formatLongOctalBytes(123, buffer, 0, buffer.length);
530         assertEquals(' ', buffer[buffer.length - 1]);
531         assertEquals('3', buffer[buffer.length - 2]); // end of number
532         TarUtils.formatOctalBytes(123, buffer, 0, buffer.length);
533         assertEquals(0, buffer[buffer.length - 1]);
534         assertEquals(' ', buffer[buffer.length - 2]);
535         assertEquals('3', buffer[buffer.length - 3]); // end of number
536         TarUtils.formatCheckSumOctalBytes(123, buffer, 0, buffer.length);
537         assertEquals(' ', buffer[buffer.length - 1]);
538         assertEquals(0, buffer[buffer.length - 2]);
539         assertEquals('3', buffer[buffer.length - 3]); // end of number
540     }
541 
542     // https://issues.apache.org/jira/browse/COMPRESS-191
543     @Test
544     public void testVerifyHeaderCheckSum() {
545         final byte[] valid = { // from bla.tar
546                 116, 101, 115, 116, 49, 46, 120, 109, 108, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
547                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
548                 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 54, 52, 52, 0, 48, 48, 48, 48, 55, 54, 53, 0, 48, 48, 48, 48, 55, 54, 53, 0, 48, 48, 48, 48, 48, 48,
549                 48, 49, 49, 52, 50, 0, 49, 48, 55, 49, 54, 53, 52, 53, 54, 50, 54, 0, 48, 49, 50, 50, 54, 48, 0, 32, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
550                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
551                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 115, 116, 97, 114, 32,
552                 32, 0, 116, 99, 117, 114, 100, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 116, 99, 117, 114, 100, 116,
553                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
554                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
555                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
556                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
557                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
558         assertTrue(TarUtils.verifyCheckSum(valid));
559 
560         final byte[] compress117 = { // from COMPRESS-117
561                 (byte) 0x37, (byte) 0x7a, (byte) 0x43, (byte) 0x2e, (byte) 0x74, (byte) 0x78, (byte) 0x74, (byte) 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
562                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
563                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0x31, (byte) 0x30, (byte) 0x30, (byte) 0x37,
564                 (byte) 0x37, (byte) 0x37, (byte) 0x20, (byte) 0x00, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x30, (byte) 0x20,
565                 (byte) 0x00, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x30, (byte) 0x20, (byte) 0x00, (byte) 0x20, (byte) 0x20,
566                 (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x33, (byte) 0x33, (byte) 0x20, (byte) 0x31,
567                 (byte) 0x31, (byte) 0x31, (byte) 0x31, (byte) 0x35, (byte) 0x31, (byte) 0x36, (byte) 0x36, (byte) 0x30, (byte) 0x31, (byte) 0x36, (byte) 0x20,
568                 (byte) 0x20, (byte) 0x20, (byte) 0x35, (byte) 0x34, (byte) 0x31, (byte) 0x37, (byte) 0x20, (byte) 0x00, (byte) 0x30, (byte) 0x00, 0, 0, 0, 0, 0,
569                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
570                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
571                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
572                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
573                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
574                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
575                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
576                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
577         assertTrue(TarUtils.verifyCheckSum(compress117));
578 
579         final byte[] invalid = { // from the testAIFF.aif file in Tika
580                 70, 79, 82, 77, 0, 0, 15, 46, 65, 73, 70, 70, 67, 79, 77, 77, 0, 0, 0, 18, 0, 2, 0, 0, 3, -64, 0, 16, 64, 14, -84, 68, 0, 0, 0, 0, 0, 0, 83, 83,
581                 78, 68, 0, 0, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, 0, 0, 0, 0, -1, -1, 0, 2, -1, -2,
582                 0, 2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, -1, -1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 1, -1, -2, 0, 1, -1, -1, 0, 1, 0, 0,
583                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 2, -1, -2, 0, 2, -1, -1, 0, 0, 0, 1, -1, -1, 0, 1, -1, -1, 0, 0, 0, 0, 0, 0, 0,
584                 0, 0, 1, -1, -2, 0, 2, -1, -2, 0, 1, 0, 0, 0, 1, -1, -1, 0, 0, 0, 1, -1, -1, 0, 0, 0, 1, -1, -2, 0, 2, -1, -1, 0, 0, 0, 0, 0, 0, -1, -1, 0, 1,
585                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 1, -1, -1, 0, 2, -1, -2, 0, 2, -1, -2, 0, 2, -1, -2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1,
586                 -1, -2, 0, 2, -1, -2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 1, 0, 0, -1, -1, 0, 2, -1, -2, 0, 2, -1, -1, 0, 0, 0, 0, 0, 0, -1,
587                 -1, 0, 1, -1, -1, 0, 1, -1, -1, 0, 2, -1, -2, 0, 1, 0, 0, -1, -1, 0, 2, -1, -2, 0, 2, -1, -2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
588                 0, 0, 1, -1, -1, 0, 0, 0, 0, -1, -1, 0, 1, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, -2, 0, 2, -1, -1,
589                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, -2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, 0, 0, -1, -1, 0, 2, -1, -2, 0, 2, -1, -2, 0,
590                 2, -1, -1, 0, 0, 0, 0, -1, -1, 0, 1, -1, -1, 0, 1, -1, -1, 0, 1, -1, -1, 0, 1, -1, -1, 0, 1, 0, 0, 0, 0, -1, -1, 0, 2, -1, -2, 0, 1, 0, 0, 0, 0,
591                 0, 0, 0, 0, 0, 0, 0, 1, -1, -1, 0, 0, 0, 0, -1, -1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
592         assertFalse(TarUtils.verifyCheckSum(invalid));
593     }
594 
595     @Test
596     public void testWriteNegativeBinary8Byte() {
597         final byte[] b = { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xf1, (byte) 0xef, };
598         assertEquals(-3601L, TarUtils.parseOctalOrBinary(b, 0, 8));
599     }
600 
601 }