初始化上传
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Tar
|
||||
{
|
||||
/// <summary>
|
||||
/// This exception is used to indicate that there is a problem
|
||||
/// with a TAR archive header.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class InvalidHeaderException : TarException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialise a new instance of the InvalidHeaderException class.
|
||||
/// </summary>
|
||||
public InvalidHeaderException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises a new instance of the InvalidHeaderException class with a specified message.
|
||||
/// </summary>
|
||||
/// <param name="message">Message describing the exception cause.</param>
|
||||
public InvalidHeaderException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of InvalidHeaderException
|
||||
/// </summary>
|
||||
/// <param name="message">Message describing the problem.</param>
|
||||
/// <param name="exception">The exception that is the cause of the current exception.</param>
|
||||
public InvalidHeaderException(string message, Exception exception)
|
||||
: base(message, exception)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the InvalidHeaderException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected InvalidHeaderException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
1028
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarArchive.cs
Normal file
1028
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarArchive.cs
Normal file
File diff suppressed because it is too large
Load Diff
599
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarBuffer.cs
Normal file
599
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarBuffer.cs
Normal file
@@ -0,0 +1,599 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Tar
|
||||
{
|
||||
/// <summary>
|
||||
/// The TarBuffer class implements the tar archive concept
|
||||
/// of a buffered input stream. This concept goes back to the
|
||||
/// days of blocked tape drives and special io devices. In the
|
||||
/// C# universe, the only real function that this class
|
||||
/// performs is to ensure that files have the correct "record"
|
||||
/// size, or other tars will complain.
|
||||
/// <p>
|
||||
/// You should never have a need to access this class directly.
|
||||
/// TarBuffers are created by Tar IO Streams.
|
||||
/// </p>
|
||||
/// </summary>
|
||||
public class TarBuffer
|
||||
{
|
||||
/* A quote from GNU tar man file on blocking and records
|
||||
A `tar' archive file contains a series of blocks. Each block
|
||||
contains `BLOCKSIZE' bytes. Although this format may be thought of as
|
||||
being on magnetic tape, other media are often used.
|
||||
|
||||
Each file archived is represented by a header block which describes
|
||||
the file, followed by zero or more blocks which give the contents of
|
||||
the file. At the end of the archive file there may be a block filled
|
||||
with binary zeros as an end-of-file marker. A reasonable system should
|
||||
write a block of zeros at the end, but must not assume that such a
|
||||
block exists when reading an archive.
|
||||
|
||||
The blocks may be "blocked" for physical I/O operations. Each
|
||||
record of N blocks is written with a single 'write ()'
|
||||
operation. On magnetic tapes, the result of such a write is a single
|
||||
record. When writing an archive, the last record of blocks should be
|
||||
written at the full size, with blocks after the zero block containing
|
||||
all zeros. When reading an archive, a reasonable system should
|
||||
properly handle an archive whose last record is shorter than the rest,
|
||||
or which contains garbage records after a zero block.
|
||||
*/
|
||||
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// The size of a block in a tar archive in bytes.
|
||||
/// </summary>
|
||||
/// <remarks>This is 512 bytes.</remarks>
|
||||
public const int BlockSize = 512;
|
||||
|
||||
/// <summary>
|
||||
/// The number of blocks in a default record.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The default value is 20 blocks per record.
|
||||
/// </remarks>
|
||||
public const int DefaultBlockFactor = 20;
|
||||
|
||||
/// <summary>
|
||||
/// The size in bytes of a default record.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The default size is 10KB.
|
||||
/// </remarks>
|
||||
public const int DefaultRecordSize = BlockSize * DefaultBlockFactor;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
/// <summary>
|
||||
/// Get the record size for this buffer
|
||||
/// </summary>
|
||||
/// <value>The record size in bytes.
|
||||
/// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></value>
|
||||
public int RecordSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return recordSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the TAR Buffer's record size.
|
||||
/// </summary>
|
||||
/// <returns>The record size in bytes.
|
||||
/// This is equal to the <see cref="BlockFactor"/> multiplied by the <see cref="BlockSize"/></returns>
|
||||
[Obsolete("Use RecordSize property instead")]
|
||||
public int GetRecordSize()
|
||||
{
|
||||
return recordSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the Blocking factor for the buffer
|
||||
/// </summary>
|
||||
/// <value>This is the number of blocks in each record.</value>
|
||||
public int BlockFactor
|
||||
{
|
||||
get
|
||||
{
|
||||
return blockFactor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the TAR Buffer's block factor
|
||||
/// </summary>
|
||||
/// <returns>The block factor; the number of blocks per record.</returns>
|
||||
[Obsolete("Use BlockFactor property instead")]
|
||||
public int GetBlockFactor()
|
||||
{
|
||||
return blockFactor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a default TarBuffer
|
||||
/// </summary>
|
||||
protected TarBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create TarBuffer for reading with default BlockFactor
|
||||
/// </summary>
|
||||
/// <param name="inputStream">Stream to buffer</param>
|
||||
/// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns>
|
||||
public static TarBuffer CreateInputTarBuffer(Stream inputStream)
|
||||
{
|
||||
if (inputStream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inputStream));
|
||||
}
|
||||
|
||||
return CreateInputTarBuffer(inputStream, DefaultBlockFactor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct TarBuffer for reading inputStream setting BlockFactor
|
||||
/// </summary>
|
||||
/// <param name="inputStream">Stream to buffer</param>
|
||||
/// <param name="blockFactor">Blocking factor to apply</param>
|
||||
/// <returns>A new <see cref="TarBuffer"/> suitable for input.</returns>
|
||||
public static TarBuffer CreateInputTarBuffer(Stream inputStream, int blockFactor)
|
||||
{
|
||||
if (inputStream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(inputStream));
|
||||
}
|
||||
|
||||
if (blockFactor <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative");
|
||||
}
|
||||
|
||||
var tarBuffer = new TarBuffer();
|
||||
tarBuffer.inputStream = inputStream;
|
||||
tarBuffer.outputStream = null;
|
||||
tarBuffer.Initialize(blockFactor);
|
||||
|
||||
return tarBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct TarBuffer for writing with default BlockFactor
|
||||
/// </summary>
|
||||
/// <param name="outputStream">output stream for buffer</param>
|
||||
/// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns>
|
||||
public static TarBuffer CreateOutputTarBuffer(Stream outputStream)
|
||||
{
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outputStream));
|
||||
}
|
||||
|
||||
return CreateOutputTarBuffer(outputStream, DefaultBlockFactor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct TarBuffer for writing Tar output to streams.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">Output stream to write to.</param>
|
||||
/// <param name="blockFactor">Blocking factor to apply</param>
|
||||
/// <returns>A new <see cref="TarBuffer"/> suitable for output.</returns>
|
||||
public static TarBuffer CreateOutputTarBuffer(Stream outputStream, int blockFactor)
|
||||
{
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outputStream));
|
||||
}
|
||||
|
||||
if (blockFactor <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(blockFactor), "Factor cannot be negative");
|
||||
}
|
||||
|
||||
var tarBuffer = new TarBuffer();
|
||||
tarBuffer.inputStream = null;
|
||||
tarBuffer.outputStream = outputStream;
|
||||
tarBuffer.Initialize(blockFactor);
|
||||
|
||||
return tarBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialization common to all constructors.
|
||||
/// </summary>
|
||||
private void Initialize(int archiveBlockFactor)
|
||||
{
|
||||
blockFactor = archiveBlockFactor;
|
||||
recordSize = archiveBlockFactor * BlockSize;
|
||||
recordBuffer = new byte[RecordSize];
|
||||
|
||||
if (inputStream != null)
|
||||
{
|
||||
currentRecordIndex = -1;
|
||||
currentBlockIndex = BlockFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRecordIndex = 0;
|
||||
currentBlockIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if an archive block indicates End of Archive. End of
|
||||
/// archive is indicated by a block that consists entirely of null bytes.
|
||||
/// All remaining blocks for the record should also be null's
|
||||
/// However some older tars only do a couple of null blocks (Old GNU tar for one)
|
||||
/// and also partial records
|
||||
/// </summary>
|
||||
/// <param name = "block">The data block to check.</param>
|
||||
/// <returns>Returns true if the block is an EOF block; false otherwise.</returns>
|
||||
[Obsolete("Use IsEndOfArchiveBlock instead")]
|
||||
public bool IsEOFBlock(byte[] block)
|
||||
{
|
||||
if (block == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(block));
|
||||
}
|
||||
|
||||
if (block.Length != BlockSize)
|
||||
{
|
||||
throw new ArgumentException("block length is invalid");
|
||||
}
|
||||
|
||||
for (int i = 0; i < BlockSize; ++i)
|
||||
{
|
||||
if (block[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if an archive block indicates the End of an Archive has been reached.
|
||||
/// End of archive is indicated by a block that consists entirely of null bytes.
|
||||
/// All remaining blocks for the record should also be null's
|
||||
/// However some older tars only do a couple of null blocks (Old GNU tar for one)
|
||||
/// and also partial records
|
||||
/// </summary>
|
||||
/// <param name = "block">The data block to check.</param>
|
||||
/// <returns>Returns true if the block is an EOF block; false otherwise.</returns>
|
||||
public static bool IsEndOfArchiveBlock(byte[] block)
|
||||
{
|
||||
if (block == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(block));
|
||||
}
|
||||
|
||||
if (block.Length != BlockSize)
|
||||
{
|
||||
throw new ArgumentException("block length is invalid");
|
||||
}
|
||||
|
||||
for (int i = 0; i < BlockSize; ++i)
|
||||
{
|
||||
if (block[i] != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip over a block on the input stream.
|
||||
/// </summary>
|
||||
public void SkipBlock()
|
||||
{
|
||||
if (inputStream == null)
|
||||
{
|
||||
throw new TarException("no input stream defined");
|
||||
}
|
||||
|
||||
if (currentBlockIndex >= BlockFactor)
|
||||
{
|
||||
if (!ReadRecord())
|
||||
{
|
||||
throw new TarException("Failed to read a record");
|
||||
}
|
||||
}
|
||||
|
||||
currentBlockIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a block from the input stream.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The block of data read.
|
||||
/// </returns>
|
||||
public byte[] ReadBlock()
|
||||
{
|
||||
if (inputStream == null)
|
||||
{
|
||||
throw new TarException("TarBuffer.ReadBlock - no input stream defined");
|
||||
}
|
||||
|
||||
if (currentBlockIndex >= BlockFactor)
|
||||
{
|
||||
if (!ReadRecord())
|
||||
{
|
||||
throw new TarException("Failed to read a record");
|
||||
}
|
||||
}
|
||||
|
||||
byte[] result = new byte[BlockSize];
|
||||
|
||||
Array.Copy(recordBuffer, (currentBlockIndex * BlockSize), result, 0, BlockSize);
|
||||
currentBlockIndex++;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a record from data stream.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// false if End-Of-File, else true.
|
||||
/// </returns>
|
||||
private bool ReadRecord()
|
||||
{
|
||||
if (inputStream == null)
|
||||
{
|
||||
throw new TarException("no input stream defined");
|
||||
}
|
||||
|
||||
currentBlockIndex = 0;
|
||||
|
||||
int offset = 0;
|
||||
int bytesNeeded = RecordSize;
|
||||
|
||||
while (bytesNeeded > 0)
|
||||
{
|
||||
long numBytes = inputStream.Read(recordBuffer, offset, bytesNeeded);
|
||||
|
||||
//
|
||||
// NOTE
|
||||
// We have found EOF, and the record is not full!
|
||||
//
|
||||
// This is a broken archive. It does not follow the standard
|
||||
// blocking algorithm. However, because we are generous, and
|
||||
// it requires little effort, we will simply ignore the error
|
||||
// and continue as if the entire record were read. This does
|
||||
// not appear to break anything upstream. We used to return
|
||||
// false in this case.
|
||||
//
|
||||
// Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
|
||||
//
|
||||
if (numBytes <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
offset += (int)numBytes;
|
||||
bytesNeeded -= (int)numBytes;
|
||||
}
|
||||
|
||||
currentRecordIndex++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current block number, within the current record, zero based.
|
||||
/// </summary>
|
||||
/// <remarks>Block numbers are zero based values</remarks>
|
||||
/// <seealso cref="RecordSize"/>
|
||||
public int CurrentBlock
|
||||
{
|
||||
get { return currentBlockIndex; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating ownership of underlying stream.
|
||||
/// When the flag is true <see cref="Close" /> will close the underlying stream also.
|
||||
/// </summary>
|
||||
/// <remarks>The default value is true.</remarks>
|
||||
public bool IsStreamOwner { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Get the current block number, within the current record, zero based.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The current zero based block number.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The absolute block number = (<see cref="GetCurrentRecordNum">record number</see> * <see cref="BlockFactor">block factor</see>) + <see cref="GetCurrentBlockNum">block number</see>.
|
||||
/// </remarks>
|
||||
[Obsolete("Use CurrentBlock property instead")]
|
||||
public int GetCurrentBlockNum()
|
||||
{
|
||||
return currentBlockIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current record number.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The current zero based record number.
|
||||
/// </returns>
|
||||
public int CurrentRecord
|
||||
{
|
||||
get { return currentRecordIndex; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current record number.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The current zero based record number.
|
||||
/// </returns>
|
||||
[Obsolete("Use CurrentRecord property instead")]
|
||||
public int GetCurrentRecordNum()
|
||||
{
|
||||
return currentRecordIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a block of data to the archive.
|
||||
/// </summary>
|
||||
/// <param name="block">
|
||||
/// The data to write to the archive.
|
||||
/// </param>
|
||||
public void WriteBlock(byte[] block)
|
||||
{
|
||||
if (block == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(block));
|
||||
}
|
||||
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new TarException("TarBuffer.WriteBlock - no output stream defined");
|
||||
}
|
||||
|
||||
if (block.Length != BlockSize)
|
||||
{
|
||||
string errorText = string.Format("TarBuffer.WriteBlock - block to write has length '{0}' which is not the block size of '{1}'",
|
||||
block.Length, BlockSize);
|
||||
throw new TarException(errorText);
|
||||
}
|
||||
|
||||
if (currentBlockIndex >= BlockFactor)
|
||||
{
|
||||
WriteRecord();
|
||||
}
|
||||
|
||||
Array.Copy(block, 0, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
|
||||
currentBlockIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write an archive record to the archive, where the record may be
|
||||
/// inside of a larger array buffer. The buffer must be "offset plus
|
||||
/// record size" long.
|
||||
/// </summary>
|
||||
/// <param name="buffer">
|
||||
/// The buffer containing the record data to write.
|
||||
/// </param>
|
||||
/// <param name="offset">
|
||||
/// The offset of the record data within buffer.
|
||||
/// </param>
|
||||
public void WriteBlock(byte[] buffer, int offset)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new TarException("TarBuffer.WriteBlock - no output stream defined");
|
||||
}
|
||||
|
||||
if ((offset < 0) || (offset >= buffer.Length))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
}
|
||||
|
||||
if ((offset + BlockSize) > buffer.Length)
|
||||
{
|
||||
string errorText = string.Format("TarBuffer.WriteBlock - record has length '{0}' with offset '{1}' which is less than the record size of '{2}'",
|
||||
buffer.Length, offset, recordSize);
|
||||
throw new TarException(errorText);
|
||||
}
|
||||
|
||||
if (currentBlockIndex >= BlockFactor)
|
||||
{
|
||||
WriteRecord();
|
||||
}
|
||||
|
||||
Array.Copy(buffer, offset, recordBuffer, (currentBlockIndex * BlockSize), BlockSize);
|
||||
|
||||
currentBlockIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a TarBuffer record to the archive.
|
||||
/// </summary>
|
||||
private void WriteRecord()
|
||||
{
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new TarException("TarBuffer.WriteRecord no output stream defined");
|
||||
}
|
||||
|
||||
outputStream.Write(recordBuffer, 0, RecordSize);
|
||||
outputStream.Flush();
|
||||
|
||||
currentBlockIndex = 0;
|
||||
currentRecordIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WriteFinalRecord writes the current record buffer to output any unwritten data is present.
|
||||
/// </summary>
|
||||
/// <remarks>Any trailing bytes are set to zero which is by definition correct behaviour
|
||||
/// for the end of a tar stream.</remarks>
|
||||
private void WriteFinalRecord()
|
||||
{
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new TarException("TarBuffer.WriteFinalRecord no output stream defined");
|
||||
}
|
||||
|
||||
if (currentBlockIndex > 0)
|
||||
{
|
||||
int dataBytes = currentBlockIndex * BlockSize;
|
||||
Array.Clear(recordBuffer, dataBytes, RecordSize - dataBytes);
|
||||
WriteRecord();
|
||||
}
|
||||
|
||||
outputStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the TarBuffer. If this is an output buffer, also flush the
|
||||
/// current block before closing.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (outputStream != null)
|
||||
{
|
||||
WriteFinalRecord();
|
||||
|
||||
if (IsStreamOwner)
|
||||
{
|
||||
outputStream.Dispose();
|
||||
}
|
||||
outputStream = null;
|
||||
}
|
||||
else if (inputStream != null)
|
||||
{
|
||||
if (IsStreamOwner)
|
||||
{
|
||||
inputStream.Dispose();
|
||||
}
|
||||
inputStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Instance Fields
|
||||
|
||||
private Stream inputStream;
|
||||
private Stream outputStream;
|
||||
|
||||
private byte[] recordBuffer;
|
||||
private int currentBlockIndex;
|
||||
private int currentRecordIndex;
|
||||
|
||||
private int recordSize = DefaultRecordSize;
|
||||
private int blockFactor = DefaultBlockFactor;
|
||||
|
||||
#endregion Instance Fields
|
||||
}
|
||||
}
|
||||
598
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarEntry.cs
Normal file
598
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarEntry.cs
Normal file
@@ -0,0 +1,598 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Core;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Tar
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents an entry in a Tar archive. It consists
|
||||
/// of the entry's header, as well as the entry's File. Entries
|
||||
/// can be instantiated in one of three ways, depending on how
|
||||
/// they are to be used.
|
||||
/// <p>
|
||||
/// TarEntries that are created from the header bytes read from
|
||||
/// an archive are instantiated with the TarEntry( byte[] )
|
||||
/// constructor. These entries will be used when extracting from
|
||||
/// or listing the contents of an archive. These entries have their
|
||||
/// header filled in using the header bytes. They also set the File
|
||||
/// to null, since they reference an archive entry not a file.</p>
|
||||
/// <p>
|
||||
/// TarEntries that are created from files that are to be written
|
||||
/// into an archive are instantiated with the CreateEntryFromFile(string)
|
||||
/// pseudo constructor. These entries have their header filled in using
|
||||
/// the File's information. They also keep a reference to the File
|
||||
/// for convenience when writing entries.</p>
|
||||
/// <p>
|
||||
/// Finally, TarEntries can be constructed from nothing but a name.
|
||||
/// This allows the programmer to construct the entry by hand, for
|
||||
/// instance when only an InputStream is available for writing to
|
||||
/// the archive, and the header information is constructed from
|
||||
/// other information. In this case the header fields are set to
|
||||
/// defaults and the File is set to null.</p>
|
||||
/// <see cref="TarHeader"/>
|
||||
/// </summary>
|
||||
public class TarEntry
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a default instance of <see cref="TarEntry"/>.
|
||||
/// </summary>
|
||||
private TarEntry()
|
||||
{
|
||||
header = new TarHeader();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an entry from an archive's header bytes. File is set
|
||||
/// to null.
|
||||
/// </summary>
|
||||
/// <param name = "headerBuffer">
|
||||
/// The header bytes from a tar archive entry.
|
||||
/// </param>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
public TarEntry(byte[] headerBuffer) : this(headerBuffer, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an entry from an archive's header bytes. File is set
|
||||
/// to null.
|
||||
/// </summary>
|
||||
/// <param name = "headerBuffer">
|
||||
/// The header bytes from a tar archive entry.
|
||||
/// </param>
|
||||
/// <param name = "nameEncoding">
|
||||
/// The <see cref="Encoding"/> used for the Name fields, or null for ASCII only
|
||||
/// </param>
|
||||
public TarEntry(byte[] headerBuffer, Encoding nameEncoding)
|
||||
{
|
||||
header = new TarHeader();
|
||||
header.ParseBuffer(headerBuffer, nameEncoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a TarEntry using the <paramref name="header">header</paramref> provided
|
||||
/// </summary>
|
||||
/// <param name="header">Header details for entry</param>
|
||||
public TarEntry(TarHeader header)
|
||||
{
|
||||
if (header == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(header));
|
||||
}
|
||||
|
||||
this.header = (TarHeader)header.Clone();
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region ICloneable Members
|
||||
|
||||
/// <summary>
|
||||
/// Clone this tar entry.
|
||||
/// </summary>
|
||||
/// <returns>Returns a clone of this entry.</returns>
|
||||
public object Clone()
|
||||
{
|
||||
var entry = new TarEntry();
|
||||
entry.file = file;
|
||||
entry.header = (TarHeader)header.Clone();
|
||||
entry.Name = Name;
|
||||
return entry;
|
||||
}
|
||||
|
||||
#endregion ICloneable Members
|
||||
|
||||
/// <summary>
|
||||
/// Construct an entry with only a <paramref name="name">name</paramref>.
|
||||
/// This allows the programmer to construct the entry's header "by hand".
|
||||
/// </summary>
|
||||
/// <param name="name">The name to use for the entry</param>
|
||||
/// <returns>Returns the newly created <see cref="TarEntry"/></returns>
|
||||
public static TarEntry CreateTarEntry(string name)
|
||||
{
|
||||
var entry = new TarEntry();
|
||||
TarEntry.NameTarHeader(entry.header, name);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an entry for a file. File is set to file, and the
|
||||
/// header is constructed from information from the file.
|
||||
/// </summary>
|
||||
/// <param name = "fileName">The file name that the entry represents.</param>
|
||||
/// <returns>Returns the newly created <see cref="TarEntry"/></returns>
|
||||
public static TarEntry CreateEntryFromFile(string fileName)
|
||||
{
|
||||
var entry = new TarEntry();
|
||||
entry.GetFileTarHeader(entry.header, fileName);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the two entries are equal. Equality is determined
|
||||
/// by the header names being equal.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <see cref="Object"/> to compare with the current Object.</param>
|
||||
/// <returns>
|
||||
/// True if the entries are equal; false if not.
|
||||
/// </returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var localEntry = obj as TarEntry;
|
||||
|
||||
if (localEntry != null)
|
||||
{
|
||||
return Name.Equals(localEntry.Name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derive a Hash value for the current <see cref="Object"/>
|
||||
/// </summary>
|
||||
/// <returns>A Hash code for the current <see cref="Object"/></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the given entry is a descendant of this entry.
|
||||
/// Descendancy is determined by the name of the descendant
|
||||
/// starting with this entry's name.
|
||||
/// </summary>
|
||||
/// <param name = "toTest">
|
||||
/// Entry to be checked as a descendent of this.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if entry is a descendant of this.
|
||||
/// </returns>
|
||||
public bool IsDescendent(TarEntry toTest)
|
||||
{
|
||||
if (toTest == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(toTest));
|
||||
}
|
||||
|
||||
return toTest.Name.StartsWith(Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get this entry's header.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// This entry's TarHeader.
|
||||
/// </returns>
|
||||
public TarHeader TarHeader
|
||||
{
|
||||
get
|
||||
{
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/Set this entry's name.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return header.Name;
|
||||
}
|
||||
set
|
||||
{
|
||||
header.Name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set this entry's user id.
|
||||
/// </summary>
|
||||
public int UserId
|
||||
{
|
||||
get
|
||||
{
|
||||
return header.UserId;
|
||||
}
|
||||
set
|
||||
{
|
||||
header.UserId = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set this entry's group id.
|
||||
/// </summary>
|
||||
public int GroupId
|
||||
{
|
||||
get
|
||||
{
|
||||
return header.GroupId;
|
||||
}
|
||||
set
|
||||
{
|
||||
header.GroupId = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set this entry's user name.
|
||||
/// </summary>
|
||||
public string UserName
|
||||
{
|
||||
get
|
||||
{
|
||||
return header.UserName;
|
||||
}
|
||||
set
|
||||
{
|
||||
header.UserName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set this entry's group name.
|
||||
/// </summary>
|
||||
public string GroupName
|
||||
{
|
||||
get
|
||||
{
|
||||
return header.GroupName;
|
||||
}
|
||||
set
|
||||
{
|
||||
header.GroupName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to set this entry's group and user ids.
|
||||
/// </summary>
|
||||
/// <param name="userId">
|
||||
/// This entry's new user id.
|
||||
/// </param>
|
||||
/// <param name="groupId">
|
||||
/// This entry's new group id.
|
||||
/// </param>
|
||||
public void SetIds(int userId, int groupId)
|
||||
{
|
||||
UserId = userId;
|
||||
GroupId = groupId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method to set this entry's group and user names.
|
||||
/// </summary>
|
||||
/// <param name="userName">
|
||||
/// This entry's new user name.
|
||||
/// </param>
|
||||
/// <param name="groupName">
|
||||
/// This entry's new group name.
|
||||
/// </param>
|
||||
public void SetNames(string userName, string groupName)
|
||||
{
|
||||
UserName = userName;
|
||||
GroupName = groupName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/Set the modification time for this entry
|
||||
/// </summary>
|
||||
public DateTime ModTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return header.ModTime;
|
||||
}
|
||||
set
|
||||
{
|
||||
header.ModTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get this entry's file.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// This entry's file.
|
||||
/// </returns>
|
||||
public string File
|
||||
{
|
||||
get
|
||||
{
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get/set this entry's recorded file size.
|
||||
/// </summary>
|
||||
public long Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return header.Size;
|
||||
}
|
||||
set
|
||||
{
|
||||
header.Size = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if this entry represents a directory, false otherwise
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// True if this entry is a directory.
|
||||
/// </returns>
|
||||
public bool IsDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
if (file != null)
|
||||
{
|
||||
return Directory.Exists(file);
|
||||
}
|
||||
|
||||
if (header != null)
|
||||
{
|
||||
if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill in a TarHeader with information from a File.
|
||||
/// </summary>
|
||||
/// <param name="header">
|
||||
/// The TarHeader to fill in.
|
||||
/// </param>
|
||||
/// <param name="file">
|
||||
/// The file from which to get the header information.
|
||||
/// </param>
|
||||
public void GetFileTarHeader(TarHeader header, string file)
|
||||
{
|
||||
if (header == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(header));
|
||||
}
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
}
|
||||
|
||||
this.file = file;
|
||||
|
||||
// bugfix from torhovl from #D forum:
|
||||
string name = file;
|
||||
|
||||
// 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory
|
||||
if (name.IndexOf(Directory.GetCurrentDirectory(), StringComparison.Ordinal) == 0)
|
||||
{
|
||||
name = name.Substring(Directory.GetCurrentDirectory().Length);
|
||||
}
|
||||
|
||||
/*
|
||||
if (Path.DirectorySeparatorChar == '\\')
|
||||
{
|
||||
// check if the OS is Windows
|
||||
// Strip off drive letters!
|
||||
if (name.Length > 2)
|
||||
{
|
||||
char ch1 = name[0];
|
||||
char ch2 = name[1];
|
||||
|
||||
if (ch2 == ':' && Char.IsLetter(ch1))
|
||||
{
|
||||
name = name.Substring(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
name = name.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
// No absolute pathnames
|
||||
// Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\",
|
||||
// so we loop on starting /'s.
|
||||
while (name.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
name = name.Substring(1);
|
||||
}
|
||||
|
||||
header.LinkName = String.Empty;
|
||||
header.Name = name;
|
||||
|
||||
if (Directory.Exists(file))
|
||||
{
|
||||
header.Mode = 1003; // Magic number for security access for a UNIX filesystem
|
||||
header.TypeFlag = TarHeader.LF_DIR;
|
||||
if ((header.Name.Length == 0) || header.Name[header.Name.Length - 1] != '/')
|
||||
{
|
||||
header.Name = header.Name + "/";
|
||||
}
|
||||
|
||||
header.Size = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
header.Mode = 33216; // Magic number for security access for a UNIX filesystem
|
||||
header.TypeFlag = TarHeader.LF_NORMAL;
|
||||
header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length;
|
||||
}
|
||||
|
||||
header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime();
|
||||
header.DevMajor = 0;
|
||||
header.DevMinor = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get entries for all files present in this entries directory.
|
||||
/// If this entry doesnt represent a directory zero entries are returned.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of TarEntry's for this entry's children.
|
||||
/// </returns>
|
||||
public TarEntry[] GetDirectoryEntries()
|
||||
{
|
||||
if ((file == null) || !Directory.Exists(file))
|
||||
{
|
||||
return Empty.Array<TarEntry>();
|
||||
}
|
||||
|
||||
string[] list = Directory.GetFileSystemEntries(file);
|
||||
TarEntry[] result = new TarEntry[list.Length];
|
||||
|
||||
for (int i = 0; i < list.Length; ++i)
|
||||
{
|
||||
result[i] = TarEntry.CreateEntryFromFile(list[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write an entry's header information to a header buffer.
|
||||
/// </summary>
|
||||
/// <param name = "outBuffer">
|
||||
/// The tar entry header buffer to fill in.
|
||||
/// </param>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
public void WriteEntryHeader(byte[] outBuffer)
|
||||
{
|
||||
WriteEntryHeader(outBuffer, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write an entry's header information to a header buffer.
|
||||
/// </summary>
|
||||
/// <param name = "outBuffer">
|
||||
/// The tar entry header buffer to fill in.
|
||||
/// </param>
|
||||
/// <param name = "nameEncoding">
|
||||
/// The <see cref="Encoding"/> used for the Name fields, or null for ASCII only
|
||||
/// </param>
|
||||
public void WriteEntryHeader(byte[] outBuffer, Encoding nameEncoding)
|
||||
{
|
||||
header.WriteHeader(outBuffer, nameEncoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method that will modify an entry's name directly
|
||||
/// in place in an entry header buffer byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">
|
||||
/// The buffer containing the entry header to modify.
|
||||
/// </param>
|
||||
/// <param name="newName">
|
||||
/// The new name to place into the header buffer.
|
||||
/// </param>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
static public void AdjustEntryName(byte[] buffer, string newName)
|
||||
{
|
||||
AdjustEntryName(buffer, newName, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method that will modify an entry's name directly
|
||||
/// in place in an entry header buffer byte array.
|
||||
/// </summary>
|
||||
/// <param name="buffer">
|
||||
/// The buffer containing the entry header to modify.
|
||||
/// </param>
|
||||
/// <param name="newName">
|
||||
/// The new name to place into the header buffer.
|
||||
/// </param>
|
||||
/// <param name="nameEncoding">
|
||||
/// The <see cref="Encoding"/> used for the Name fields, or null for ASCII only
|
||||
/// </param>
|
||||
static public void AdjustEntryName(byte[] buffer, string newName, Encoding nameEncoding)
|
||||
{
|
||||
TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN, nameEncoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill in a TarHeader given only the entry's name.
|
||||
/// </summary>
|
||||
/// <param name="header">
|
||||
/// The TarHeader to fill in.
|
||||
/// </param>
|
||||
/// <param name="name">
|
||||
/// The tar entry name.
|
||||
/// </param>
|
||||
static public void NameTarHeader(TarHeader header, string name)
|
||||
{
|
||||
if (header == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(header));
|
||||
}
|
||||
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
bool isDir = name.EndsWith("/", StringComparison.Ordinal);
|
||||
|
||||
header.Name = name;
|
||||
header.Mode = isDir ? 1003 : 33216;
|
||||
header.UserId = 0;
|
||||
header.GroupId = 0;
|
||||
header.Size = 0;
|
||||
|
||||
header.ModTime = DateTime.UtcNow;
|
||||
|
||||
header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
|
||||
|
||||
header.LinkName = String.Empty;
|
||||
header.UserName = String.Empty;
|
||||
header.GroupName = String.Empty;
|
||||
|
||||
header.DevMajor = 0;
|
||||
header.DevMinor = 0;
|
||||
}
|
||||
|
||||
#region Instance Fields
|
||||
|
||||
/// <summary>
|
||||
/// The name of the file this entry represents or null if the entry is not based on a file.
|
||||
/// </summary>
|
||||
private string file;
|
||||
|
||||
/// <summary>
|
||||
/// The entry's header information.
|
||||
/// </summary>
|
||||
private TarHeader header;
|
||||
|
||||
#endregion Instance Fields
|
||||
}
|
||||
}
|
||||
54
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarException.cs
Normal file
54
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarException.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Tar
|
||||
{
|
||||
/// <summary>
|
||||
/// TarException represents exceptions specific to Tar classes and code.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TarException : SharpZipBaseException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="TarException" />.
|
||||
/// </summary>
|
||||
public TarException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="TarException" /> with its message string.
|
||||
/// </summary>
|
||||
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||
public TarException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialise a new instance of <see cref="TarException" />.
|
||||
/// </summary>
|
||||
/// <param name="message">A <see cref="string"/> that describes the error.</param>
|
||||
/// <param name="innerException">The <see cref="Exception"/> that caused this exception.</param>
|
||||
public TarException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the TarException class with serialized data.
|
||||
/// </summary>
|
||||
/// <param name="info">
|
||||
/// The System.Runtime.Serialization.SerializationInfo that holds the serialized
|
||||
/// object data about the exception being thrown.
|
||||
/// </param>
|
||||
/// <param name="context">
|
||||
/// The System.Runtime.Serialization.StreamingContext that contains contextual information
|
||||
/// about the source or destination.
|
||||
/// </param>
|
||||
protected TarException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Tar
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads the extended header of a Tar stream
|
||||
/// </summary>
|
||||
public class TarExtendedHeaderReader
|
||||
{
|
||||
private const byte LENGTH = 0;
|
||||
private const byte KEY = 1;
|
||||
private const byte VALUE = 2;
|
||||
private const byte END = 3;
|
||||
|
||||
private readonly Dictionary<string, string> headers = new Dictionary<string, string>();
|
||||
|
||||
private string[] headerParts = new string[3];
|
||||
|
||||
private int bbIndex;
|
||||
private byte[] byteBuffer;
|
||||
private char[] charBuffer;
|
||||
|
||||
private readonly StringBuilder sb = new StringBuilder();
|
||||
private readonly Decoder decoder = Encoding.UTF8.GetDecoder();
|
||||
|
||||
private int state = LENGTH;
|
||||
|
||||
private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' };
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="TarExtendedHeaderReader"/>.
|
||||
/// </summary>
|
||||
public TarExtendedHeaderReader()
|
||||
{
|
||||
ResetBuffers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read <paramref name="length"/> bytes from <paramref name="buffer"/>
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="length"></param>
|
||||
public void Read(byte[] buffer, int length)
|
||||
{
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
byte next = buffer[i];
|
||||
|
||||
if (next == StateNext[state])
|
||||
{
|
||||
Flush();
|
||||
headerParts[state] = sb.ToString();
|
||||
sb.Clear();
|
||||
|
||||
if (++state == END)
|
||||
{
|
||||
headers.Add(headerParts[KEY], headerParts[VALUE]);
|
||||
headerParts = new string[3];
|
||||
state = LENGTH;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byteBuffer[bbIndex++] = next;
|
||||
if (bbIndex == 4)
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Flush()
|
||||
{
|
||||
decoder.Convert(byteBuffer, 0, bbIndex, charBuffer, 0, 4, false, out int bytesUsed, out int charsUsed, out bool completed);
|
||||
|
||||
sb.Append(charBuffer, 0, charsUsed);
|
||||
ResetBuffers();
|
||||
}
|
||||
|
||||
private void ResetBuffers()
|
||||
{
|
||||
charBuffer = new char[4];
|
||||
byteBuffer = new byte[4];
|
||||
bbIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the parsed headers as key-value strings
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Headers
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO: Check for invalid state? -NM 2018-07-01
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1310
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarHeader.cs
Normal file
1310
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarHeader.cs
Normal file
File diff suppressed because it is too large
Load Diff
771
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs
Normal file
771
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs
Normal file
@@ -0,0 +1,771 @@
|
||||
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>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
public TarInputStream(Stream inputStream)
|
||||
: this(inputStream, TarBuffer.DefaultBlockFactor, null)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Construct a TarInputStream with default block factor
|
||||
/// </summary>
|
||||
/// <param name="inputStream">stream to source data from</param>
|
||||
/// <param name="nameEncoding">The <see cref="Encoding"/> used for the Name fields, or null for ASCII only</param>
|
||||
public TarInputStream(Stream inputStream, Encoding nameEncoding)
|
||||
: this(inputStream, TarBuffer.DefaultBlockFactor, nameEncoding)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
public TarInputStream(Stream inputStream, int blockFactor)
|
||||
{
|
||||
this.inputStream = inputStream;
|
||||
tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
|
||||
encoding = null;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="nameEncoding">The <see cref="Encoding"/> used for the Name fields, or null for ASCII only</param>
|
||||
public TarInputStream(Stream inputStream, int blockFactor, Encoding nameEncoding)
|
||||
{
|
||||
this.inputStream = inputStream;
|
||||
tarBuffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
|
||||
encoding = nameEncoding;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
/// <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 Stream Overrides
|
||||
|
||||
/// <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;
|
||||
|
||||
// Read the second zero-filled block
|
||||
tarBuffer.ReadBlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
hasHitEOF = false;
|
||||
}
|
||||
|
||||
if (hasHitEOF)
|
||||
{
|
||||
currentEntry = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var header = new TarHeader();
|
||||
header.ParseBuffer(headerBuf, encoding);
|
||||
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, encoding).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
|
||||
byte[] nameBuffer = new byte[TarBuffer.BlockSize];
|
||||
long numToRead = this.entrySize;
|
||||
|
||||
var xhr = new TarExtendedHeaderReader();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
xhr.Read(nameBuffer, numRead);
|
||||
numToRead -= numRead;
|
||||
}
|
||||
|
||||
if (xhr.Headers.TryGetValue("path", out string name))
|
||||
{
|
||||
longName = new StringBuilder(name);
|
||||
}
|
||||
|
||||
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, encoding);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private 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
|
||||
{
|
||||
// This interface does not considering name encoding.
|
||||
// How this interface should be?
|
||||
/// <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 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
|
||||
{
|
||||
Encoding nameEncoding;
|
||||
/// <summary>
|
||||
/// Construct standard entry factory class with ASCII name encoding
|
||||
/// </summary>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
public EntryFactoryAdapter()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Construct standard entry factory with name encoding
|
||||
/// </summary>
|
||||
/// <param name="nameEncoding">The <see cref="Encoding"/> used for the Name fields, or null for ASCII only</param>
|
||||
public EntryFactoryAdapter(Encoding nameEncoding)
|
||||
{
|
||||
this.nameEncoding = nameEncoding;
|
||||
}
|
||||
/// <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, nameEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
#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>
|
||||
private 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>
|
||||
private readonly Stream inputStream;
|
||||
|
||||
private readonly Encoding encoding;
|
||||
|
||||
#endregion Instance Fields
|
||||
}
|
||||
}
|
||||
522
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarOutputStream.cs
Normal file
522
常用工具集/Utility/ICSharpCode.SharpZipLib/Tar/TarOutputStream.cs
Normal file
@@ -0,0 +1,522 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace ICSharpCode.SharpZipLib.Tar
|
||||
{
|
||||
/// <summary>
|
||||
/// The TarOutputStream writes a UNIX tar archive as an OutputStream.
|
||||
/// Methods are provided to put entries, and then write their contents
|
||||
/// by writing to this stream using write().
|
||||
/// </summary>
|
||||
/// public
|
||||
public class TarOutputStream : Stream
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct TarOutputStream using default block factor
|
||||
/// </summary>
|
||||
/// <param name="outputStream">stream to write to</param>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
public TarOutputStream(Stream outputStream)
|
||||
: this(outputStream, TarBuffer.DefaultBlockFactor)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct TarOutputStream using default block factor
|
||||
/// </summary>
|
||||
/// <param name="outputStream">stream to write to</param>
|
||||
/// <param name="nameEncoding">The <see cref="Encoding"/> used for the Name fields, or null for ASCII only</param>
|
||||
public TarOutputStream(Stream outputStream, Encoding nameEncoding)
|
||||
: this(outputStream, TarBuffer.DefaultBlockFactor, nameEncoding)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct TarOutputStream with user specified block factor
|
||||
/// </summary>
|
||||
/// <param name="outputStream">stream to write to</param>
|
||||
/// <param name="blockFactor">blocking factor</param>
|
||||
[Obsolete("No Encoding for Name field is specified, any non-ASCII bytes will be discarded")]
|
||||
public TarOutputStream(Stream outputStream, int blockFactor)
|
||||
{
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outputStream));
|
||||
}
|
||||
|
||||
this.outputStream = outputStream;
|
||||
buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor);
|
||||
|
||||
assemblyBuffer = new byte[TarBuffer.BlockSize];
|
||||
blockBuffer = new byte[TarBuffer.BlockSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct TarOutputStream with user specified block factor
|
||||
/// </summary>
|
||||
/// <param name="outputStream">stream to write to</param>
|
||||
/// <param name="blockFactor">blocking factor</param>
|
||||
/// <param name="nameEncoding">The <see cref="Encoding"/> used for the Name fields, or null for ASCII only</param>
|
||||
public TarOutputStream(Stream outputStream, int blockFactor, Encoding nameEncoding)
|
||||
{
|
||||
if (outputStream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(outputStream));
|
||||
}
|
||||
|
||||
this.outputStream = outputStream;
|
||||
buffer = TarBuffer.CreateOutputTarBuffer(outputStream, blockFactor);
|
||||
|
||||
assemblyBuffer = new byte[TarBuffer.BlockSize];
|
||||
blockBuffer = new byte[TarBuffer.BlockSize];
|
||||
|
||||
this.nameEncoding = nameEncoding;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
/// <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 buffer.IsStreamOwner; }
|
||||
set { buffer.IsStreamOwner = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// true if the stream supports reading; otherwise, false.
|
||||
/// </summary>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return outputStream.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// true if the stream supports seeking; otherwise, false.
|
||||
/// </summary>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return outputStream.CanSeek;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// true if stream supports writing; otherwise, false.
|
||||
/// </summary>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return outputStream.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// length of stream in bytes
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return outputStream.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// gets or sets the position within the current stream.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return outputStream.Position;
|
||||
}
|
||||
set
|
||||
{
|
||||
outputStream.Position = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// set the position within the current stream
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset relative to the <paramref name="origin"/> to seek to</param>
|
||||
/// <param name="origin">The <see cref="SeekOrigin"/> to seek from.</param>
|
||||
/// <returns>The new position in the stream.</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return outputStream.Seek(offset, origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the length of the current stream
|
||||
/// </summary>
|
||||
/// <param name="value">The new stream length.</param>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
outputStream.SetLength(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a byte from the stream and advance the position within the stream
|
||||
/// by one byte or returns -1 if at the end of the stream.
|
||||
/// </summary>
|
||||
/// <returns>The byte value or -1 if at end of stream</returns>
|
||||
public override int ReadByte()
|
||||
{
|
||||
return outputStream.ReadByte();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// read bytes from the current stream and advance the position within the
|
||||
/// stream by the number of bytes read.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to store read bytes in.</param>
|
||||
/// <param name="offset">The index into the buffer to being storing bytes at.</param>
|
||||
/// <param name="count">The desired number of bytes to read.</param>
|
||||
/// <returns>The total number of bytes read, or zero if at the end of the stream.
|
||||
/// The number of bytes may be less than the <paramref name="count">count</paramref>
|
||||
/// requested if data is not available.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return outputStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// All buffered data is written to destination
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
outputStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the TAR archive without closing the underlying OutputStream.
|
||||
/// The result is that the EOF block of nulls is written.
|
||||
/// </summary>
|
||||
public void Finish()
|
||||
{
|
||||
if (IsEntryOpen)
|
||||
{
|
||||
CloseEntry();
|
||||
}
|
||||
WriteEofBlock();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the TAR archive and closes the underlying OutputStream.
|
||||
/// </summary>
|
||||
/// <remarks>This means that Finish() is called followed by calling the
|
||||
/// TarBuffer's Close().</remarks>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!isClosed)
|
||||
{
|
||||
isClosed = true;
|
||||
Finish();
|
||||
buffer.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the record size being used by this stream's TarBuffer.
|
||||
/// </summary>
|
||||
public int RecordSize
|
||||
{
|
||||
get { return buffer.RecordSize; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the record size being used by this stream's TarBuffer.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The TarBuffer record size.
|
||||
/// </returns>
|
||||
[Obsolete("Use RecordSize property instead")]
|
||||
public int GetRecordSize()
|
||||
{
|
||||
return buffer.RecordSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a value indicating whether an entry is open, requiring more data to be written.
|
||||
/// </summary>
|
||||
private bool IsEntryOpen
|
||||
{
|
||||
get { return (currBytes < currSize); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Put an entry on the output stream. This writes the entry's
|
||||
/// header and positions the output stream for writing
|
||||
/// the contents of the entry. Once this method is called, the
|
||||
/// stream is ready for calls to write() to write the entry's
|
||||
/// contents. Once the contents are written, closeEntry()
|
||||
/// <B>MUST</B> be called to ensure that all buffered data
|
||||
/// is completely written to the output stream.
|
||||
/// </summary>
|
||||
/// <param name="entry">
|
||||
/// The TarEntry to be written to the archive.
|
||||
/// </param>
|
||||
public void PutNextEntry(TarEntry entry)
|
||||
{
|
||||
if (entry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entry));
|
||||
}
|
||||
|
||||
var namelen = nameEncoding != null ? nameEncoding.GetByteCount(entry.TarHeader.Name) : entry.TarHeader.Name.Length;
|
||||
|
||||
if (namelen > TarHeader.NAMELEN)
|
||||
{
|
||||
var longHeader = new TarHeader();
|
||||
longHeader.TypeFlag = TarHeader.LF_GNU_LONGNAME;
|
||||
longHeader.Name = longHeader.Name + "././@LongLink";
|
||||
longHeader.Mode = 420;//644 by default
|
||||
longHeader.UserId = entry.UserId;
|
||||
longHeader.GroupId = entry.GroupId;
|
||||
longHeader.GroupName = entry.GroupName;
|
||||
longHeader.UserName = entry.UserName;
|
||||
longHeader.LinkName = "";
|
||||
longHeader.Size = namelen + 1; // Plus one to avoid dropping last char
|
||||
|
||||
longHeader.WriteHeader(blockBuffer, nameEncoding);
|
||||
buffer.WriteBlock(blockBuffer); // Add special long filename header block
|
||||
|
||||
int nameCharIndex = 0;
|
||||
|
||||
while (nameCharIndex < namelen + 1 /* we've allocated one for the null char, now we must make sure it gets written out */)
|
||||
{
|
||||
Array.Clear(blockBuffer, 0, blockBuffer.Length);
|
||||
TarHeader.GetAsciiBytes(entry.TarHeader.Name, nameCharIndex, this.blockBuffer, 0, TarBuffer.BlockSize, nameEncoding); // This func handles OK the extra char out of string length
|
||||
nameCharIndex += TarBuffer.BlockSize;
|
||||
buffer.WriteBlock(blockBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
entry.WriteEntryHeader(blockBuffer, nameEncoding);
|
||||
buffer.WriteBlock(blockBuffer);
|
||||
|
||||
currBytes = 0;
|
||||
|
||||
currSize = entry.IsDirectory ? 0 : entry.Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close an entry. This method MUST be called for all file
|
||||
/// entries that contain data. The reason is that we must
|
||||
/// buffer data written to the stream in order to satisfy
|
||||
/// the buffer's block based writes. Thus, there may be
|
||||
/// data fragments still being assembled that must be written
|
||||
/// to the output stream before this entry is closed and the
|
||||
/// next entry written.
|
||||
/// </summary>
|
||||
public void CloseEntry()
|
||||
{
|
||||
if (assemblyBufferLength > 0)
|
||||
{
|
||||
Array.Clear(assemblyBuffer, assemblyBufferLength, assemblyBuffer.Length - assemblyBufferLength);
|
||||
|
||||
buffer.WriteBlock(assemblyBuffer);
|
||||
|
||||
currBytes += assemblyBufferLength;
|
||||
assemblyBufferLength = 0;
|
||||
}
|
||||
|
||||
if (currBytes < currSize)
|
||||
{
|
||||
string errorText = string.Format(
|
||||
"Entry closed at '{0}' before the '{1}' bytes specified in the header were written",
|
||||
currBytes, currSize);
|
||||
throw new TarException(errorText);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte to the current tar archive entry.
|
||||
/// This method simply calls Write(byte[], int, int).
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The byte to be written.
|
||||
/// </param>
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
Write(new byte[] { value }, 0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes bytes to the current tar archive entry. This method
|
||||
/// is aware of the current entry and will throw an exception if
|
||||
/// you attempt to write bytes past the length specified for the
|
||||
/// current entry. The method is also (painfully) aware of the
|
||||
/// record buffering required by TarBuffer, and manages buffers
|
||||
/// that are not a multiple of recordsize in length, including
|
||||
/// assembling records from small buffers.
|
||||
/// </summary>
|
||||
/// <param name = "buffer">
|
||||
/// The buffer to write to the archive.
|
||||
/// </param>
|
||||
/// <param name = "offset">
|
||||
/// The offset in the buffer from which to get bytes.
|
||||
/// </param>
|
||||
/// <param name = "count">
|
||||
/// The number of bytes to write.
|
||||
/// </param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), "Cannot be negative");
|
||||
}
|
||||
|
||||
if (buffer.Length - offset < count)
|
||||
{
|
||||
throw new ArgumentException("offset and count combination is invalid");
|
||||
}
|
||||
|
||||
if (count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "Cannot be negative");
|
||||
}
|
||||
|
||||
if ((currBytes + count) > currSize)
|
||||
{
|
||||
string errorText = string.Format("request to write '{0}' bytes exceeds size in header of '{1}' bytes",
|
||||
count, this.currSize);
|
||||
throw new ArgumentOutOfRangeException(nameof(count), errorText);
|
||||
}
|
||||
|
||||
//
|
||||
// We have to deal with assembly!!!
|
||||
// The programmer can be writing little 32 byte chunks for all
|
||||
// we know, and we must assemble complete blocks for writing.
|
||||
// TODO REVIEW Maybe this should be in TarBuffer? Could that help to
|
||||
// eliminate some of the buffer copying.
|
||||
//
|
||||
if (assemblyBufferLength > 0)
|
||||
{
|
||||
if ((assemblyBufferLength + count) >= blockBuffer.Length)
|
||||
{
|
||||
int aLen = blockBuffer.Length - assemblyBufferLength;
|
||||
|
||||
Array.Copy(assemblyBuffer, 0, blockBuffer, 0, assemblyBufferLength);
|
||||
Array.Copy(buffer, offset, blockBuffer, assemblyBufferLength, aLen);
|
||||
|
||||
this.buffer.WriteBlock(blockBuffer);
|
||||
|
||||
currBytes += blockBuffer.Length;
|
||||
|
||||
offset += aLen;
|
||||
count -= aLen;
|
||||
|
||||
assemblyBufferLength = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count);
|
||||
offset += count;
|
||||
assemblyBufferLength += count;
|
||||
count -= count;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// When we get here we have EITHER:
|
||||
// o An empty "assembly" buffer.
|
||||
// o No bytes to write (count == 0)
|
||||
//
|
||||
while (count > 0)
|
||||
{
|
||||
if (count < blockBuffer.Length)
|
||||
{
|
||||
Array.Copy(buffer, offset, assemblyBuffer, assemblyBufferLength, count);
|
||||
assemblyBufferLength += count;
|
||||
break;
|
||||
}
|
||||
|
||||
this.buffer.WriteBlock(buffer, offset);
|
||||
|
||||
int bufferLength = blockBuffer.Length;
|
||||
currBytes += bufferLength;
|
||||
count -= bufferLength;
|
||||
offset += bufferLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write an EOF (end of archive) block to the tar archive.
|
||||
/// The end of the archive is indicated by two blocks consisting entirely of zero bytes.
|
||||
/// </summary>
|
||||
private void WriteEofBlock()
|
||||
{
|
||||
Array.Clear(blockBuffer, 0, blockBuffer.Length);
|
||||
buffer.WriteBlock(blockBuffer);
|
||||
buffer.WriteBlock(blockBuffer);
|
||||
}
|
||||
|
||||
#region Instance Fields
|
||||
|
||||
/// <summary>
|
||||
/// bytes written for this entry so far
|
||||
/// </summary>
|
||||
private long currBytes;
|
||||
|
||||
/// <summary>
|
||||
/// current 'Assembly' buffer length
|
||||
/// </summary>
|
||||
private int assemblyBufferLength;
|
||||
|
||||
/// <summary>
|
||||
/// Flag indicating whether this instance has been closed or not.
|
||||
/// </summary>
|
||||
private bool isClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Size for the current entry
|
||||
/// </summary>
|
||||
protected long currSize;
|
||||
|
||||
/// <summary>
|
||||
/// single block working buffer
|
||||
/// </summary>
|
||||
protected byte[] blockBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// 'Assembly' buffer used to assemble data before writing
|
||||
/// </summary>
|
||||
protected byte[] assemblyBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// TarBuffer used to provide correct blocking factor
|
||||
/// </summary>
|
||||
protected TarBuffer buffer;
|
||||
|
||||
/// <summary>
|
||||
/// the destination stream for the archive contents
|
||||
/// </summary>
|
||||
protected Stream outputStream;
|
||||
|
||||
/// <summary>
|
||||
/// name encoding
|
||||
/// </summary>
|
||||
protected Encoding nameEncoding;
|
||||
|
||||
#endregion Instance Fields
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user