2
* The Apache Software License, Version 1.1
4
* Copyright (c) 1999 The Apache Software Foundation. All rights
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
11
* 1. Redistributions of source code must retain the above copyright
12
* notice, this list of conditions and the following disclaimer.
14
* 2. Redistributions in binary form must reproduce the above copyright
15
* notice, this list of conditions and the following disclaimer in
16
* the documentation and/or other materials provided with the
19
* 3. The end-user documentation included with the redistribution, if
20
* any, must include the following acknowlegement:
21
* "This product includes software developed by the
22
* Apache Software Foundation (http://www.apache.org/)."
23
* Alternately, this acknowlegement may appear in the software itself,
24
* if and wherever such third-party acknowlegements normally appear.
26
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
27
* Foundation" must not be used to endorse or promote products derived
28
* from this software without prior written permission. For written
29
* permission, please contact apache@apache.org.
31
* 5. Products derived from this software may not be called "Apache"
32
* nor may "Apache" appear in their names without prior written
33
* permission of the Apache Group.
35
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47
* ====================================================================
49
* This software consists of voluntary contributions made by many
50
* individuals on behalf of the Apache Software Foundation. For more
51
* information on the Apache Software Foundation, please see
52
* <http://www.apache.org/>.
56
* This package is based on the work done by Timothy Gerard Endres
57
* (time@ice.com) to whom the Ant project is very grateful for his great code.
60
package org.apache.tools.tar;
65
* The TarInputStream reads a UNIX tar archive as an InputStream.
66
* methods are provided to position at each successive entry in
67
* the archive, and the read each entry as a normal input stream
70
* @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
71
* @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a>
73
public class TarInputStream extends FilterInputStream {
75
protected boolean debug;
76
protected boolean hasHitEOF;
77
protected int entrySize;
78
protected int entryOffset;
79
protected byte[] oneBuf;
80
protected byte[] readBuf;
81
protected TarBuffer buffer;
82
protected TarEntry currEntry;
84
public TarInputStream(InputStream is) {
85
this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
88
public TarInputStream(InputStream is, int blockSize) {
89
this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
92
public TarInputStream(InputStream is, int blockSize, int recordSize) {
95
this.buffer = new TarBuffer(is, blockSize, recordSize);
97
this.oneBuf = new byte[1];
99
this.hasHitEOF = false;
103
* Sets the debugging flag.
105
* @param debugF True to turn on debugging.
107
public void setDebug(boolean debug) {
109
this.buffer.setDebug(debug);
113
* Closes this stream. Calls the TarBuffer's close() method.
115
public void close() throws IOException {
120
* Get the record size being used by this stream's TarBuffer.
122
* @return The TarBuffer record size.
124
public int getRecordSize() {
125
return this.buffer.getRecordSize();
129
* Get the available data that can be read from the current
130
* entry in the archive. This does not indicate how much data
131
* is left in the entire archive, only in the current entry.
132
* This value is determined from the entry's size header field
133
* and the amount of data already read from the current entry.
136
* @return The number of available bytes for the current entry.
138
public int available() throws IOException {
139
return this.entrySize - this.entryOffset;
143
* Skip bytes in the input buffer. This skips bytes in the
144
* current entry's data, not the entire archive, and will
145
* stop at the end of the current entry's data if the number
146
* to skip extends beyond that point.
148
* @param numToSkip The number of bytes to skip.
150
public void skip(int numToSkip) throws IOException {
153
// This is horribly inefficient, but it ensures that we
154
// properly skip over bytes via the TarBuffer...
156
byte[] skipBuf = new byte[8 * 1024];
158
for (int num = numToSkip; num > 0; ) {
159
int numRead = this.read(skipBuf, 0,
160
(num > skipBuf.length ? skipBuf.length
172
* Since we do not support marking just yet, we return false.
176
public boolean markSupported() {
181
* Since we do not support marking just yet, we do nothing.
183
* @param markLimit The limit to mark.
185
public void mark(int markLimit) {}
188
* Since we do not support marking just yet, we do nothing.
190
public void reset() {}
193
* Get the next entry in this tar archive. This will skip
194
* over any remaining data in the current entry, if there
195
* is one, and place the input stream at the header of the
196
* next entry, and read the header and instantiate a new
197
* TarEntry from the header bytes and return that entry.
198
* If there are no more entries in the archive, null will
199
* be returned to indicate that the end of the archive has
202
* @return The next TarEntry in the archive, or null.
204
public TarEntry getNextEntry() throws IOException {
205
if (this.hasHitEOF) {
209
if (this.currEntry != null) {
210
int numToSkip = this.entrySize - this.entryOffset;
213
System.err.println("TarInputStream: SKIP currENTRY '"
214
+ this.currEntry.getName() + "' SZ "
215
+ this.entrySize + " OFF "
216
+ this.entryOffset + " skipping "
217
+ numToSkip + " bytes");
221
this.skip(numToSkip);
227
byte[] headerBuf = this.buffer.readRecord();
229
if (headerBuf == null) {
231
System.err.println("READ NULL RECORD");
233
this.hasHitEOF = true;
234
} else if (this.buffer.isEOFRecord(headerBuf)) {
236
System.err.println("READ EOF RECORD");
238
this.hasHitEOF = true;
241
if (this.hasHitEOF) {
242
this.currEntry = null;
244
this.currEntry = new TarEntry(headerBuf);
246
if (!(headerBuf[257] == 'u' && headerBuf[258] == 's'
247
&& headerBuf[259] == 't' && headerBuf[260] == 'a'
248
&& headerBuf[261] == 'r')) {
250
this.entryOffset = 0;
251
this.currEntry = null;
253
throw new IOException("bad header in block "
254
+ this.buffer.getCurrentBlockNum()
256
+ this.buffer.getCurrentRecordNum()
258
"header magic is not 'ustar', but '"
265
+ ((int) headerBuf[257])
267
+ ((int) headerBuf[258])
269
+ ((int) headerBuf[259])
271
+ ((int) headerBuf[260])
273
+ ((int) headerBuf[261]));
277
System.err.println("TarInputStream: SET CURRENTRY '"
278
+ this.currEntry.getName()
280
+ this.currEntry.getSize());
283
this.entryOffset = 0;
285
// REVIEW How do we resolve this discrepancy?!
286
this.entrySize = (int) this.currEntry.getSize();
289
if (this.currEntry != null && this.currEntry.isGNULongNameEntry()) {
291
StringBuffer longName = new StringBuffer();
292
byte[] buffer = new byte[256];
294
while ((length = read(buffer)) >= 0) {
295
longName.append(new String(buffer, 0, length));
298
this.currEntry.setName(longName.toString());
301
return this.currEntry;
305
* Reads a byte from the current tar archive entry.
307
* This method simply calls read( byte[], int, int ).
309
* @return The byte read, or -1 at EOF.
311
public int read() throws IOException {
312
int num = this.read(this.oneBuf, 0, 1);
317
return (int) this.oneBuf[0];
322
* Reads bytes from the current tar archive entry.
324
* This method simply calls read( byte[], int, int ).
326
* @param buf The buffer into which to place bytes read.
327
* @return The number of bytes read, or -1 at EOF.
329
public int read(byte[] buf) throws IOException {
330
return this.read(buf, 0, buf.length);
334
* Reads bytes from the current tar archive entry.
336
* This method is aware of the boundaries of the current
337
* entry in the archive and will deal with them as if they
338
* were this stream's start and EOF.
340
* @param buf The buffer into which to place bytes read.
341
* @param offset The offset at which to place bytes read.
342
* @param numToRead The number of bytes to read.
343
* @return The number of bytes read, or -1 at EOF.
345
public int read(byte[] buf, int offset, int numToRead) throws IOException {
348
if (this.entryOffset >= this.entrySize) {
352
if ((numToRead + this.entryOffset) > this.entrySize) {
353
numToRead = (this.entrySize - this.entryOffset);
356
if (this.readBuf != null) {
357
int sz = (numToRead > this.readBuf.length) ? this.readBuf.length
360
System.arraycopy(this.readBuf, 0, buf, offset, sz);
362
if (sz >= this.readBuf.length) {
365
int newLen = this.readBuf.length - sz;
366
byte[] newBuf = new byte[newLen];
368
System.arraycopy(this.readBuf, sz, newBuf, 0, newLen);
370
this.readBuf = newBuf;
378
while (numToRead > 0) {
379
byte[] rec = this.buffer.readRecord();
383
throw new IOException("unexpected EOF with " + numToRead
388
int recLen = rec.length;
391
System.arraycopy(rec, 0, buf, offset, sz);
393
this.readBuf = new byte[recLen - sz];
395
System.arraycopy(rec, sz, this.readBuf, 0, recLen - sz);
399
System.arraycopy(rec, 0, buf, offset, recLen);
407
this.entryOffset += totalRead;
413
* Copies the contents of the current tar archive entry directly into
416
* @param out The OutputStream into which to write the entry's data.
418
public void copyEntryContents(OutputStream out) throws IOException {
419
byte[] buf = new byte[32 * 1024];
422
int numRead = this.read(buf, 0, buf.length);
428
out.write(buf, 0, numRead);