|
|
// 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 Externals.Compression.Tar {
/// <summary>
/// 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().
/// </summary>
internal class TarInputStream : Stream { #region Constructors
/// <summary>
/// Construct a TarInputStream with default block factor
/// </summary>
/// <param name="inputStream">stream to source data from</param>
public TarInputStream(Stream inputStream) : this(inputStream, TarBuffer.DefaultBlockFactor) { }
/// <summary>
/// Construct a TarInputStream with user specified block factor
/// </summary>
/// <param name="inputStream">stream to source data from</param>
/// <param name="blockFactor">block factor to apply to archive</param>
public TarInputStream(Stream inputStream, int blockFactor) { this.inputStream = inputStream; tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor); }
#endregion
/// <summary>
/// Get/set flag indicating ownership of the underlying stream.
/// When the flag is true <see cref="Close"></see> will close the underlying stream also.
/// </summary>
public bool IsStreamOwner { get { return tarBuffer.IsStreamOwner; } set { tarBuffer.IsStreamOwner = value; } }
#region Stream Overrides
/// <summary>
/// Gets a value indicating whether the current stream supports reading
/// </summary>
public override bool CanRead { get { return inputStream.CanRead; } } /// <summary>
/// Gets a value indicating whether the current stream supports seeking
/// This property always returns false.
/// </summary>
public override bool CanSeek { get { return false; } } /// <summary>
/// Gets a value indicating if the stream supports writing.
/// This property always returns false.
/// </summary>
public override bool CanWrite { get { return false; } } /// <summary>
/// The length in bytes of the stream
/// </summary>
public override long Length { get { return inputStream.Length; } } /// <summary>
/// Gets or sets the position within the stream.
/// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException
/// </summary>
/// <exception cref="NotSupportedException">Any attempt to set position</exception>
public override long Position { get { return inputStream.Position; } set { throw new NotSupportedException("TarInputStream Seek not supported"); } } /// <summary>
/// Flushes the baseInputStream
/// </summary>
public override void Flush() { inputStream.Flush(); } /// <summary>
/// Set the streams position. This operation is not supported and will throw a NotSupportedException
/// </summary>
/// <param name="offset">The offset relative to the origin to seek to.</param>
/// <param name="origin">The <see cref="SeekOrigin"/> to start seeking from.</param>
/// <returns>The new position in the stream.</returns>
/// <exception cref="NotSupportedException">Any access</exception>
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("TarInputStream Seek not supported"); } /// <summary>
/// Sets the length of the stream
/// This operation is not supported and will throw a NotSupportedException
/// </summary>
/// <param name="value">The new stream length.</param>
/// <exception cref="NotSupportedException">Any access</exception>
public override void SetLength(long value) { throw new NotSupportedException("TarInputStream SetLength not supported"); } /// <summary>
/// Writes a block of bytes to this stream using data from a buffer.
/// This operation is not supported and will throw a NotSupportedException
/// </summary>
/// <param name="buffer">The buffer containing bytes to write.</param>
/// <param name="offset">The offset in the buffer of the frist byte to write.</param>
/// <param name="count">The number of bytes to write.</param>
/// <exception cref="NotSupportedException">Any access</exception>
public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException("TarInputStream Write not supported"); } /// <summary>
/// Writes a byte to the current position in the file stream.
/// This operation is not supported and will throw a NotSupportedException
/// </summary>
/// <param name="value">The byte value to write.</param>
/// <exception cref="NotSupportedException">Any access</exception>
public override void WriteByte(byte value) { throw new NotSupportedException("TarInputStream WriteByte not supported"); } /// <summary>
/// Reads a byte from the current tar archive entry.
/// </summary>
/// <returns>A byte cast to an int; -1 if the at the end of the stream.</returns>
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]; } /// <summary>
/// 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
/// </summary>
/// <param name="buffer">
/// The buffer into which to place bytes read.
/// </param>
/// <param name="offset">
/// The offset at which to place bytes read.
/// </param>
/// <param name="count">
/// The number of bytes to read.
/// </param>
/// <returns>
/// The number of bytes read, or 0 at end of stream/EOF.
/// </returns>
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; } /// <summary>
/// Closes this stream. Calls the TarBuffer's close() method.
/// The underlying stream is closed by the TarBuffer.
/// </summary>
public override void Close() { tarBuffer.Close(); } #endregion
/// <summary>
/// Set the entry factory for this instance.
/// </summary>
/// <param name="factory">The factory for creating new entries</param>
public void SetEntryFactory(IEntryFactory factory) { entryFactory = factory; } /// <summary>
/// Get the record size being used by this stream's TarBuffer.
/// </summary>
public int RecordSize { get { return tarBuffer.RecordSize; } }
/// <summary>
/// Get the record size being used by this stream's TarBuffer.
/// </summary>
/// <returns>
/// TarBuffer record size.
/// </returns>
[Obsolete("Use RecordSize property instead")] public int GetRecordSize() { return tarBuffer.RecordSize; } /// <summary>
/// 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.
/// </summary>
/// <returns>
/// The number of available bytes for the current entry.
/// </returns>
public long Available { get { return entrySize - entryOffset; } } /// <summary>
/// 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.
/// </summary>
/// <param name="skipCount">
/// The number of bytes to skip.
/// </param>
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; } } /// <summary>
/// Return a value of true if marking is supported; false otherwise.
/// </summary>
/// <remarks>Currently marking is not supported, the return value is always false.</remarks>
public bool IsMarkSupported { get { return false; } } /// <summary>
/// Since we do not support marking just yet, we do nothing.
/// </summary>
/// <param name ="markLimit">
/// The limit to mark.
/// </param>
public void Mark(int markLimit) { } /// <summary>
/// Since we do not support marking just yet, we do nothing.
/// </summary>
public void Reset() { } /// <summary>
/// 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.
/// </summary>
/// <returns>
/// The next TarEntry in the archive, or null.
/// </returns>
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; } /// <summary>
/// Copies the contents of the current tar archive entry directly into
/// an output stream.
/// </summary>
/// <param name="outputStream">
/// The OutputStream into which to write the entry's data.
/// </param>
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; }
/// <summary>
/// This interface is provided, along with the method <see cref="SetEntryFactory"/>, to allow
/// the programmer to have their own <see cref="TarEntry"/> subclass instantiated for the
/// entries return from <see cref="GetNextEntry"/>.
/// </summary>
internal interface IEntryFactory { /// <summary>
/// Create an entry based on name alone
/// </summary>
/// <param name="name">
/// Name of the new EntryPointNotFoundException to create
/// </param>
/// <returns>created TarEntry or descendant class</returns>
TarEntry CreateEntry(string name); /// <summary>
/// Create an instance based on an actual file
/// </summary>
/// <param name="fileName">
/// Name of file to represent in the entry
/// </param>
/// <returns>
/// Created TarEntry or descendant class
/// </returns>
TarEntry CreateEntryFromFile(string fileName); /// <summary>
/// Create a tar entry based on the header information passed
/// </summary>
/// <param name="headerBuffer">
/// Buffer containing header information to create an an entry from.
/// </param>
/// <returns>
/// Created TarEntry or descendant class
/// </returns>
TarEntry CreateEntry(byte[] headerBuffer); }
/// <summary>
/// Standard entry factory class creating instances of the class TarEntry
/// </summary>
internal class EntryFactoryAdapter : IEntryFactory { /// <summary>
/// Create a <see cref="TarEntry"/> based on named
/// </summary>
/// <param name="name">The name to use for the entry</param>
/// <returns>A new <see cref="TarEntry"/></returns>
public TarEntry CreateEntry(string name) { return TarEntry.CreateTarEntry(name); } /// <summary>
/// Create a tar entry with details obtained from <paramref name="fileName">file</paramref>
/// </summary>
/// <param name="fileName">The name of the file to retrieve details from.</param>
/// <returns>A new <see cref="TarEntry"/></returns>
public TarEntry CreateEntryFromFile(string fileName) { return TarEntry.CreateEntryFromFile(fileName); }
/// <summary>
/// Create an entry based on details in <paramref name="headerBuffer">header</paramref>
/// </summary>
/// <param name="headerBuffer">The buffer containing entry details.</param>
/// <returns>A new <see cref="TarEntry"/></returns>
public TarEntry CreateEntry(byte[] headerBuffer) { return new TarEntry(headerBuffer); } }
#region Instance Fields
/// <summary>
/// Flag set when last block has been read
/// </summary>
protected bool hasHitEOF; /// <summary>
/// Size of this entry as recorded in header
/// </summary>
protected long entrySize; /// <summary>
/// Number of bytes read for this entry so far
/// </summary>
protected long entryOffset;
/// <summary>
/// Buffer used with calls to <code>Read()</code>
/// </summary>
protected byte[] readBuffer; /// <summary>
/// Working buffer
/// </summary>
protected TarBuffer tarBuffer; /// <summary>
/// Current entry being read
/// </summary>
TarEntry currentEntry; /// <summary>
/// Factory used to create TarEntry or descendant class instance
/// </summary>
protected IEntryFactory entryFactory; /// <summary>
/// Stream used as the source of input data.
/// </summary>
readonly Stream inputStream; #endregion
} }
/* The original Java file had this header: ** Authored by Timothy Gerard Endres ** <mailto:time@gjt.org> <http://www.trustice.com>
** ** 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. ** */
|