1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.tar;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.io.StringWriter;
25 import java.nio.ByteBuffer;
26 import java.util.Arrays;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.Map;
30 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.ArchiveEntry;
31 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.ArchiveOutputStream;
32 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.zip.ZipEncoding;
33 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
34 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.utils.CharsetNames;
35 import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.compress.utils.CountingOutputStream;
36
37
38
39
40
41
42
43 public class TarArchiveOutputStream extends ArchiveOutputStream {
44
45 public static final int LONGFILE_ERROR = 0;
46
47
48 public static final int LONGFILE_TRUNCATE = 1;
49
50
51 public static final int LONGFILE_GNU = 2;
52
53
54 public static final int LONGFILE_POSIX = 3;
55
56
57 public static final int BIGNUMBER_ERROR = 0;
58
59
60 public static final int BIGNUMBER_STAR = 1;
61
62
63 public static final int BIGNUMBER_POSIX = 2;
64
65 private long currSize;
66 private String currName;
67 private long currBytes;
68 private final byte[] recordBuf;
69 private int assemLen;
70 private final byte[] assemBuf;
71 private int longFileMode = LONGFILE_ERROR;
72 private int bigNumberMode = BIGNUMBER_ERROR;
73 private int recordsWritten;
74 private final int recordsPerBlock;
75 private final int recordSize;
76
77 private boolean closed = false;
78
79
80 private boolean haveUnclosedEntry = false;
81
82
83 private boolean finished = false;
84
85 private final OutputStream out;
86
87 private final ZipEncoding encoding;
88
89 private boolean addPaxHeadersForNonAsciiNames = false;
90 private static final ZipEncoding ASCII =
91 ZipEncodingHelper.getZipEncoding("ASCII");
92
93
94
95
96
97 public TarArchiveOutputStream(OutputStream os) {
98 this(os, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE);
99 }
100
101
102
103
104
105
106
107 public TarArchiveOutputStream(OutputStream os, String encoding) {
108 this(os, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding);
109 }
110
111
112
113
114
115
116 public TarArchiveOutputStream(OutputStream os, int blockSize) {
117 this(os, blockSize, TarConstants.DEFAULT_RCDSIZE);
118 }
119
120
121
122
123
124
125
126
127 public TarArchiveOutputStream(OutputStream os, int blockSize,
128 String encoding) {
129 this(os, blockSize, TarConstants.DEFAULT_RCDSIZE, encoding);
130 }
131
132
133
134
135
136
137
138 public TarArchiveOutputStream(OutputStream os, int blockSize, int recordSize) {
139 this(os, blockSize, recordSize, null);
140 }
141
142
143
144
145
146
147
148
149
150 public TarArchiveOutputStream(OutputStream os, int blockSize,
151 int recordSize, String encoding) {
152 out = new CountingOutputStream(os);
153 this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
154
155 this.assemLen = 0;
156 this.assemBuf = new byte[recordSize];
157 this.recordBuf = new byte[recordSize];
158 this.recordSize = recordSize;
159 this.recordsPerBlock = blockSize / recordSize;
160 }
161
162
163
164
165
166
167
168
169 public void setLongFileMode(int longFileMode) {
170 this.longFileMode = longFileMode;
171 }
172
173
174
175
176
177
178
179
180
181 public void setBigNumberMode(int bigNumberMode) {
182 this.bigNumberMode = bigNumberMode;
183 }
184
185
186
187
188
189 public void setAddPaxHeadersForNonAsciiNames(boolean b) {
190 addPaxHeadersForNonAsciiNames = b;
191 }
192
193 @Deprecated
194 @Override
195 public int getCount() {
196 return (int) getBytesWritten();
197 }
198
199 @Override
200 public long getBytesWritten() {
201 return ((CountingOutputStream) out).getBytesWritten();
202 }
203
204
205
206
207
208
209
210
211
212
213 @Override
214 public void finish() throws IOException {
215 if (finished) {
216 throw new IOException("This archive has already been finished");
217 }
218
219 if (haveUnclosedEntry) {
220 throw new IOException("This archives contains unclosed entries.");
221 }
222 writeEOFRecord();
223 writeEOFRecord();
224 padAsNeeded();
225 out.flush();
226 finished = true;
227 }
228
229
230
231
232
233 @Override
234 public void close() throws IOException {
235 if (!finished) {
236 finish();
237 }
238
239 if (!closed) {
240 out.close();
241 closed = true;
242 }
243 }
244
245
246
247
248
249
250 public int getRecordSize() {
251 return this.recordSize;
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 @Override
268 public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException {
269 if (finished) {
270 throw new IOException("Stream has already been finished");
271 }
272 TarArchiveEntry/../../../../../../../edu/internet2/middleware/grouperInstallerExt/org/apache/commons/compress/archivers/tar/TarArchiveEntry.html#TarArchiveEntry">TarArchiveEntry entry = (TarArchiveEntry) archiveEntry;
273 Map<String, String> paxHeaders = new HashMap<String, String>();
274 final String entryName = entry.getName();
275 boolean paxHeaderContainsPath = handleLongName(entry, entryName, paxHeaders, "path",
276 TarConstants.LF_GNUTYPE_LONGNAME, "file name");
277
278 final String linkName = entry.getLinkName();
279 boolean paxHeaderContainsLinkPath = linkName != null && linkName.length() > 0
280 && handleLongName(entry, linkName, paxHeaders, "linkpath",
281 TarConstants.LF_GNUTYPE_LONGLINK, "link name");
282
283 if (bigNumberMode == BIGNUMBER_POSIX) {
284 addPaxHeadersForBigNumbers(paxHeaders, entry);
285 } else if (bigNumberMode != BIGNUMBER_STAR) {
286 failForBigNumbers(entry);
287 }
288
289 if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsPath
290 && !ASCII.canEncode(entryName)) {
291 paxHeaders.put("path", entryName);
292 }
293
294 if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsLinkPath
295 && (entry.isLink() || entry.isSymbolicLink())
296 && !ASCII.canEncode(linkName)) {
297 paxHeaders.put("linkpath", linkName);
298 }
299
300 if (paxHeaders.size() > 0) {
301 writePaxHeaders(entry, entryName, paxHeaders);
302 }
303
304 entry.writeEntryHeader(recordBuf, encoding,
305 bigNumberMode == BIGNUMBER_STAR);
306 writeRecord(recordBuf);
307
308 currBytes = 0;
309
310 if (entry.isDirectory()) {
311 currSize = 0;
312 } else {
313 currSize = entry.getSize();
314 }
315 currName = entryName;
316 haveUnclosedEntry = true;
317 }
318
319
320
321
322
323
324
325
326
327
328
329 @Override
330 public void closeArchiveEntry() throws IOException {
331 if (finished) {
332 throw new IOException("Stream has already been finished");
333 }
334 if (!haveUnclosedEntry){
335 throw new IOException("No current entry to close");
336 }
337 if (assemLen > 0) {
338 for (int i = assemLen; i < assemBuf.length; ++i) {
339 assemBuf[i] = 0;
340 }
341
342 writeRecord(assemBuf);
343
344 currBytes += assemLen;
345 assemLen = 0;
346 }
347
348 if (currBytes < currSize) {
349 throw new IOException("entry '" + currName + "' closed at '"
350 + currBytes
351 + "' before the '" + currSize
352 + "' bytes specified in the header were written");
353 }
354 haveUnclosedEntry = false;
355 }
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371 @Override
372 public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
373 if (!haveUnclosedEntry) {
374 throw new IllegalStateException("No current tar entry");
375 }
376 if (currBytes + numToWrite > currSize) {
377 throw new IOException("request to write '" + numToWrite
378 + "' bytes exceeds size in header of '"
379 + currSize + "' bytes for entry '"
380 + currName + "'");
381
382
383
384
385
386
387
388
389 }
390
391 if (assemLen > 0) {
392 if (assemLen + numToWrite >= recordBuf.length) {
393 int aLen = recordBuf.length - assemLen;
394
395 System.arraycopy(assemBuf, 0, recordBuf, 0,
396 assemLen);
397 System.arraycopy(wBuf, wOffset, recordBuf,
398 assemLen, aLen);
399 writeRecord(recordBuf);
400
401 currBytes += recordBuf.length;
402 wOffset += aLen;
403 numToWrite -= aLen;
404 assemLen = 0;
405 } else {
406 System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
407 numToWrite);
408
409 wOffset += numToWrite;
410 assemLen += numToWrite;
411 numToWrite = 0;
412 }
413 }
414
415
416
417
418
419
420 while (numToWrite > 0) {
421 if (numToWrite < recordBuf.length) {
422 System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
423 numToWrite);
424
425 assemLen += numToWrite;
426
427 break;
428 }
429
430 writeRecord(wBuf, wOffset);
431
432 int num = recordBuf.length;
433
434 currBytes += num;
435 numToWrite -= num;
436 wOffset += num;
437 }
438 }
439
440
441
442
443
444 void writePaxHeaders(TarArchiveEntry entry,
445 String entryName,
446 Map<String, String> headers) throws IOException {
447 String name = "./PaxHeaders.X/" + stripTo7Bits(entryName);
448 if (name.length() >= TarConstants.NAMELEN) {
449 name = name.substring(0, TarConstants.NAMELEN - 1);
450 }
451 TarArchiveEntryddleware/grouperInstallerExt/org/apache/commons/compress/archivers/tar/TarArchiveEntry.html#TarArchiveEntry">TarArchiveEntry pex = new TarArchiveEntry(name,
452 TarConstants.LF_PAX_EXTENDED_HEADER_LC);
453 transferModTime(entry, pex);
454
455 StringWriter w = new StringWriter();
456 for (Map.Entry<String, String> h : headers.entrySet()) {
457 String key = h.getKey();
458 String value = h.getValue();
459 int len = key.length() + value.length()
460 + 3
461 + 2 ;
462 String line = len + " " + key + "=" + value + "\n";
463 int actualLength = line.getBytes(CharsetNames.UTF_8).length;
464 while (len != actualLength) {
465
466
467
468
469
470 len = actualLength;
471 line = len + " " + key + "=" + value + "\n";
472 actualLength = line.getBytes(CharsetNames.UTF_8).length;
473 }
474 w.write(line);
475 }
476 byte[] data = w.toString().getBytes(CharsetNames.UTF_8);
477 pex.setSize(data.length);
478 putArchiveEntry(pex);
479 write(data);
480 closeArchiveEntry();
481 }
482
483 private String stripTo7Bits(String name) {
484 final int length = name.length();
485 StringBuilder result = new StringBuilder(length);
486 for (int i = 0; i < length; i++) {
487 char stripped = (char) (name.charAt(i) & 0x7F);
488 if (shouldBeReplaced(stripped)) {
489 result.append("_");
490 } else {
491 result.append(stripped);
492 }
493 }
494 return result.toString();
495 }
496
497
498
499
500
501 private boolean shouldBeReplaced(char c) {
502 return c == 0
503 || c == '/'
504 || c == '\\';
505 }
506
507
508
509
510
511 private void writeEOFRecord() throws IOException {
512 Arrays.fill(recordBuf, (byte) 0);
513 writeRecord(recordBuf);
514 }
515
516 @Override
517 public void flush() throws IOException {
518 out.flush();
519 }
520
521 @Override
522 public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
523 throws IOException {
524 if(finished) {
525 throw new IOException("Stream has already been finished");
526 }
527 return new TarArchiveEntry(inputFile, entryName);
528 }
529
530
531
532
533
534
535
536 private void writeRecord(byte[] record) throws IOException {
537 if (record.length != recordSize) {
538 throw new IOException("record to write has length '"
539 + record.length
540 + "' which is not the record size of '"
541 + recordSize + "'");
542 }
543
544 out.write(record);
545 recordsWritten++;
546 }
547
548
549
550
551
552
553
554
555
556
557 private void writeRecord(byte[] buf, int offset) throws IOException {
558
559 if (offset + recordSize > buf.length) {
560 throw new IOException("record has length '" + buf.length
561 + "' with offset '" + offset
562 + "' which is less than the record size of '"
563 + recordSize + "'");
564 }
565
566 out.write(buf, offset, recordSize);
567 recordsWritten++;
568 }
569
570 private void padAsNeeded() throws IOException {
571 int start = recordsWritten % recordsPerBlock;
572 if (start != 0) {
573 for (int i = start; i < recordsPerBlock; i++) {
574 writeEOFRecord();
575 }
576 }
577 }
578
579 private void addPaxHeadersForBigNumbers(Map<String, String> paxHeaders,
580 TarArchiveEntry entry) {
581 addPaxHeaderForBigNumber(paxHeaders, "size", entry.getSize(),
582 TarConstants.MAXSIZE);
583 addPaxHeaderForBigNumber(paxHeaders, "gid", entry.getGroupId(),
584 TarConstants.MAXID);
585 addPaxHeaderForBigNumber(paxHeaders, "mtime",
586 entry.getModTime().getTime() / 1000,
587 TarConstants.MAXSIZE);
588 addPaxHeaderForBigNumber(paxHeaders, "uid", entry.getUserId(),
589 TarConstants.MAXID);
590
591 addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devmajor",
592 entry.getDevMajor(), TarConstants.MAXID);
593 addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devminor",
594 entry.getDevMinor(), TarConstants.MAXID);
595
596 failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
597 }
598
599 private void addPaxHeaderForBigNumber(Map<String, String> paxHeaders,
600 String header, long value,
601 long maxValue) {
602 if (value < 0 || value > maxValue) {
603 paxHeaders.put(header, String.valueOf(value));
604 }
605 }
606
607 private void failForBigNumbers(TarArchiveEntry entry) {
608 failForBigNumber("entry size", entry.getSize(), TarConstants.MAXSIZE);
609 failForBigNumber("group id", entry.getGroupId(), TarConstants.MAXID);
610 failForBigNumber("last modification time",
611 entry.getModTime().getTime() / 1000,
612 TarConstants.MAXSIZE);
613 failForBigNumber("user id", entry.getUserId(), TarConstants.MAXID);
614 failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
615 failForBigNumber("major device number", entry.getDevMajor(),
616 TarConstants.MAXID);
617 failForBigNumber("minor device number", entry.getDevMinor(),
618 TarConstants.MAXID);
619 }
620
621 private void failForBigNumber(String field, long value, long maxValue) {
622 if (value < 0 || value > maxValue) {
623 throw new RuntimeException(field + " '" + value
624 + "' is too big ( > "
625 + maxValue + " )");
626 }
627 }
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651 private boolean handleLongName(TarArchiveEntry entry , String name,
652 Map<String, String> paxHeaders,
653 String paxHeaderName, byte linkType, String fieldName)
654 throws IOException {
655 final ByteBuffer encodedName = encoding.encode(name);
656 final int len = encodedName.limit() - encodedName.position();
657 if (len >= TarConstants.NAMELEN) {
658
659 if (longFileMode == LONGFILE_POSIX) {
660 paxHeaders.put(paxHeaderName, name);
661 return true;
662 } else if (longFileMode == LONGFILE_GNU) {
663
664
665 TarArchiveEntryrouperInstallerExt/org/apache/commons/compress/archivers/tar/TarArchiveEntry.html#TarArchiveEntry">TarArchiveEntry longLinkEntry = new TarArchiveEntry(TarConstants.GNU_LONGLINK, linkType);
666
667 longLinkEntry.setSize(len + 1);
668 transferModTime(entry, longLinkEntry);
669 putArchiveEntry(longLinkEntry);
670 write(encodedName.array(), encodedName.arrayOffset(), len);
671 write(0);
672 closeArchiveEntry();
673 } else if (longFileMode != LONGFILE_TRUNCATE) {
674 throw new RuntimeException(fieldName + " '" + name
675 + "' is too long ( > "
676 + TarConstants.NAMELEN + " bytes)");
677 }
678 }
679 return false;
680 }
681
682 private void transferModTime(TarArchiveEntry/../../../../../../../../edu/internet2/middleware/grouperInstallerExt/org/apache/commons/compress/archivers/tar/TarArchiveEntry.html#TarArchiveEntry">TarArchiveEntry from, TarArchiveEntry to) {
683 Date fromModTime = from.getModTime();
684 long fromModTimeSeconds = fromModTime.getTime() / 1000;
685 if (fromModTimeSeconds < 0 || fromModTimeSeconds > TarConstants.MAXSIZE) {
686 fromModTime = new Date(0);
687 }
688 to.setModTime(fromModTime);
689 }
690 }