001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: /*
020: * This package is based on the work done by Timothy Gerard Endres
021: * (time@ice.com) to whom the Ant project is very grateful for his great code.
022: */
023:
024: package org.apache.tools.tar;
025:
026: import java.io.FilterInputStream;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.OutputStream;
030:
031: /**
032: * The TarInputStream reads a UNIX tar archive as an InputStream.
033: * methods are provided to position at each successive entry in
034: * the archive, and the read each entry as a normal input stream
035: * using read().
036: *
037: */
038: public class TarInputStream extends FilterInputStream {
039:
040: // CheckStyle:VisibilityModifier OFF - bc
041: protected boolean debug;
042: protected boolean hasHitEOF;
043: protected long entrySize;
044: protected long entryOffset;
045: protected byte[] readBuf;
046: protected TarBuffer buffer;
047: protected TarEntry currEntry;
048:
049: /**
050: * This contents of this array is not used at all in this class,
051: * it is only here to avoid repreated object creation during calls
052: * to the no-arg read method.
053: */
054: protected byte[] oneBuf;
055:
056: // CheckStyle:VisibilityModifier ON
057:
058: /**
059: * Constructor for TarInputStream.
060: * @param is the input stream to use
061: */
062: public TarInputStream(InputStream is) {
063: this (is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
064: }
065:
066: /**
067: * Constructor for TarInputStream.
068: * @param is the input stream to use
069: * @param blockSize the block size to use
070: */
071: public TarInputStream(InputStream is, int blockSize) {
072: this (is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
073: }
074:
075: /**
076: * Constructor for TarInputStream.
077: * @param is the input stream to use
078: * @param blockSize the block size to use
079: * @param recordSize the record size to use
080: */
081: public TarInputStream(InputStream is, int blockSize, int recordSize) {
082: super (is);
083:
084: this .buffer = new TarBuffer(is, blockSize, recordSize);
085: this .readBuf = null;
086: this .oneBuf = new byte[1];
087: this .debug = false;
088: this .hasHitEOF = false;
089: }
090:
091: /**
092: * Sets the debugging flag.
093: *
094: * @param debug True to turn on debugging.
095: */
096: public void setDebug(boolean debug) {
097: this .debug = debug;
098: this .buffer.setDebug(debug);
099: }
100:
101: /**
102: * Closes this stream. Calls the TarBuffer's close() method.
103: * @throws IOException on error
104: */
105: public void close() throws IOException {
106: this .buffer.close();
107: }
108:
109: /**
110: * Get the record size being used by this stream's TarBuffer.
111: *
112: * @return The TarBuffer record size.
113: */
114: public int getRecordSize() {
115: return this .buffer.getRecordSize();
116: }
117:
118: /**
119: * Get the available data that can be read from the current
120: * entry in the archive. This does not indicate how much data
121: * is left in the entire archive, only in the current entry.
122: * This value is determined from the entry's size header field
123: * and the amount of data already read from the current entry.
124: * Integer.MAX_VALUE is returen in case more than Integer.MAX_VALUE
125: * bytes are left in the current entry in the archive.
126: *
127: * @return The number of available bytes for the current entry.
128: * @throws IOException for signature
129: */
130: public int available() throws IOException {
131: if (this .entrySize - this .entryOffset > Integer.MAX_VALUE) {
132: return Integer.MAX_VALUE;
133: }
134: return (int) (this .entrySize - this .entryOffset);
135: }
136:
137: /**
138: * Skip bytes in the input buffer. This skips bytes in the
139: * current entry's data, not the entire archive, and will
140: * stop at the end of the current entry's data if the number
141: * to skip extends beyond that point.
142: *
143: * @param numToSkip The number of bytes to skip.
144: * @return the number actually skipped
145: * @throws IOException on error
146: */
147: public long skip(long numToSkip) throws IOException {
148: // REVIEW
149: // This is horribly inefficient, but it ensures that we
150: // properly skip over bytes via the TarBuffer...
151: //
152: byte[] skipBuf = new byte[8 * 1024];
153: long skip = numToSkip;
154: while (skip > 0) {
155: int realSkip = (int) (skip > skipBuf.length ? skipBuf.length
156: : skip);
157: int numRead = this .read(skipBuf, 0, realSkip);
158: if (numRead == -1) {
159: break;
160: }
161: skip -= numRead;
162: }
163: return (numToSkip - skip);
164: }
165:
166: /**
167: * Since we do not support marking just yet, we return false.
168: *
169: * @return False.
170: */
171: public boolean markSupported() {
172: return false;
173: }
174:
175: /**
176: * Since we do not support marking just yet, we do nothing.
177: *
178: * @param markLimit The limit to mark.
179: */
180: public void mark(int markLimit) {
181: }
182:
183: /**
184: * Since we do not support marking just yet, we do nothing.
185: */
186: public void reset() {
187: }
188:
189: /**
190: * Get the next entry in this tar archive. This will skip
191: * over any remaining data in the current entry, if there
192: * is one, and place the input stream at the header of the
193: * next entry, and read the header and instantiate a new
194: * TarEntry from the header bytes and return that entry.
195: * If there are no more entries in the archive, null will
196: * be returned to indicate that the end of the archive has
197: * been reached.
198: *
199: * @return The next TarEntry in the archive, or null.
200: * @throws IOException on error
201: */
202: public TarEntry getNextEntry() throws IOException {
203: if (this .hasHitEOF) {
204: return null;
205: }
206:
207: if (this .currEntry != null) {
208: long numToSkip = this .entrySize - this .entryOffset;
209:
210: if (this .debug) {
211: System.err.println("TarInputStream: SKIP currENTRY '"
212: + this .currEntry.getName() + "' SZ "
213: + this .entrySize + " OFF " + this .entryOffset
214: + " skipping " + numToSkip + " bytes");
215: }
216:
217: if (numToSkip > 0) {
218: this .skip(numToSkip);
219: }
220:
221: this .readBuf = null;
222: }
223:
224: byte[] headerBuf = this .buffer.readRecord();
225:
226: if (headerBuf == null) {
227: if (this .debug) {
228: System.err.println("READ NULL RECORD");
229: }
230: this .hasHitEOF = true;
231: } else if (this .buffer.isEOFRecord(headerBuf)) {
232: if (this .debug) {
233: System.err.println("READ EOF RECORD");
234: }
235: this .hasHitEOF = true;
236: }
237:
238: if (this .hasHitEOF) {
239: this .currEntry = null;
240: } else {
241: this .currEntry = new TarEntry(headerBuf);
242:
243: if (this .debug) {
244: System.err.println("TarInputStream: SET CURRENTRY '"
245: + this .currEntry.getName() + "' size = "
246: + this .currEntry.getSize());
247: }
248:
249: this .entryOffset = 0;
250:
251: this .entrySize = this .currEntry.getSize();
252: }
253:
254: if (this .currEntry != null
255: && this .currEntry.isGNULongNameEntry()) {
256: // read in the name
257: StringBuffer longName = new StringBuffer();
258: byte[] buf = new byte[256];
259: int length = 0;
260: while ((length = read(buf)) >= 0) {
261: longName.append(new String(buf, 0, length));
262: }
263: getNextEntry();
264: if (this .currEntry == null) {
265: // Bugzilla: 40334
266: // Malformed tar file - long entry name not followed by entry
267: return null;
268: }
269: // remove trailing null terminator
270: if (longName.length() > 0
271: && longName.charAt(longName.length() - 1) == 0) {
272: longName.deleteCharAt(longName.length() - 1);
273: }
274: this .currEntry.setName(longName.toString());
275: }
276:
277: return this .currEntry;
278: }
279:
280: /**
281: * Reads a byte from the current tar archive entry.
282: *
283: * This method simply calls read( byte[], int, int ).
284: *
285: * @return The byte read, or -1 at EOF.
286: * @throws IOException on error
287: */
288: public int read() throws IOException {
289: int num = this .read(this .oneBuf, 0, 1);
290: return num == -1 ? -1 : ((int) this .oneBuf[0]) & 0xFF;
291: }
292:
293: /**
294: * Reads bytes from the current tar archive entry.
295: *
296: * This method is aware of the boundaries of the current
297: * entry in the archive and will deal with them as if they
298: * were this stream's start and EOF.
299: *
300: * @param buf The buffer into which to place bytes read.
301: * @param offset The offset at which to place bytes read.
302: * @param numToRead The number of bytes to read.
303: * @return The number of bytes read, or -1 at EOF.
304: * @throws IOException on error
305: */
306: public int read(byte[] buf, int offset, int numToRead)
307: throws IOException {
308: int totalRead = 0;
309:
310: if (this .entryOffset >= this .entrySize) {
311: return -1;
312: }
313:
314: if ((numToRead + this .entryOffset) > this .entrySize) {
315: numToRead = (int) (this .entrySize - this .entryOffset);
316: }
317:
318: if (this .readBuf != null) {
319: int sz = (numToRead > this .readBuf.length) ? this .readBuf.length
320: : numToRead;
321:
322: System.arraycopy(this .readBuf, 0, buf, offset, sz);
323:
324: if (sz >= this .readBuf.length) {
325: this .readBuf = null;
326: } else {
327: int newLen = this .readBuf.length - sz;
328: byte[] newBuf = new byte[newLen];
329:
330: System.arraycopy(this .readBuf, sz, newBuf, 0, newLen);
331:
332: this .readBuf = newBuf;
333: }
334:
335: totalRead += sz;
336: numToRead -= sz;
337: offset += sz;
338: }
339:
340: while (numToRead > 0) {
341: byte[] rec = this .buffer.readRecord();
342:
343: if (rec == null) {
344: // Unexpected EOF!
345: throw new IOException("unexpected EOF with "
346: + numToRead + " bytes unread");
347: }
348:
349: int sz = numToRead;
350: int recLen = rec.length;
351:
352: if (recLen > sz) {
353: System.arraycopy(rec, 0, buf, offset, sz);
354:
355: this .readBuf = new byte[recLen - sz];
356:
357: System.arraycopy(rec, sz, this .readBuf, 0, recLen - sz);
358: } else {
359: sz = recLen;
360:
361: System.arraycopy(rec, 0, buf, offset, recLen);
362: }
363:
364: totalRead += sz;
365: numToRead -= sz;
366: offset += sz;
367: }
368:
369: this .entryOffset += totalRead;
370:
371: return totalRead;
372: }
373:
374: /**
375: * Copies the contents of the current tar archive entry directly into
376: * an output stream.
377: *
378: * @param out The OutputStream into which to write the entry's data.
379: * @throws IOException on error
380: */
381: public void copyEntryContents(OutputStream out) throws IOException {
382: byte[] buf = new byte[32 * 1024];
383:
384: while (true) {
385: int numRead = this .read(buf, 0, buf.length);
386:
387: if (numRead == -1) {
388: break;
389: }
390:
391: out.write(buf, 0, numRead);
392: }
393: }
394: }
|