| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 | using System;using System.IO;using System.Text;namespace ICSharpCode.SharpZipLib.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>	public 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>		/// Gets or sets a flag indicating ownership of underlying stream.		/// When the flag is true <see cref="Stream.Dispose()" /> will close the underlying stream also.		/// </summary>		/// <remarks>The default value is true.</remarks>		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("nameof(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");				}				var 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>		protected override void Dispose(bool disposing)		{			if (disposing)			{				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				hasHitEOF |= TarBuffer.IsEndOfArchiveBlock(headerBuf);			if (hasHitEOF) {				currentEntry = null;			} else {				try {					var 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_LINK &&							   header.TypeFlag != TarHeader.LF_SYMLINK &&							   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>		public 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>		public 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	}}
 |