1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.tar;
25
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Map.Entry;
32
33 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.ArchiveEntry;
34 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.ArchiveInputStream;
35 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.zip.ZipEncoding;
36 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
37 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.utils.ArchiveUtils;
38 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.utils.CharsetNames;
39 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.utils.IOUtils;
40
41
42
43
44
45
46
47
48 public class TarArchiveInputStream extends ArchiveInputStream {
49
50 private static final int SMALL_BUFFER_SIZE = 256;
51
52 private final byte[] SMALL_BUF = new byte[SMALL_BUFFER_SIZE];
53
54
55 private final int recordSize;
56
57
58 private final int blockSize;
59
60
61 private boolean hasHitEOF;
62
63
64 private long entrySize;
65
66
67 private long entryOffset;
68
69
70 private final InputStream is;
71
72
73 private TarArchiveEntry currEntry;
74
75
76 private final ZipEncoding encoding;
77
78
79
80
81
82 public TarArchiveInputStream(InputStream is) {
83 this(is, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE);
84 }
85
86
87
88
89
90
91
92 public TarArchiveInputStream(InputStream is, String encoding) {
93 this(is, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE,
94 encoding);
95 }
96
97
98
99
100
101
102 public TarArchiveInputStream(InputStream is, int blockSize) {
103 this(is, blockSize, TarConstants.DEFAULT_RCDSIZE);
104 }
105
106
107
108
109
110
111
112
113 public TarArchiveInputStream(InputStream is, int blockSize,
114 String encoding) {
115 this(is, blockSize, TarConstants.DEFAULT_RCDSIZE, encoding);
116 }
117
118
119
120
121
122
123
124 public TarArchiveInputStream(InputStream is, int blockSize, int recordSize) {
125 this(is, blockSize, recordSize, null);
126 }
127
128
129
130
131
132
133
134
135
136 public TarArchiveInputStream(InputStream is, int blockSize, int recordSize,
137 String encoding) {
138 this.is = is;
139 this.hasHitEOF = false;
140 this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
141 this.recordSize = recordSize;
142 this.blockSize = blockSize;
143 }
144
145
146
147
148
149 @Override
150 public void close() throws IOException {
151 is.close();
152 }
153
154
155
156
157
158
159 public int getRecordSize() {
160 return recordSize;
161 }
162
163
164
165
166
167
168
169
170
171
172
173
174
175 @Override
176 public int available() throws IOException {
177 if (entrySize - entryOffset > Integer.MAX_VALUE) {
178 return Integer.MAX_VALUE;
179 }
180 return (int) (entrySize - entryOffset);
181 }
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200 @Override
201 public long skip(final long n) throws IOException {
202 if (n <= 0) {
203 return 0;
204 }
205
206 final long available = entrySize - entryOffset;
207 final long skipped = is.skip(Math.min(n, available));
208 count(skipped);
209 entryOffset += skipped;
210 return skipped;
211 }
212
213
214
215
216
217
218 @Override
219 public boolean markSupported() {
220 return false;
221 }
222
223
224
225
226
227
228 @Override
229 public void mark(int markLimit) {
230 }
231
232
233
234
235 @Override
236 public synchronized void reset() {
237 }
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252 public TarArchiveEntry getNextTarEntry() throws IOException {
253 if (hasHitEOF) {
254 return null;
255 }
256
257 if (currEntry != null) {
258
259 IOUtils.skip(this, Long.MAX_VALUE);
260
261
262 skipRecordPadding();
263 }
264
265 byte[] headerBuf = getRecord();
266
267 if (headerBuf == null) {
268
269 currEntry = null;
270 return null;
271 }
272
273 try {
274 currEntry = new TarArchiveEntry(headerBuf, encoding);
275 } catch (IllegalArgumentException e) {
276 IOException ioe = new IOException("Error detected parsing the header");
277 ioe.initCause(e);
278 throw ioe;
279 }
280
281 entryOffset = 0;
282 entrySize = currEntry.getSize();
283
284 if (currEntry.isGNULongLinkEntry()) {
285 byte[] longLinkData = getLongNameData();
286 if (longLinkData == null) {
287
288
289
290 return null;
291 }
292 currEntry.setLinkName(encoding.decode(longLinkData));
293 }
294
295 if (currEntry.isGNULongNameEntry()) {
296 byte[] longNameData = getLongNameData();
297 if (longNameData == null) {
298
299
300
301 return null;
302 }
303 currEntry.setName(encoding.decode(longNameData));
304 }
305
306 if (currEntry.isPaxHeader()){
307 paxHeaders();
308 }
309
310 if (currEntry.isGNUSparse()){
311 readGNUSparse();
312 }
313
314
315
316
317
318 entrySize = currEntry.getSize();
319
320 return currEntry;
321 }
322
323
324
325
326
327 private void skipRecordPadding() throws IOException {
328 if (this.entrySize > 0 && this.entrySize % this.recordSize != 0) {
329 long numRecords = (this.entrySize / this.recordSize) + 1;
330 long padding = (numRecords * this.recordSize) - this.entrySize;
331 long skipped = IOUtils.skip(is, padding);
332 count(skipped);
333 }
334 }
335
336
337
338
339
340
341
342 protected byte[] getLongNameData() throws IOException {
343
344 ByteArrayOutputStream longName = new ByteArrayOutputStream();
345 int length = 0;
346 while ((length = read(SMALL_BUF)) >= 0) {
347 longName.write(SMALL_BUF, 0, length);
348 }
349 getNextEntry();
350 if (currEntry == null) {
351
352
353 return null;
354 }
355 byte[] longNameData = longName.toByteArray();
356
357 length = longNameData.length;
358 while (length > 0 && longNameData[length - 1] == 0) {
359 --length;
360 }
361 if (length != longNameData.length) {
362 byte[] l = new byte[length];
363 System.arraycopy(longNameData, 0, l, 0, length);
364 longNameData = l;
365 }
366 return longNameData;
367 }
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383 private byte[] getRecord() throws IOException {
384 byte[] headerBuf = readRecord();
385 hasHitEOF = isEOFRecord(headerBuf);
386 if (hasHitEOF && headerBuf != null) {
387 tryToConsumeSecondEOFRecord();
388 consumeRemainderOfLastBlock();
389 headerBuf = null;
390 }
391 return headerBuf;
392 }
393
394
395
396
397
398
399
400
401 protected boolean isEOFRecord(byte[] record) {
402 return record == null || ArchiveUtils.isArrayZero(record, recordSize);
403 }
404
405
406
407
408
409
410
411 protected byte[] readRecord() throws IOException {
412
413 byte[] record = new byte[recordSize];
414
415 int readNow = IOUtils.readFully(is, record);
416 count(readNow);
417 if (readNow != recordSize) {
418 return null;
419 }
420
421 return record;
422 }
423
424 private void paxHeaders() throws IOException{
425 Map<String, String> headers = parsePaxHeaders(this);
426 getNextEntry();
427 applyPaxHeadersToCurrentEntry(headers);
428 }
429
430 Map<String, String> parsePaxHeaders(InputStream i) throws IOException {
431 Map<String, String> headers = new HashMap<String, String>();
432
433 while(true){
434 int ch;
435 int len = 0;
436 int read = 0;
437 while((ch = i.read()) != -1) {
438 read++;
439 if (ch == ' '){
440
441 ByteArrayOutputStream coll = new ByteArrayOutputStream();
442 while((ch = i.read()) != -1) {
443 read++;
444 if (ch == '='){
445 String keyword = coll.toString(CharsetNames.UTF_8);
446
447 final int restLen = len - read;
448 byte[] rest = new byte[restLen];
449 int got = IOUtils.readFully(i, rest);
450 if (got != restLen) {
451 throw new IOException("Failed to read "
452 + "Paxheader. Expected "
453 + restLen
454 + " bytes, read "
455 + got);
456 }
457
458 String value = new String(rest, 0,
459 restLen - 1, CharsetNames.UTF_8);
460 headers.put(keyword, value);
461 break;
462 }
463 coll.write((byte) ch);
464 }
465 break;
466 }
467 len *= 10;
468 len += ch - '0';
469 }
470 if (ch == -1){
471 break;
472 }
473 }
474 return headers;
475 }
476
477 private void applyPaxHeadersToCurrentEntry(Map<String, String> headers) {
478
479
480
481
482
483
484
485
486
487
488
489 for (Entry<String, String> ent : headers.entrySet()){
490 String key = ent.getKey();
491 String val = ent.getValue();
492 if ("path".equals(key)){
493 currEntry.setName(val);
494 } else if ("linkpath".equals(key)){
495 currEntry.setLinkName(val);
496 } else if ("gid".equals(key)){
497 currEntry.setGroupId(Integer.parseInt(val));
498 } else if ("gname".equals(key)){
499 currEntry.setGroupName(val);
500 } else if ("uid".equals(key)){
501 currEntry.setUserId(Integer.parseInt(val));
502 } else if ("uname".equals(key)){
503 currEntry.setUserName(val);
504 } else if ("size".equals(key)){
505 currEntry.setSize(Long.parseLong(val));
506 } else if ("mtime".equals(key)){
507 currEntry.setModTime((long) (Double.parseDouble(val) * 1000));
508 } else if ("SCHILY.devminor".equals(key)){
509 currEntry.setDevMinor(Integer.parseInt(val));
510 } else if ("SCHILY.devmajor".equals(key)){
511 currEntry.setDevMajor(Integer.parseInt(val));
512 }
513 }
514 }
515
516
517
518
519
520
521
522
523
524 private void readGNUSparse() throws IOException {
525
526
527
528
529 if (currEntry.isExtended()) {
530 TarArchiveSparseEntry entry;
531 do {
532 byte[] headerBuf = getRecord();
533 if (headerBuf == null) {
534 currEntry = null;
535 break;
536 }
537 entry = new TarArchiveSparseEntry(headerBuf);
538
539
540
541 } while (entry.isExtended());
542 }
543 }
544
545
546
547
548
549
550
551
552 @Override
553 public ArchiveEntry getNextEntry() throws IOException {
554 return getNextTarEntry();
555 }
556
557
558
559
560
561
562
563
564
565
566
567 private void tryToConsumeSecondEOFRecord() throws IOException {
568 boolean shouldReset = true;
569 boolean marked = is.markSupported();
570 if (marked) {
571 is.mark(recordSize);
572 }
573 try {
574 shouldReset = !isEOFRecord(readRecord());
575 } finally {
576 if (shouldReset && marked) {
577 pushedBackBytes(recordSize);
578 is.reset();
579 }
580 }
581 }
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596 @Override
597 public int read(byte[] buf, int offset, int numToRead) throws IOException {
598 int totalRead = 0;
599
600 if (hasHitEOF || entryOffset >= entrySize) {
601 return -1;
602 }
603
604 if (currEntry == null) {
605 throw new IllegalStateException("No current tar entry");
606 }
607
608 numToRead = Math.min(numToRead, available());
609
610 totalRead = is.read(buf, offset, numToRead);
611
612 if (totalRead == -1) {
613 if (numToRead > 0) {
614 throw new IOException("Truncated TAR archive");
615 }
616 hasHitEOF = true;
617 } else {
618 count(totalRead);
619 entryOffset += totalRead;
620 }
621
622 return totalRead;
623 }
624
625
626
627
628
629
630 @Override
631 public boolean canReadEntryData(ArchiveEntry ae) {
632 if (ae instanceof TarArchiveEntry) {
633 TarArchiveEntry/../../../../../../../../edu/internet2/middleware/grouperInstallerExt/org/apache/commons/compress/archivers/tar/TarArchiveEntry.html#TarArchiveEntry">TarArchiveEntry te = (TarArchiveEntry) ae;
634 return !te.isGNUSparse();
635 }
636 return false;
637 }
638
639
640
641
642
643
644 public TarArchiveEntry getCurrentEntry() {
645 return currEntry;
646 }
647
648 protected final void setCurrentEntry(TarArchiveEntry e) {
649 currEntry = e;
650 }
651
652 protected final boolean isAtEOF() {
653 return hasHitEOF;
654 }
655
656 protected final void setAtEOF(boolean b) {
657 hasHitEOF = b;
658 }
659
660
661
662
663
664
665 private void consumeRemainderOfLastBlock() throws IOException {
666 long bytesReadOfLastBlock = getBytesRead() % blockSize;
667 if (bytesReadOfLastBlock > 0) {
668 long skipped = IOUtils.skip(is, blockSize - bytesReadOfLastBlock);
669 count(skipped);
670 }
671 }
672
673
674
675
676
677
678
679
680
681
682 public static boolean matches(byte[] signature, int length) {
683 if (length < TarConstants.VERSION_OFFSET+TarConstants.VERSIONLEN) {
684 return false;
685 }
686
687 if (ArchiveUtils.matchAsciiBuffer(TarConstants.MAGIC_POSIX,
688 signature, TarConstants.MAGIC_OFFSET, TarConstants.MAGICLEN)
689 &&
690 ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_POSIX,
691 signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN)
692 ){
693 return true;
694 }
695 if (ArchiveUtils.matchAsciiBuffer(TarConstants.MAGIC_GNU,
696 signature, TarConstants.MAGIC_OFFSET, TarConstants.MAGICLEN)
697 &&
698 (
699 ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_GNU_SPACE,
700 signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN)
701 ||
702 ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_GNU_ZERO,
703 signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN)
704 )
705 ){
706 return true;
707 }
708
709 if (ArchiveUtils.matchAsciiBuffer(TarConstants.MAGIC_ANT,
710 signature, TarConstants.MAGIC_OFFSET, TarConstants.MAGICLEN)
711 &&
712 ArchiveUtils.matchAsciiBuffer(TarConstants.VERSION_ANT,
713 signature, TarConstants.VERSION_OFFSET, TarConstants.VERSIONLEN)
714 ){
715 return true;
716 }
717 return false;
718 }
719
720 }