1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.archivers.tar;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.UncheckedIOException;
24 import java.math.BigInteger;
25 import java.nio.ByteBuffer;
26 import java.nio.charset.Charset;
27 import java.nio.charset.StandardCharsets;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34
35 import org.apache.commons.compress.archivers.zip.ZipEncoding;
36 import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
37 import org.apache.commons.compress.utils.IOUtils;
38 import org.apache.commons.compress.utils.ParsingUtils;
39 import org.apache.commons.io.output.ByteArrayOutputStream;
40
41
42
43
44
45
46
47 public class TarUtils {
48
49 private static final BigInteger NEG_1_BIG_INT = BigInteger.valueOf(-1);
50
51 private static final int BYTE_MASK = 255;
52
53 static final ZipEncoding DEFAULT_ENCODING = ZipEncodingHelper.getZipEncoding(Charset.defaultCharset());
54
55
56
57
58 static final ZipEncoding FALLBACK_ENCODING = new ZipEncoding() {
59
60 @Override
61 public boolean canEncode(final String name) {
62 return true;
63 }
64
65 @Override
66 public String decode(final byte[] buffer) {
67 final int length = buffer.length;
68 final StringBuilder result = new StringBuilder(length);
69 for (final byte b : buffer) {
70 if (b == 0) {
71 break;
72 }
73 result.append((char) (b & 0xFF));
74 }
75 return result.toString();
76 }
77
78 @Override
79 public ByteBuffer encode(final String name) {
80 return ByteBuffer.wrap(name.getBytes(StandardCharsets.US_ASCII));
81 }
82 };
83
84
85
86
87
88
89
90 public static long computeCheckSum(final byte[] buf) {
91 long sum = 0;
92 for (final byte element : buf) {
93 sum += BYTE_MASK & element;
94 }
95 return sum;
96 }
97
98
99
100
101 private static String exceptionMessage(final byte[] buffer, final int offset, final int length, final int current, final byte currentByte) {
102
103
104
105
106
107
108
109 String string = new String(buffer, offset, length, Charset.defaultCharset());
110 string = string.replace("\0", "{NUL}");
111 return "Invalid byte " + currentByte + " at offset " + (current - offset) + " in '" + string + "' len=" + length;
112 }
113
114 private static void formatBigIntegerBinary(final long value, final byte[] buf, final int offset, final int length, final boolean negative) {
115 final BigInteger val = BigInteger.valueOf(value);
116 final byte[] b = val.toByteArray();
117 final int len = b.length;
118 if (len > length - 1) {
119 throw new IllegalArgumentException("Value " + value + " is too large for " + length + " byte field.");
120 }
121 final int off = offset + length - len;
122 System.arraycopy(b, 0, buf, off, len);
123 Arrays.fill(buf, offset + 1, off, (byte) (negative ? 0xff : 0));
124 }
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 public static int formatCheckSumOctalBytes(final long value, final byte[] buf, final int offset, final int length) {
140 int idx = length - 2;
141 formatUnsignedOctalString(value, buf, offset, idx);
142 buf[offset + idx++] = 0;
143 buf[offset + idx] = (byte) ' ';
144 return offset + length;
145 }
146
147 private static void formatLongBinary(final long value, final byte[] buf, final int offset, final int length, final boolean negative) {
148 final int bits = (length - 1) * 8;
149 final long max = 1L << bits;
150 long val = Math.abs(value);
151 if (val < 0 || val >= max) {
152 throw new IllegalArgumentException("Value " + value + " is too large for " + length + " byte field.");
153 }
154 if (negative) {
155 val ^= max - 1;
156 val++;
157 val |= 0xffL << bits;
158 }
159 for (int i = offset + length - 1; i >= offset; i--) {
160 buf[i] = (byte) val;
161 val >>= 8;
162 }
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 public static int formatLongOctalBytes(final long value, final byte[] buf, final int offset, final int length) {
178 final int idx = length - 1;
179 formatUnsignedOctalString(value, buf, offset, idx);
180 buf[offset + idx] = (byte) ' ';
181 return offset + length;
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197 public static int formatLongOctalOrBinaryBytes(final long value, final byte[] buf, final int offset, final int length) {
198
199 final long maxAsOctalChar = length == TarConstants.UIDLEN ? TarConstants.MAXID : TarConstants.MAXSIZE;
200 final boolean negative = value < 0;
201 if (!negative && value <= maxAsOctalChar) {
202 return formatLongOctalBytes(value, buf, offset, length);
203 }
204 if (length < 9) {
205 formatLongBinary(value, buf, offset, length, negative);
206 } else {
207 formatBigIntegerBinary(value, buf, offset, length, negative);
208 }
209 buf[offset] = (byte) (negative ? 0xff : 0x80);
210 return offset + length;
211 }
212
213
214
215
216
217
218
219
220
221
222
223 public static int formatNameBytes(final String name, final byte[] buf, final int offset, final int length) {
224 try {
225 return formatNameBytes(name, buf, offset, length, DEFAULT_ENCODING);
226 } catch (final IOException ex) {
227 try {
228 return formatNameBytes(name, buf, offset, length, FALLBACK_ENCODING);
229 } catch (final IOException ex2) {
230
231 throw new UncheckedIOException(ex2);
232 }
233 }
234 }
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 public static int formatNameBytes(final String name, final byte[] buf, final int offset, final int length, final ZipEncoding encoding) throws IOException {
250 int len = name.length();
251 ByteBuffer b = encoding.encode(name);
252 while (b.limit() > length && len > 0) {
253 b = encoding.encode(name.substring(0, --len));
254 }
255 final int limit = b.limit() - b.position();
256 System.arraycopy(b.array(), b.arrayOffset(), buf, offset, limit);
257
258 Arrays.fill(buf, offset + limit, offset + length, (byte) 0);
259 return offset + length;
260 }
261
262
263
264
265
266
267
268
269
270
271
272
273
274 public static int formatOctalBytes(final long value, final byte[] buf, final int offset, final int length) {
275 int idx = length - 2;
276 formatUnsignedOctalString(value, buf, offset, idx);
277 buf[offset + idx++] = (byte) ' ';
278 buf[offset + idx] = 0;
279 return offset + length;
280 }
281
282
283
284
285
286
287
288
289
290
291 public static void formatUnsignedOctalString(final long value, final byte[] buffer, final int offset, final int length) {
292 int remaining = length;
293 remaining--;
294 if (value == 0) {
295 buffer[offset + remaining--] = (byte) '0';
296 } else {
297 long val = value;
298 for (; remaining >= 0 && val != 0; --remaining) {
299
300 buffer[offset + remaining] = (byte) ((byte) '0' + (byte) (val & 7));
301 val = val >>> 3;
302
303 }
304 if (val != 0) {
305 throw new IllegalArgumentException(value + "=" + Long.toOctalString(value) + " will not fit in octal number buffer of length " + length);
306 }
307 }
308 for (; remaining >= 0; --remaining) {
309 buffer[offset + remaining] = (byte) '0';
310 }
311 Arrays.fill(buffer, offset, offset + remaining + 1, (byte) '0');
312 }
313
314 private static long parseBinaryBigInteger(final byte[] buffer, final int offset, final int length, final boolean negative) {
315 final byte[] remainder = new byte[length - 1];
316 System.arraycopy(buffer, offset + 1, remainder, 0, length - 1);
317 BigInteger val = new BigInteger(remainder);
318 if (negative) {
319
320 val = val.add(NEG_1_BIG_INT).not();
321 }
322 if (val.bitLength() > 63) {
323 throw new IllegalArgumentException("At offset " + offset + ", " + length + " byte binary number exceeds maximum signed long value");
324 }
325 return negative ? -val.longValue() : val.longValue();
326 }
327
328 private static long parseBinaryLong(final byte[] buffer, final int offset, final int length, final boolean negative) {
329 if (length >= 9) {
330 throw new IllegalArgumentException("At offset " + offset + ", " + length + " byte binary number exceeds maximum signed long value");
331 }
332 long val = 0;
333 for (int i = 1; i < length; i++) {
334 val = (val << 8) + (buffer[offset + i] & 0xff);
335 }
336 if (negative) {
337
338 val--;
339 val ^= (long) Math.pow(2.0, (length - 1) * 8.0) - 1;
340 }
341 return negative ? -val : val;
342 }
343
344
345
346
347
348
349
350
351
352 public static boolean parseBoolean(final byte[] buffer, final int offset) {
353 return buffer[offset] == 1;
354 }
355
356
357
358
359
360
361
362
363
364
365 protected static List<TarArchiveStructSparse> parseFromPAX01SparseHeaders(final String sparseMap) throws IOException {
366 final List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
367 final String[] sparseHeaderStrings = sparseMap.split(",");
368 if (sparseHeaderStrings.length % 2 == 1) {
369 throw new IOException("Corrupted TAR archive. Bad format in GNU.sparse.map PAX Header");
370 }
371 for (int i = 0; i < sparseHeaderStrings.length; i += 2) {
372 final long sparseOffset = ParsingUtils.parseLongValue(sparseHeaderStrings[i]);
373 if (sparseOffset < 0) {
374 throw new IOException("Corrupted TAR archive. Sparse struct offset contains negative value");
375 }
376 final long sparseNumbytes = ParsingUtils.parseLongValue(sparseHeaderStrings[i + 1]);
377 if (sparseNumbytes < 0) {
378 throw new IOException("Corrupted TAR archive. Sparse struct numbytes contains negative value");
379 }
380 sparseHeaders.add(new TarArchiveStructSparse(sparseOffset, sparseNumbytes));
381 }
382 return Collections.unmodifiableList(sparseHeaders);
383 }
384
385
386
387
388
389
390
391
392
393 public static String parseName(final byte[] buffer, final int offset, final int length) {
394 try {
395 return parseName(buffer, offset, length, DEFAULT_ENCODING);
396 } catch (final IOException ex) {
397 try {
398 return parseName(buffer, offset, length, FALLBACK_ENCODING);
399 } catch (final IOException ex2) {
400
401 throw new UncheckedIOException(ex2);
402 }
403 }
404 }
405
406
407
408
409
410
411
412
413
414
415
416
417 public static String parseName(final byte[] buffer, final int offset, final int length, final ZipEncoding encoding) throws IOException {
418 int len = 0;
419 for (int i = offset; len < length && buffer[i] != 0; i++) {
420 len++;
421 }
422 if (len > 0) {
423 final byte[] b = new byte[len];
424 System.arraycopy(buffer, offset, b, 0, len);
425 return encoding.decode(b);
426 }
427 return "";
428 }
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451 public static long parseOctal(final byte[] buffer, final int offset, final int length) {
452 long result = 0;
453 int end = offset + length;
454 int start = offset;
455 if (length < 2) {
456 throw new IllegalArgumentException("Length " + length + " must be at least 2");
457 }
458 if (buffer[start] == 0) {
459 return 0L;
460 }
461
462 while (start < end) {
463 if (buffer[start] != ' ') {
464 break;
465 }
466 start++;
467 }
468
469
470
471
472 byte trailer = buffer[end - 1];
473 while (start < end && (trailer == 0 || trailer == ' ')) {
474 end--;
475 trailer = buffer[end - 1];
476 }
477 for (; start < end; start++) {
478 final byte currentByte = buffer[start];
479
480 if (currentByte < '0' || currentByte > '7') {
481 throw new IllegalArgumentException(exceptionMessage(buffer, offset, length, start, currentByte));
482 }
483 result = (result << 3) + (currentByte - '0');
484
485 }
486 return result;
487 }
488
489
490
491
492
493
494
495
496
497
498
499
500
501 public static long parseOctalOrBinary(final byte[] buffer, final int offset, final int length) {
502 if ((buffer[offset] & 0x80) == 0) {
503 return parseOctal(buffer, offset, length);
504 }
505 final boolean negative = buffer[offset] == (byte) 0xff;
506 if (length < 9) {
507 return parseBinaryLong(buffer, offset, length, negative);
508 }
509 return parseBinaryBigInteger(buffer, offset, length, negative);
510 }
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527 @Deprecated
528 protected static List<TarArchiveStructSparse> parsePAX01SparseHeaders(final String sparseMap) {
529 try {
530 return parseFromPAX01SparseHeaders(sparseMap);
531 } catch (final IOException ex) {
532 throw new UncheckedIOException(ex.getMessage(), ex);
533 }
534 }
535
536
537
538
539
540
541
542
543
544
545
546 protected static List<TarArchiveStructSparse> parsePAX1XSparseHeaders(final InputStream inputStream, final int recordSize) throws IOException {
547
548 final List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
549 long bytesRead = 0;
550 long[] readResult = readLineOfNumberForPax1x(inputStream);
551 long sparseHeadersCount = readResult[0];
552 if (sparseHeadersCount < 0) {
553
554 throw new IOException("Corrupted TAR archive. Negative value in sparse headers block");
555 }
556 bytesRead += readResult[1];
557 while (sparseHeadersCount-- > 0) {
558 readResult = readLineOfNumberForPax1x(inputStream);
559 final long sparseOffset = readResult[0];
560 if (sparseOffset < 0) {
561 throw new IOException("Corrupted TAR archive. Sparse header block offset contains negative value");
562 }
563 bytesRead += readResult[1];
564
565 readResult = readLineOfNumberForPax1x(inputStream);
566 final long sparseNumbytes = readResult[0];
567 if (sparseNumbytes < 0) {
568 throw new IOException("Corrupted TAR archive. Sparse header block numbytes contains negative value");
569 }
570 bytesRead += readResult[1];
571 sparseHeaders.add(new TarArchiveStructSparse(sparseOffset, sparseNumbytes));
572 }
573
574 final long bytesToSkip = recordSize - bytesRead % recordSize;
575 IOUtils.skip(inputStream, bytesToSkip);
576 return sparseHeaders;
577 }
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604 @Deprecated
605 protected static Map<String, String> parsePaxHeaders(final InputStream inputStream, final List<TarArchiveStructSparse> sparseHeaders,
606 final Map<String, String> globalPaxHeaders) throws IOException {
607 return parsePaxHeaders(inputStream, sparseHeaders, globalPaxHeaders, -1);
608 }
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636 protected static Map<String, String> parsePaxHeaders(final InputStream inputStream, final List<TarArchiveStructSparse> sparseHeaders,
637 final Map<String, String> globalPaxHeaders, final long headerSize) throws IOException {
638 final Map<String, String> headers = new HashMap<>(globalPaxHeaders);
639 Long offset = null;
640
641 int totalRead = 0;
642 while (true) {
643 int ch;
644 int len = 0;
645 int read = 0;
646 while ((ch = inputStream.read()) != -1) {
647 read++;
648 totalRead++;
649 if (ch == '\n') {
650 break;
651 }
652 if (ch == ' ') {
653
654 final ByteArrayOutputStream coll = new ByteArrayOutputStream();
655 while ((ch = inputStream.read()) != -1) {
656 read++;
657 totalRead++;
658 if (totalRead < 0 || headerSize >= 0 && totalRead >= headerSize) {
659 break;
660 }
661 if (ch == '=') {
662 final String keyword = coll.toString(StandardCharsets.UTF_8);
663
664 final int restLen = len - read;
665 if (restLen <= 1) {
666 headers.remove(keyword);
667 } else if (headerSize >= 0 && restLen > headerSize - totalRead) {
668 throw new IOException("Paxheader value size " + restLen + " exceeds size of header record");
669 } else {
670 final byte[] rest = IOUtils.readRange(inputStream, restLen);
671 final int got = rest.length;
672 if (got != restLen) {
673 throw new IOException("Failed to read Paxheader. Expected " + restLen + " bytes, read " + got);
674 }
675 totalRead += restLen;
676
677 if (rest[restLen - 1] != '\n') {
678 throw new IOException("Failed to read Paxheader.Value should end with a newline");
679 }
680 final String value = new String(rest, 0, restLen - 1, StandardCharsets.UTF_8);
681 headers.put(keyword, value);
682
683
684 if (keyword.equals(TarGnuSparseKeys.OFFSET)) {
685 if (offset != null) {
686
687 sparseHeaders.add(new TarArchiveStructSparse(offset, 0));
688 }
689 try {
690 offset = Long.valueOf(value);
691 } catch (final NumberFormatException ex) {
692 throw new IOException("Failed to read Paxheader." + TarGnuSparseKeys.OFFSET + " contains a non-numeric value");
693 }
694 if (offset < 0) {
695 throw new IOException("Failed to read Paxheader." + TarGnuSparseKeys.OFFSET + " contains negative value");
696 }
697 }
698
699
700 if (keyword.equals(TarGnuSparseKeys.NUMBYTES)) {
701 if (offset == null) {
702 throw new IOException(
703 "Failed to read Paxheader." + TarGnuSparseKeys.OFFSET + " is expected before GNU.sparse.numbytes shows up.");
704 }
705 final long numbytes = ParsingUtils.parseLongValue(value);
706 if (numbytes < 0) {
707 throw new IOException("Failed to read Paxheader." + TarGnuSparseKeys.NUMBYTES + " contains negative value");
708 }
709 sparseHeaders.add(new TarArchiveStructSparse(offset, numbytes));
710 offset = null;
711 }
712 }
713 break;
714 }
715 coll.write((byte) ch);
716 }
717 break;
718 }
719
720 if (ch < '0' || ch > '9') {
721 throw new IOException("Failed to read Paxheader. Encountered a non-number while reading length");
722 }
723 len *= 10;
724 len += ch - '0';
725 }
726 if (ch == -1) {
727 break;
728 }
729 }
730 if (offset != null) {
731
732 sparseHeaders.add(new TarArchiveStructSparse(offset, 0));
733 }
734 return headers;
735 }
736
737
738
739
740
741
742
743
744
745 public static TarArchiveStructSparse parseSparse(final byte[] buffer, final int offset) {
746 final long sparseOffset = parseOctalOrBinary(buffer, offset, TarConstants.SPARSE_OFFSET_LEN);
747 final long sparseNumbytes = parseOctalOrBinary(buffer, offset + TarConstants.SPARSE_OFFSET_LEN, TarConstants.SPARSE_NUMBYTES_LEN);
748 return new TarArchiveStructSparse(sparseOffset, sparseNumbytes);
749 }
750
751
752
753
754
755
756
757
758
759 private static long[] readLineOfNumberForPax1x(final InputStream inputStream) throws IOException {
760 int number;
761 long result = 0;
762 long bytesRead = 0;
763 while ((number = inputStream.read()) != '\n') {
764 bytesRead += 1;
765 if (number == -1) {
766 throw new IOException("Unexpected EOF when reading parse information of 1.X PAX format");
767 }
768 if (number < '0' || number > '9') {
769 throw new IOException("Corrupted TAR archive. Non-numeric value in sparse headers block");
770 }
771 result = result * 10 + (number - '0');
772 }
773 bytesRead += 1;
774 return new long[] { result, bytesRead };
775 }
776
777
778
779
780 static List<TarArchiveStructSparse> readSparseStructs(final byte[] buffer, final int offset, final int entries) throws IOException {
781 final List<TarArchiveStructSparse> sparseHeaders = new ArrayList<>();
782 for (int i = 0; i < entries; i++) {
783 try {
784 final TarArchiveStructSparse sparseHeader = parseSparse(buffer,
785 offset + i * (TarConstants.SPARSE_OFFSET_LEN + TarConstants.SPARSE_NUMBYTES_LEN));
786 if (sparseHeader.getOffset() < 0) {
787 throw new IOException("Corrupted TAR archive, sparse entry with negative offset");
788 }
789 if (sparseHeader.getNumbytes() < 0) {
790 throw new IOException("Corrupted TAR archive, sparse entry with negative numbytes");
791 }
792 sparseHeaders.add(sparseHeader);
793 } catch (final IllegalArgumentException ex) {
794
795 throw new IOException("Corrupted TAR archive, sparse entry is invalid", ex);
796 }
797 }
798 return Collections.unmodifiableList(sparseHeaders);
799 }
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817 public static boolean verifyCheckSum(final byte[] header) {
818 final long storedSum = parseOctal(header, TarConstants.CHKSUM_OFFSET, TarConstants.CHKSUMLEN);
819 long unsignedSum = 0;
820 long signedSum = 0;
821 for (int i = 0; i < header.length; i++) {
822 byte b = header[i];
823 if (TarConstants.CHKSUM_OFFSET <= i && i < TarConstants.CHKSUM_OFFSET + TarConstants.CHKSUMLEN) {
824 b = ' ';
825 }
826 unsignedSum += 0xff & b;
827 signedSum += b;
828 }
829 return storedSum == unsignedSum || storedSum == signedSum;
830 }
831
832
833 private TarUtils() {
834 }
835
836 }