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: package org.apache.commons.vfs.provider.tar;
018:
019: import java.io.FilterInputStream;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.OutputStream;
023:
024: /**
025: * The TarInputStream reads a UNIX tar archive as an InputStream. methods are
026: * provided to position at each successive entry in the archive, and the read
027: * each entry as a normal input stream using read().
028: *
029: * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
030: * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
031: * @author <a href="mailto:peter@apache.org">Peter Donald</a>
032: * @version $Revision: 480428 $ $Date: 2006-11-28 22:15:24 -0800 (Tue, 28 Nov 2006) $
033: * @see TarInputStream
034: * @see TarEntry
035: */
036: class TarInputStream extends FilterInputStream {
037: private TarBuffer m_buffer;
038: private TarEntry m_currEntry;
039: private boolean m_debug;
040: private int m_entryOffset;
041: private int m_entrySize;
042: private boolean m_hasHitEOF;
043: private byte[] m_oneBuf;
044: private byte[] m_readBuf;
045:
046: /**
047: * Construct a TarInputStream using specified input
048: * stream and default block and record sizes.
049: *
050: * @param input stream to create TarInputStream from
051: * @see TarBuffer#DEFAULT_BLOCKSIZE
052: * @see TarBuffer#DEFAULT_RECORDSIZE
053: */
054: TarInputStream(final InputStream input) {
055: this (input, TarBuffer.DEFAULT_BLOCKSIZE,
056: TarBuffer.DEFAULT_RECORDSIZE);
057: }
058:
059: /**
060: * Construct a TarInputStream using specified input
061: * stream, block size and default record sizes.
062: *
063: * @param input stream to create TarInputStream from
064: * @param blockSize the block size to use
065: * @see TarBuffer#DEFAULT_RECORDSIZE
066: */
067: TarInputStream(final InputStream input, final int blockSize) {
068: this (input, blockSize, TarBuffer.DEFAULT_RECORDSIZE);
069: }
070:
071: /**
072: * Construct a TarInputStream using specified input
073: * stream, block size and record sizes.
074: *
075: * @param input stream to create TarInputStream from
076: * @param blockSize the block size to use
077: * @param recordSize the record size to use
078: */
079: TarInputStream(final InputStream input, final int blockSize,
080: final int recordSize) {
081: super (input);
082:
083: m_buffer = new TarBuffer(input, blockSize, recordSize);
084: m_oneBuf = new byte[1];
085: }
086:
087: /**
088: * Sets the debugging flag.
089: *
090: * @param debug The new Debug value
091: */
092: public void setDebug(final boolean debug) {
093: m_debug = debug;
094: m_buffer.setDebug(debug);
095: }
096:
097: /**
098: * Get the next entry in this tar archive. This will skip over any remaining
099: * data in the current entry, if there is one, and place the input stream at
100: * the header of the next entry, and read the header and instantiate a new
101: * TarEntry from the header bytes and return that entry. If there are no
102: * more entries in the archive, null will be returned to indicate that the
103: * end of the archive has been reached.
104: *
105: * @return The next TarEntry in the archive, or null.
106: * @exception IOException Description of Exception
107: */
108: public TarEntry getNextEntry() throws IOException {
109: if (m_hasHitEOF) {
110: return null;
111: }
112:
113: if (m_currEntry != null) {
114: final int numToSkip = m_entrySize - m_entryOffset;
115:
116: if (m_debug) {
117: final String message = "TarInputStream: SKIP currENTRY '"
118: + m_currEntry.getName()
119: + "' SZ "
120: + m_entrySize
121: + " OFF "
122: + m_entryOffset
123: + " skipping "
124: + numToSkip + " bytes";
125: debug(message);
126: }
127:
128: if (numToSkip > 0) {
129: skip(numToSkip);
130: }
131:
132: m_readBuf = null;
133: }
134:
135: final byte[] headerBuf = m_buffer.readRecord();
136: if (headerBuf == null) {
137: if (m_debug) {
138: debug("READ NULL RECORD");
139: }
140: m_hasHitEOF = true;
141: } else if (m_buffer.isEOFRecord(headerBuf)) {
142: if (m_debug) {
143: debug("READ EOF RECORD");
144: }
145: m_hasHitEOF = true;
146: }
147:
148: if (m_hasHitEOF) {
149: m_currEntry = null;
150: } else {
151: m_currEntry = new TarEntry(headerBuf);
152:
153: if (!(headerBuf[257] == 'u' && headerBuf[258] == 's'
154: && headerBuf[259] == 't' && headerBuf[260] == 'a' && headerBuf[261] == 'r')) {
155: //Must be v7Format
156: }
157:
158: if (m_debug) {
159: final String message = "TarInputStream: SET CURRENTRY '"
160: + m_currEntry.getName()
161: + "' size = "
162: + m_currEntry.getSize();
163: debug(message);
164: }
165:
166: m_entryOffset = 0;
167:
168: // REVIEW How do we resolve this discrepancy?!
169: m_entrySize = (int) m_currEntry.getSize();
170: }
171:
172: if (null != m_currEntry && m_currEntry.isGNULongNameEntry()) {
173: // read in the name
174: final StringBuffer longName = new StringBuffer();
175: final byte[] buffer = new byte[256];
176: int length = 0;
177: while ((length = read(buffer)) >= 0) {
178: final String str = new String(buffer, 0, length);
179: longName.append(str);
180: }
181: getNextEntry();
182:
183: // remove trailing null terminator
184: if (longName.length() > 0
185: && longName.charAt(longName.length() - 1) == 0) {
186: longName.deleteCharAt(longName.length() - 1);
187: }
188:
189: m_currEntry.setName(longName.toString());
190: }
191:
192: return m_currEntry;
193: }
194:
195: /**
196: * Get the record size being used by this stream's TarBuffer.
197: *
198: * @return The TarBuffer record size.
199: */
200: public int getRecordSize() {
201: return m_buffer.getRecordSize();
202: }
203:
204: /**
205: * Get the available data that can be read from the current entry in the
206: * archive. This does not indicate how much data is left in the entire
207: * archive, only in the current entry. This value is determined from the
208: * entry's size header field and the amount of data already read from the
209: * current entry.
210: *
211: * @return The number of available bytes for the current entry.
212: * @exception IOException when an IO error causes operation to fail
213: */
214: public int available() throws IOException {
215: return m_entrySize - m_entryOffset;
216: }
217:
218: /**
219: * Closes this stream. Calls the TarBuffer's close() method.
220: *
221: * @exception IOException when an IO error causes operation to fail
222: */
223: public void close() throws IOException {
224: m_buffer.close();
225: }
226:
227: /**
228: * Copies the contents of the current tar archive entry directly into an
229: * output stream.
230: *
231: * @param output The OutputStream into which to write the entry's data.
232: * @exception IOException when an IO error causes operation to fail
233: */
234: public void copyEntryContents(final OutputStream output)
235: throws IOException {
236: final byte[] buffer = new byte[32 * 1024];
237: while (true) {
238: final int numRead = read(buffer, 0, buffer.length);
239: if (numRead == -1) {
240: break;
241: }
242:
243: output.write(buffer, 0, numRead);
244: }
245: }
246:
247: /**
248: * Since we do not support marking just yet, we do nothing.
249: *
250: * @param markLimit The limit to mark.
251: */
252: public void mark(int markLimit) {
253: }
254:
255: /**
256: * Since we do not support marking just yet, we return false.
257: *
258: * @return False.
259: */
260: public boolean markSupported() {
261: return false;
262: }
263:
264: /**
265: * Reads a byte from the current tar archive entry. This method simply calls
266: * read( byte[], int, int ).
267: *
268: * @return The byte read, or -1 at EOF.
269: * @exception IOException when an IO error causes operation to fail
270: */
271: public int read() throws IOException {
272: final int num = read(m_oneBuf, 0, 1);
273: if (num == -1) {
274: return num;
275: } else {
276: return (int) m_oneBuf[0];
277: }
278: }
279:
280: /**
281: * Reads bytes from the current tar archive entry. This method simply calls
282: * read( byte[], int, int ).
283: *
284: * @param buffer The buffer into which to place bytes read.
285: * @return The number of bytes read, or -1 at EOF.
286: * @exception IOException when an IO error causes operation to fail
287: */
288: public int read(final byte[] buffer) throws IOException {
289: return read(buffer, 0, buffer.length);
290: }
291:
292: /**
293: * Reads bytes from the current tar archive entry. This method is aware of
294: * the boundaries of the current entry in the archive and will deal with
295: * them as if they were this stream's start and EOF.
296: *
297: * @param buffer The buffer into which to place bytes read.
298: * @param offset The offset at which to place bytes read.
299: * @param count The number of bytes to read.
300: * @return The number of bytes read, or -1 at EOF.
301: * @exception IOException when an IO error causes operation to fail
302: */
303: public int read(final byte[] buffer, final int offset,
304: final int count) throws IOException {
305: int position = offset;
306: int numToRead = count;
307: int totalRead = 0;
308:
309: if (m_entryOffset >= m_entrySize) {
310: return -1;
311: }
312:
313: if ((numToRead + m_entryOffset) > m_entrySize) {
314: numToRead = (m_entrySize - m_entryOffset);
315: }
316:
317: if (null != m_readBuf) {
318: final int size = (numToRead > m_readBuf.length) ? m_readBuf.length
319: : numToRead;
320:
321: System.arraycopy(m_readBuf, 0, buffer, position, size);
322:
323: if (size >= m_readBuf.length) {
324: m_readBuf = null;
325: } else {
326: final int newLength = m_readBuf.length - size;
327: final byte[] newBuffer = new byte[newLength];
328:
329: System.arraycopy(m_readBuf, size, newBuffer, 0,
330: newLength);
331:
332: m_readBuf = newBuffer;
333: }
334:
335: totalRead += size;
336: numToRead -= size;
337: position += size;
338: }
339:
340: while (numToRead > 0) {
341: final byte[] rec = m_buffer.readRecord();
342: if (null == rec) {
343: // Unexpected EOF!
344: final String message = "unexpected EOF with "
345: + numToRead + " bytes unread";
346: throw new IOException(message);
347: }
348:
349: int size = numToRead;
350: final int recordLength = rec.length;
351:
352: if (recordLength > size) {
353: System.arraycopy(rec, 0, buffer, position, size);
354:
355: m_readBuf = new byte[recordLength - size];
356:
357: System.arraycopy(rec, size, m_readBuf, 0, recordLength
358: - size);
359: } else {
360: size = recordLength;
361:
362: System
363: .arraycopy(rec, 0, buffer, position,
364: recordLength);
365: }
366:
367: totalRead += size;
368: numToRead -= size;
369: position += size;
370: }
371:
372: m_entryOffset += totalRead;
373:
374: return totalRead;
375: }
376:
377: /**
378: * Since we do not support marking just yet, we do nothing.
379: */
380: public void reset() {
381: }
382:
383: /**
384: * Skip bytes in the input buffer. This skips bytes in the current entry's
385: * data, not the entire archive, and will stop at the end of the current
386: * entry's data if the number to skip extends beyond that point.
387: *
388: * @param numToSkip The number of bytes to skip.
389: * @exception IOException when an IO error causes operation to fail
390: */
391: public void skip(final int numToSkip) throws IOException {
392: // REVIEW
393: // This is horribly inefficient, but it ensures that we
394: // properly skip over bytes via the TarBuffer...
395: //
396: final byte[] skipBuf = new byte[8 * 1024];
397: int num = numToSkip;
398: while (num > 0) {
399: final int count = (num > skipBuf.length) ? skipBuf.length
400: : num;
401: final int numRead = read(skipBuf, 0, count);
402: if (numRead == -1) {
403: break;
404: }
405:
406: num -= numRead;
407: }
408: }
409:
410: /**
411: * Utility method to do debugging.
412: * Capable of being overidden in sub-classes.
413: *
414: * @param message the message to use in debugging
415: */
416: protected void debug(final String message) {
417: if (m_debug) {
418: System.err.println(message);
419: }
420: }
421: }
|