// TarInputStream.cs // // Copyright (C) 2001 Mike Krueger // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // Linking this library statically or dynamically with other modules is // making a combined work based on this library. Thus, the terms and // conditions of the GNU General Public License cover the whole // combination. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. using System; using System.IO; using System.Text; namespace ICSharpCode.SharpZipLib.Tar { /// /// The TarInputStream reads a UNIX tar archive as an InputStream. /// methods are provided to position at each successive entry in /// the archive, and the read each entry as a normal input stream /// using read(). /// public class TarInputStream : Stream { #region Constructors /// /// Construct a TarInputStream with default block factor /// /// stream to source data from public TarInputStream(Stream inputStream) : this(inputStream, TarBuffer.DefaultBlockFactor) { } /// /// Construct a TarInputStream with user specified block factor /// /// stream to source data from /// block factor to apply to archive public TarInputStream(Stream inputStream, int blockFactor) { this.inputStream = inputStream; tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor); } #endregion /// /// Get/set flag indicating ownership of the underlying stream. /// When the flag is true will close the underlying stream also. /// public bool IsStreamOwner { get { return tarBuffer.IsStreamOwner; } set { tarBuffer.IsStreamOwner = value; } } #region Stream Overrides /// /// Gets a value indicating whether the current stream supports reading /// public override bool CanRead { get { return inputStream.CanRead; } } /// /// Gets a value indicating whether the current stream supports seeking /// This property always returns false. /// public override bool CanSeek { get { return false; } } /// /// Gets a value indicating if the stream supports writing. /// This property always returns false. /// public override bool CanWrite { get { return false; } } /// /// The length in bytes of the stream /// public override long Length { get { return inputStream.Length; } } /// /// Gets or sets the position within the stream. /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException /// /// Any attempt to set position public override long Position { get { return inputStream.Position; } set { throw new NotSupportedException("TarInputStream Seek not supported"); } } /// /// Flushes the baseInputStream /// public override void Flush() { inputStream.Flush(); } /// /// Set the streams position. This operation is not supported and will throw a NotSupportedException /// /// The offset relative to the origin to seek to. /// The to start seeking from. /// The new position in the stream. /// Any access public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("TarInputStream Seek not supported"); } /// /// Sets the length of the stream /// This operation is not supported and will throw a NotSupportedException /// /// The new stream length. /// Any access public override void SetLength(long value) { throw new NotSupportedException("TarInputStream SetLength not supported"); } /// /// Writes a block of bytes to this stream using data from a buffer. /// This operation is not supported and will throw a NotSupportedException /// /// The buffer containing bytes to write. /// The offset in the buffer of the frist byte to write. /// The number of bytes to write. /// Any access public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException("TarInputStream Write not supported"); } /// /// Writes a byte to the current position in the file stream. /// This operation is not supported and will throw a NotSupportedException /// /// The byte value to write. /// Any access public override void WriteByte(byte value) { throw new NotSupportedException("TarInputStream WriteByte not supported"); } /// /// Reads a byte from the current tar archive entry. /// /// A byte cast to an int; -1 if the at the end of the stream. public override int ReadByte() { byte[] oneByteBuffer = new byte[1]; int num = Read(oneByteBuffer, 0, 1); if (num <= 0) { // return -1 to indicate that no byte was read. return -1; } return oneByteBuffer[0]; } /// /// Reads bytes from the current tar archive entry. /// /// This method is aware of the boundaries of the current /// entry in the archive and will deal with them appropriately /// /// /// The buffer into which to place bytes read. /// /// /// The offset at which to place bytes read. /// /// /// The number of bytes to read. /// /// /// The number of bytes read, or 0 at end of stream/EOF. /// public override int Read(byte[] buffer, int offset, int count) { if ( buffer == null ) { throw new ArgumentNullException("buffer"); } int totalRead = 0; if (entryOffset >= entrySize) { return 0; } long numToRead = count; if ((numToRead + entryOffset) > entrySize) { numToRead = entrySize - entryOffset; } if (readBuffer != null) { int sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int)numToRead; Array.Copy(readBuffer, 0, buffer, offset, sz); if (sz >= readBuffer.Length) { readBuffer = null; } else { int newLen = readBuffer.Length - sz; byte[] newBuf = new byte[newLen]; Array.Copy(readBuffer, sz, newBuf, 0, newLen); readBuffer = newBuf; } totalRead += sz; numToRead -= sz; offset += sz; } while (numToRead > 0) { byte[] rec = tarBuffer.ReadBlock(); if (rec == null) { // Unexpected EOF! throw new TarException("unexpected EOF with " + numToRead + " bytes unread"); } int sz = (int)numToRead; int recLen = rec.Length; if (recLen > sz) { Array.Copy(rec, 0, buffer, offset, sz); readBuffer = new byte[recLen - sz]; Array.Copy(rec, sz, readBuffer, 0, recLen - sz); } else { sz = recLen; Array.Copy(rec, 0, buffer, offset, recLen); } totalRead += sz; numToRead -= sz; offset += sz; } entryOffset += totalRead; return totalRead; } /// /// Closes this stream. Calls the TarBuffer's close() method. /// The underlying stream is closed by the TarBuffer. /// public override void Close() { tarBuffer.Close(); } #endregion /// /// Set the entry factory for this instance. /// /// The factory for creating new entries public void SetEntryFactory(IEntryFactory factory) { entryFactory = factory; } /// /// Get the record size being used by this stream's TarBuffer. /// public int RecordSize { get { return tarBuffer.RecordSize; } } /// /// Get the record size being used by this stream's TarBuffer. /// /// /// TarBuffer record size. /// [Obsolete("Use RecordSize property instead")] public int GetRecordSize() { return tarBuffer.RecordSize; } /// /// Get the available data that can be read from the current /// entry in the archive. This does not indicate how much data /// is left in the entire archive, only in the current entry. /// This value is determined from the entry's size header field /// and the amount of data already read from the current entry. /// /// /// The number of available bytes for the current entry. /// public long Available { get { return entrySize - entryOffset; } } /// /// Skip bytes in the input buffer. This skips bytes in the /// current entry's data, not the entire archive, and will /// stop at the end of the current entry's data if the number /// to skip extends beyond that point. /// /// /// The number of bytes to skip. /// public void Skip(long skipCount) { // TODO: REVIEW efficiency of TarInputStream.Skip // This is horribly inefficient, but it ensures that we // properly skip over bytes via the TarBuffer... // byte[] skipBuf = new byte[8 * 1024]; for (long num = skipCount; num > 0;) { int toRead = num > skipBuf.Length ? skipBuf.Length : (int)num; int numRead = Read(skipBuf, 0, toRead); if (numRead == -1) { break; } num -= numRead; } } /// /// Return a value of true if marking is supported; false otherwise. /// /// Currently marking is not supported, the return value is always false. public bool IsMarkSupported { get { return false; } } /// /// Since we do not support marking just yet, we do nothing. /// /// /// The limit to mark. /// public void Mark(int markLimit) { } /// /// Since we do not support marking just yet, we do nothing. /// public void Reset() { } /// /// Get the next entry in this tar archive. This will skip /// over any remaining data in the current entry, if there /// is one, and place the input stream at the header of the /// next entry, and read the header and instantiate a new /// TarEntry from the header bytes and return that entry. /// If there are no more entries in the archive, null will /// be returned to indicate that the end of the archive has /// been reached. /// /// /// The next TarEntry in the archive, or null. /// public TarEntry GetNextEntry() { if (hasHitEOF) { return null; } if (currentEntry != null) { SkipToNextEntry(); } byte[] headerBuf = tarBuffer.ReadBlock(); if (headerBuf == null) { hasHitEOF = true; } else if (TarBuffer.IsEndOfArchiveBlock(headerBuf)) { hasHitEOF = true; } if (hasHitEOF) { currentEntry = null; } else { try { TarHeader header = new TarHeader(); header.ParseBuffer(headerBuf); if ( !header.IsChecksumValid ) { throw new TarException("Header checksum is invalid"); } this.entryOffset = 0; this.entrySize = header.Size; StringBuilder longName = null; if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME) { byte[] nameBuffer = new byte[TarBuffer.BlockSize]; long numToRead = this.entrySize; longName = new StringBuilder(); while (numToRead > 0) { int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead)); if (numRead == -1) { throw new InvalidHeaderException("Failed to read long name entry"); } longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString()); numToRead -= numRead; } SkipToNextEntry(); headerBuf = this.tarBuffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_GHDR) { // POSIX global extended header // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = this.tarBuffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_XHDR) { // POSIX extended header // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = this.tarBuffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) { // TODO: could show volume name when verbose SkipToNextEntry(); headerBuf = this.tarBuffer.ReadBlock(); } else if (header.TypeFlag != TarHeader.LF_NORMAL && header.TypeFlag != TarHeader.LF_OLDNORM && header.TypeFlag != TarHeader.LF_DIR) { // Ignore things we dont understand completely for now SkipToNextEntry(); headerBuf = tarBuffer.ReadBlock(); } if (entryFactory == null) { currentEntry = new TarEntry(headerBuf); if (longName != null) { currentEntry.Name = longName.ToString(); } } else { currentEntry = entryFactory.CreateEntry(headerBuf); } // Magic was checked here for 'ustar' but there are multiple valid possibilities // so this is not done anymore. entryOffset = 0; // TODO: Review How do we resolve this discrepancy?! entrySize = this.currentEntry.Size; } catch (InvalidHeaderException ex) { entrySize = 0; entryOffset = 0; currentEntry = null; string errorText = string.Format("Bad header in record {0} block {1} {2}", tarBuffer.CurrentRecord, tarBuffer.CurrentBlock, ex.Message); throw new InvalidHeaderException(errorText); } } return currentEntry; } /// /// Copies the contents of the current tar archive entry directly into /// an output stream. /// /// /// The OutputStream into which to write the entry's data. /// public void CopyEntryContents(Stream outputStream) { byte[] tempBuffer = new byte[32 * 1024]; while (true) { int numRead = Read(tempBuffer, 0, tempBuffer.Length); if (numRead <= 0) { break; } outputStream.Write(tempBuffer, 0, numRead); } } void SkipToNextEntry() { long numToSkip = entrySize - entryOffset; if (numToSkip > 0) { Skip(numToSkip); } readBuffer = null; } /// /// This interface is provided, along with the method , to allow /// the programmer to have their own subclass instantiated for the /// entries return from . /// public interface IEntryFactory { /// /// Create an entry based on name alone /// /// /// Name of the new EntryPointNotFoundException to create /// /// created TarEntry or descendant class TarEntry CreateEntry(string name); /// /// Create an instance based on an actual file /// /// /// Name of file to represent in the entry /// /// /// Created TarEntry or descendant class /// TarEntry CreateEntryFromFile(string fileName); /// /// Create a tar entry based on the header information passed /// /// /// Buffer containing header information to create an an entry from. /// /// /// Created TarEntry or descendant class /// TarEntry CreateEntry(byte[] headerBuffer); } /// /// Standard entry factory class creating instances of the class TarEntry /// public class EntryFactoryAdapter : IEntryFactory { /// /// Create a based on named /// /// The name to use for the entry /// A new public TarEntry CreateEntry(string name) { return TarEntry.CreateTarEntry(name); } /// /// Create a tar entry with details obtained from file /// /// The name of the file to retrieve details from. /// A new public TarEntry CreateEntryFromFile(string fileName) { return TarEntry.CreateEntryFromFile(fileName); } /// /// Create an entry based on details in header /// /// The buffer containing entry details. /// A new public TarEntry CreateEntry(byte[] headerBuffer) { return new TarEntry(headerBuffer); } } #region Instance Fields /// /// Flag set when last block has been read /// protected bool hasHitEOF; /// /// Size of this entry as recorded in header /// protected long entrySize; /// /// Number of bytes read for this entry so far /// protected long entryOffset; /// /// Buffer used with calls to Read() /// protected byte[] readBuffer; /// /// Working buffer /// protected TarBuffer tarBuffer; /// /// Current entry being read /// TarEntry currentEntry; /// /// Factory used to create TarEntry or descendant class instance /// protected IEntryFactory entryFactory; /// /// Stream used as the source of input data. /// readonly Stream inputStream; #endregion } } /* The original Java file had this header: ** Authored by Timothy Gerard Endres ** ** ** This work has been placed into the public domain. ** You may use this work in any way and for any purpose you wish. ** ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR ** REDISTRIBUTION OF THIS SOFTWARE. ** */