📄 zipfile.cs
字号:
baseStream.Seek(pos--, SeekOrigin.Begin);
} while (ReadLeInt() != signature);
return baseStream.Position;
}
/// <summary>
/// Search for and read the central directory of a zip file filling the entries
/// array. This is called exactly once by the constructors.
/// </summary>
/// <exception cref="System.IO.IOException">
/// An i/o error occurs.
/// </exception>
/// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
/// The central directory is malformed or cannot be found
/// </exception>
void ReadEntries()
{
// Search for the End Of Central Directory. When a zip comment is
// present the directory may start earlier.
//
// TODO: The search is limited to 64K which is the maximum size of a trailing comment field to aid speed.
// This should be compatible with both SFX and ZIP files but has only been tested for Zip files
// Need to confirm this is valid in all cases.
// Could also speed this up by reading memory in larger blocks.
if (baseStream.CanSeek == false) {
throw new ZipException("ZipFile stream must be seekable");
}
long locatedCentralDirOffset = LocateBlockWithSignature(ZipConstants.ENDSIG, baseStream.Length, ZipConstants.ENDHDR, 0xffff);
if (locatedCentralDirOffset < 0) {
throw new ZipException("Cannot find central directory");
}
int thisDiskNumber = ReadLeShort();
int startCentralDirDisk = ReadLeShort();
int entriesForThisDisk = ReadLeShort();
int entriesForWholeCentralDir = ReadLeShort();
int centralDirSize = ReadLeInt();
int offsetOfCentralDir = ReadLeInt();
int commentSize = ReadLeShort();
byte[] zipComment = new byte[commentSize];
baseStream.Read(zipComment, 0, zipComment.Length);
comment = ZipConstants.ConvertToString(zipComment);
/* Its seems possible that this is too strict, more digging required.
if (thisDiskNumber != 0 || startCentralDirDisk != 0 || entriesForThisDisk != entriesForWholeCentralDir) {
throw new ZipException("Spanned archives are not currently handled");
}
*/
entries = new ZipEntry[entriesForWholeCentralDir];
// SFX support, find the offset of the first entry vis the start of the stream
// This applies to Zip files that are appended to the end of the SFX stub.
// Zip files created by some archivers have the offsets altered to reflect the true offsets
// and so dont require any adjustment here...
if (offsetOfCentralDir < locatedCentralDirOffset - (4 + centralDirSize)) {
offsetOfFirstEntry = locatedCentralDirOffset - (4 + centralDirSize + offsetOfCentralDir);
if (offsetOfFirstEntry <= 0) {
throw new ZipException("Invalid SFX file");
}
}
baseStream.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin);
for (int i = 0; i < entriesForThisDisk; i++) {
if (ReadLeInt() != ZipConstants.CENSIG) {
throw new ZipException("Wrong Central Directory signature");
}
int versionMadeBy = ReadLeShort();
int versionToExtract = ReadLeShort();
int bitFlags = ReadLeShort();
int method = ReadLeShort();
int dostime = ReadLeInt();
int crc = ReadLeInt();
int csize = ReadLeInt();
int size = ReadLeInt();
int nameLen = ReadLeShort();
int extraLen = ReadLeShort();
int commentLen = ReadLeShort();
int diskStartNo = ReadLeShort(); // Not currently used
int internalAttributes = ReadLeShort(); // Not currently used
int externalAttributes = ReadLeInt();
int offset = ReadLeInt();
byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
baseStream.Read(buffer, 0, nameLen);
string name = ZipConstants.ConvertToString(buffer, nameLen);
ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy);
entry.CompressionMethod = (CompressionMethod)method;
entry.Crc = crc & 0xffffffffL;
entry.Size = size & 0xffffffffL;
entry.CompressedSize = csize & 0xffffffffL;
entry.Flags = bitFlags;
entry.DosTime = (uint)dostime;
if (extraLen > 0) {
byte[] extra = new byte[extraLen];
baseStream.Read(extra, 0, extraLen);
entry.ExtraData = extra;
}
if (commentLen > 0) {
baseStream.Read(buffer, 0, commentLen);
entry.Comment = ZipConstants.ConvertToString(buffer, commentLen);
}
entry.ZipFileIndex = i;
entry.Offset = offset;
entry.ExternalFileAttributes = externalAttributes;
entries[i] = entry;
}
}
/// <summary>
/// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying input stream.
/// Once closed, no further instance methods should be called.
/// </summary>
/// <exception cref="System.IO.IOException">
/// An i/o error occurs.
/// </exception>
public void Close()
{
entries = null;
if ( isStreamOwner ) {
lock(baseStream) {
baseStream.Close();
}
}
}
/// <summary>
/// Returns an enumerator for the Zip entries in this Zip file.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The Zip file has been closed.
/// </exception>
public IEnumerator GetEnumerator()
{
if (entries == null) {
throw new InvalidOperationException("ZipFile has closed");
}
return new ZipEntryEnumeration(entries);
}
/// <summary>
/// Return the index of the entry with a matching name
/// </summary>
/// <param name="name">Entry name to find</param>
/// <param name="ignoreCase">If true the comparison is case insensitive</param>
/// <returns>The index position of the matching entry or -1 if not found</returns>
/// <exception cref="InvalidOperationException">
/// The Zip file has been closed.
/// </exception>
public int FindEntry(string name, bool ignoreCase)
{
if (entries == null) {
throw new InvalidOperationException("ZipFile has been closed");
}
for (int i = 0; i < entries.Length; i++) {
if (string.Compare(name, entries[i].Name, ignoreCase) == 0) {
return i;
}
}
return -1;
}
/// <summary>
/// Indexer property for ZipEntries
/// </summary>
[System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")]
public ZipEntry this[int index] {
get {
return (ZipEntry) entries[index].Clone();
}
}
/// <summary>
/// Searches for a zip entry in this archive with the given name.
/// String comparisons are case insensitive
/// </summary>
/// <param name="name">
/// The name to find. May contain directory components separated by slashes ('/').
/// </param>
/// <returns>
/// The zip entry, or null if no entry with that name exists.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The Zip file has been closed.
/// </exception>
public ZipEntry GetEntry(string name)
{
if (entries == null) {
throw new InvalidOperationException("ZipFile has been closed");
}
int index = FindEntry(name, true);
return index >= 0 ? (ZipEntry) entries[index].Clone() : null;
}
/// <summary>
/// Test an archive for integrity/validity
/// </summary>
/// <param name="testData">Perform low level data Crc check</param>
/// <returns>true iff the test passes, false otherwise</returns>
public bool TestArchive(bool testData)
{
bool result = true;
try {
for (int i = 0; i < Size; ++i) {
long offset = TestLocalHeader(this[i], true, true);
if (testData) {
Stream entryStream = this.GetInputStream(this[i]);
// TODO: events for updating info, recording errors etc
Crc32 crc = new Crc32();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) {
crc.Update(buffer, 0, bytesRead);
}
if (this[i].Crc != crc.Value) {
result = false;
// TODO: Event here....
break; // Do all entries giving more info at some point?
}
}
}
}
catch {
result = false;
}
return result;
}
/// <summary>
/// Test the local header against that provided from the central directory
/// </summary>
/// <param name="entry">
/// The entry to test against
/// </param>
/// <param name="fullTest">
/// If true be extremely picky about the testing, otherwise be relaxed
/// </param>
/// <param name="extractTest">
/// Apply extra testing to see if the entry can be extracted by the library
/// </param>
/// <returns>The offset of the entries data in the file</returns>
long TestLocalHeader(ZipEntry entry, bool fullTest, bool extractTest)
{
lock(baseStream)
{
baseStream.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
if (ReadLeInt() != ZipConstants.LOCSIG) {
throw new ZipException("Wrong local header signature");
}
short shortValue = (short)ReadLeShort(); // version required to extract
if (extractTest == true && shortValue > ZipConstants.VERSION_MADE_BY) {
throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", shortValue));
}
short localFlags = (short)ReadLeShort(); // general purpose bit flags.
if (extractTest == true) {
if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0) {
throw new ZipException("The library doesnt support the zip version required to extract this entry");
}
}
if (localFlags != entry.Flags) {
throw new ZipException("Central header/local header flags mismatch");
}
if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) {
throw new ZipException("Central header/local header compression method mismatch");
}
shortValue = (short)ReadLeShort(); // file time
shortValue = (short)ReadLeShort(); // file date
int intValue = ReadLeInt(); // Crc
if (fullTest) {
if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) {
if (intValue != (int)entry.Crc)
throw new ZipException("Central header/local header crc mismatch");
}
}
intValue = ReadLeInt(); // compressed Size
intValue = ReadLeInt(); // uncompressed size
// TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings
// Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably
int storedNameLength = ReadLeShort();
if (entry.Name.Length > storedNameLength) {
throw new ZipException("file name length mismatch");
}
int extraLen = storedNameLength + ReadLeShort();
return offsetOfFirstEntry + entry.Offset + ZipConstants.LOCHDR + extraLen;
}
}
/// <summary>
/// Checks, if the local header of the entry at index i matches the
/// central directory, and returns the offset to the data.
/// </summary>
/// <returns>
/// The start offset of the (compressed) data.
/// </returns>
/// <exception cref="System.IO.EndOfStreamException">
/// The stream ends prematurely
/// </exception>
/// <exception cref="ICSharpCode.SharpZipLib.Zip.ZipException">
/// The local header signature is invalid, the entry and central header file name lengths are different
/// or the local and entry compression methods dont match
/// </exception>
long CheckLocalHeader(ZipEntry entry)
{
return TestLocalHeader(entry, false, true);
}
// Refactor this, its done elsewhere as well
void ReadFully(Stream s, byte[] outBuf)
{
int off = 0;
int len = outBuf.Length;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -