// Shared.cs // ------------------------------------------------------------------ // // Copyright (c) 2006-2011 Dino Chiesa. // All rights reserved. // // This code module is part of DotNetZip, a zipfile class library. // // ------------------------------------------------------------------ // // This code is licensed under the Microsoft Public License. // See the file License.txt for the license details. // More info on: http://dotnetzip.codeplex.com // // ------------------------------------------------------------------ // // Last Saved: <2011-August-02 19:41:01> // // ------------------------------------------------------------------ // // This module defines some shared utility classes and methods. // // Created: Tue, 27 Mar 2007 15:30 // using System; using System.IO; using System.Security.Permissions; namespace Ionic.Zip { /// /// Collects general purpose utility methods. /// internal static class SharedUtilities { /// private null constructor //private SharedUtilities() { } // workitem 8423 public static Int64 GetFileLength(string fileName) { if (!File.Exists(fileName)) throw new System.IO.FileNotFoundException(fileName); long fileLength = 0L; FileShare fs = FileShare.ReadWrite; #if !NETCF // FileShare.Delete is not defined for the Compact Framework fs |= FileShare.Delete; #endif using (var s = File.Open(fileName, FileMode.Open, FileAccess.Read, fs)) { fileLength = s.Length; } return fileLength; } [System.Diagnostics.Conditional("NETCF")] public static void Workaround_Ladybug318918(Stream s) { // This is a workaround for this issue: // https://connect.microsoft.com/VisualStudio/feedback/details/318918 // It's required only on NETCF. s.Flush(); } #if LEGACY /// /// Round the given DateTime value to an even second value. /// /// /// /// /// Round up in the case of an odd second value. The rounding does not consider /// fractional seconds. /// /// /// This is useful because the Zip spec allows storage of time only to the nearest /// even second. So if you want to compare the time of an entry in the archive with /// it's actual time in the filesystem, you need to round the actual filesystem /// time, or use a 2-second threshold for the comparison. /// /// /// This is most nautrally an extension method for the DateTime class but this /// library is built for .NET 2.0, not for .NET 3.5; This means extension methods /// are a no-no. /// /// /// The DateTime value to round /// The ruonded DateTime value public static DateTime RoundToEvenSecond(DateTime source) { // round to nearest second: if ((source.Second % 2) == 1) source += new TimeSpan(0, 0, 1); DateTime dtRounded = new DateTime(source.Year, source.Month, source.Day, source.Hour, source.Minute, source.Second); //if (source.Millisecond >= 500) dtRounded = dtRounded.AddSeconds(1); return dtRounded; } #endif #if YOU_LIKE_REDUNDANT_CODE internal static string NormalizePath(string path) { // remove leading single dot slash if (path.StartsWith(".\\")) path = path.Substring(2); // remove intervening dot-slash path = path.Replace("\\.\\", "\\"); // remove double dot when preceded by a directory name var re = new System.Text.RegularExpressions.Regex(@"^(.*\\)?([^\\\.]+\\\.\.\\)(.+)$"); path = re.Replace(path, "$1$3"); return path; } #endif private static System.Text.RegularExpressions.Regex doubleDotRegex1 = new System.Text.RegularExpressions.Regex(@"^(.*/)?([^/\\.]+/\\.\\./)(.+)$"); private static string SimplifyFwdSlashPath(string path) { if (path.StartsWith("./")) path = path.Substring(2); path = path.Replace("/./", "/"); // Replace foo/anything/../bar with foo/bar path = doubleDotRegex1.Replace(path, "$1$3"); return path; } /// /// Utility routine for transforming path names from filesystem format (on Windows that means backslashes) to /// a format suitable for use within zipfiles. This means trimming the volume letter and colon (if any) And /// swapping backslashes for forward slashes. /// /// source path. /// transformed path public static string NormalizePathForUseInZipFile(string pathName) { // boundary case if (String.IsNullOrEmpty(pathName)) return pathName; // trim volume if necessary if ((pathName.Length >= 2) && ((pathName[1] == ':') && (pathName[2] == '\\'))) pathName = pathName.Substring(3); // swap slashes pathName = pathName.Replace('\\', '/'); // trim all leading slashes while (pathName.StartsWith("/")) pathName = pathName.Substring(1); return SimplifyFwdSlashPath(pathName); } static System.Text.Encoding ibm437 = System.Text.Encoding.GetEncoding("IBM437"); static System.Text.Encoding utf8 = System.Text.Encoding.GetEncoding("UTF-8"); internal static byte[] StringToByteArray(string value, System.Text.Encoding encoding) { byte[] a = encoding.GetBytes(value); return a; } internal static byte[] StringToByteArray(string value) { return StringToByteArray(value, ibm437); } //internal static byte[] Utf8StringToByteArray(string value) //{ // return StringToByteArray(value, utf8); //} //internal static string StringFromBuffer(byte[] buf, int maxlength) //{ // return StringFromBuffer(buf, maxlength, ibm437); //} internal static string Utf8StringFromBuffer(byte[] buf) { return StringFromBuffer(buf, utf8); } internal static string StringFromBuffer(byte[] buf, System.Text.Encoding encoding) { // this form of the GetString() method is required for .NET CF compatibility string s = encoding.GetString(buf, 0, buf.Length); return s; } internal static int ReadSignature(System.IO.Stream s) { int x = 0; try { x = _ReadFourBytes(s, "n/a"); } catch (BadReadException) { } return x; } internal static int ReadEntrySignature(System.IO.Stream s) { // handle the case of ill-formatted zip archives - includes a data descriptor // when none is expected. int x = 0; try { x = _ReadFourBytes(s, "n/a"); if (x == ZipConstants.ZipEntryDataDescriptorSignature) { // advance past data descriptor - 12 bytes if not zip64 s.Seek(12, SeekOrigin.Current); // workitem 10178 Workaround_Ladybug318918(s); x = _ReadFourBytes(s, "n/a"); if (x != ZipConstants.ZipEntrySignature) { // Maybe zip64 was in use for the prior entry. // Therefore, skip another 8 bytes. s.Seek(8, SeekOrigin.Current); // workitem 10178 Workaround_Ladybug318918(s); x = _ReadFourBytes(s, "n/a"); if (x != ZipConstants.ZipEntrySignature) { // seek back to the first spot s.Seek(-24, SeekOrigin.Current); // workitem 10178 Workaround_Ladybug318918(s); x = _ReadFourBytes(s, "n/a"); } } } } catch (BadReadException) { } return x; } internal static int ReadInt(System.IO.Stream s) { return _ReadFourBytes(s, "Could not read block - no data! (position 0x{0:X8})"); } private static int _ReadFourBytes(System.IO.Stream s, string message) { int n = 0; byte[] block = new byte[4]; #if NETCF // workitem 9181 // Reading here in NETCF sometimes reads "backwards". Seems to happen for // larger files. Not sure why. Maybe an error in caching. If the data is: // // 00100210: 9efa 0f00 7072 6f6a 6563 742e 6963 7750 ....project.icwP // 00100220: 4b05 0600 0000 0006 0006 0091 0100 008e K............... // 00100230: 0010 0000 00 ..... // // ...and the stream Position is 10021F, then a Read of 4 bytes is returning // 50776369, instead of 06054b50. This seems to happen the 2nd time Read() // is called from that Position.. // // submitted to connect.microsoft.com // https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=318918#tabs // for (int i = 0; i < block.Length; i++) { n+= s.Read(block, i, 1); } #else n = s.Read(block, 0, block.Length); #endif if (n != block.Length) throw new BadReadException(String.Format(message, s.Position)); int data = unchecked((((block[3] * 256 + block[2]) * 256) + block[1]) * 256 + block[0]); return data; } /// /// Finds a signature in the zip stream. This is useful for finding /// the end of a zip entry, for example, or the beginning of the next ZipEntry. /// /// /// /// /// Scans through 64k at a time. /// /// /// /// If the method fails to find the requested signature, the stream Position /// after completion of this method is unchanged. If the method succeeds in /// finding the requested signature, the stream position after completion is /// direct AFTER the signature found in the stream. /// /// /// /// The stream to search /// The 4-byte signature to find /// The number of bytes read internal static long FindSignature(System.IO.Stream stream, int SignatureToFind) { long startingPosition = stream.Position; int BATCH_SIZE = 65536; // 8192; byte[] targetBytes = new byte[4]; targetBytes[0] = (byte)(SignatureToFind >> 24); targetBytes[1] = (byte)((SignatureToFind & 0x00FF0000) >> 16); targetBytes[2] = (byte)((SignatureToFind & 0x0000FF00) >> 8); targetBytes[3] = (byte)(SignatureToFind & 0x000000FF); byte[] batch = new byte[BATCH_SIZE]; int n = 0; bool success = false; do { n = stream.Read(batch, 0, batch.Length); if (n != 0) { for (int i = 0; i < n; i++) { if (batch[i] == targetBytes[3]) { long curPosition = stream.Position; stream.Seek(i - n, System.IO.SeekOrigin.Current); // workitem 10178 Workaround_Ladybug318918(stream); // workitem 7711 int sig = ReadSignature(stream); success = (sig == SignatureToFind); if (!success) { stream.Seek(curPosition, System.IO.SeekOrigin.Begin); // workitem 10178 Workaround_Ladybug318918(stream); } else break; // out of for loop } } } else break; if (success) break; } while (true); if (!success) { stream.Seek(startingPosition, System.IO.SeekOrigin.Begin); // workitem 10178 Workaround_Ladybug318918(stream); return -1; // or throw? } // subtract 4 for the signature. long bytesRead = (stream.Position - startingPosition) - 4; return bytesRead; } // If I have a time in the .NET environment, and I want to use it for // SetWastWriteTime() etc, then I need to adjust it for Win32. internal static DateTime AdjustTime_Reverse(DateTime time) { if (time.Kind == DateTimeKind.Utc) return time; DateTime adjusted = time; if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime()) adjusted = time - new System.TimeSpan(1, 0, 0); else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime()) adjusted = time + new System.TimeSpan(1, 0, 0); return adjusted; } #if NECESSARY // If I read a time from a file with GetLastWriteTime() (etc), I need // to adjust it for display in the .NET environment. internal static DateTime AdjustTime_Forward(DateTime time) { if (time.Kind == DateTimeKind.Utc) return time; DateTime adjusted = time; if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime()) adjusted = time + new System.TimeSpan(1, 0, 0); else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime()) adjusted = time - new System.TimeSpan(1, 0, 0); return adjusted; } #endif internal static DateTime PackedToDateTime(Int32 packedDateTime) { // workitem 7074 & workitem 7170 if (packedDateTime == 0xFFFF || packedDateTime == 0) return new System.DateTime(1995, 1, 1, 0, 0, 0, 0); // return a fixed date when none is supplied. Int16 packedTime = unchecked((Int16)(packedDateTime & 0x0000ffff)); Int16 packedDate = unchecked((Int16)((packedDateTime & 0xffff0000) >> 16)); int year = 1980 + ((packedDate & 0xFE00) >> 9); int month = (packedDate & 0x01E0) >> 5; int day = packedDate & 0x001F; int hour = (packedTime & 0xF800) >> 11; int minute = (packedTime & 0x07E0) >> 5; //int second = packedTime & 0x001F; int second = (packedTime & 0x001F) * 2; // validation and error checking. // this is not foolproof but will catch most errors. if (second >= 60) { minute++; second = 0; } if (minute >= 60) { hour++; minute = 0; } if (hour >= 24) { day++; hour = 0; } DateTime d = System.DateTime.Now; bool success= false; try { d = new System.DateTime(year, month, day, hour, minute, second, 0); success= true; } catch (System.ArgumentOutOfRangeException) { if (year == 1980 && (month == 0 || day == 0)) { try { d = new System.DateTime(1980, 1, 1, hour, minute, second, 0); success= true; } catch (System.ArgumentOutOfRangeException) { try { d = new System.DateTime(1980, 1, 1, 0, 0, 0, 0); success= true; } catch (System.ArgumentOutOfRangeException) { } } } // workitem 8814 // my god, I can't believe how many different ways applications // can mess up a simple date format. else { try { while (year < 1980) year++; while (year > 2030) year--; while (month < 1) month++; while (month > 12) month--; while (day < 1) day++; while (day > 28) day--; while (minute < 0) minute++; while (minute > 59) minute--; while (second < 0) second++; while (second > 59) second--; d = new System.DateTime(year, month, day, hour, minute, second, 0); success= true; } catch (System.ArgumentOutOfRangeException) { } } } if (!success) { string msg = String.Format("y({0}) m({1}) d({2}) h({3}) m({4}) s({5})", year, month, day, hour, minute, second); throw new ZipException(String.Format("Bad date/time format in the zip file. ({0})", msg)); } // workitem 6191 //d = AdjustTime_Reverse(d); d = DateTime.SpecifyKind(d, DateTimeKind.Local); return d; } internal static Int32 DateTimeToPacked(DateTime time) { // The time is passed in here only for purposes of writing LastModified to the // zip archive. It should always be LocalTime, but we convert anyway. And, // since the time is being written out, it needs to be adjusted. time = time.ToLocalTime(); // workitem 7966 //time = AdjustTime_Forward(time); // see http://www.vsft.com/hal/dostime.htm for the format UInt16 packedDate = (UInt16)((time.Day & 0x0000001F) | ((time.Month << 5) & 0x000001E0) | (((time.Year - 1980) << 9) & 0x0000FE00)); UInt16 packedTime = (UInt16)((time.Second / 2 & 0x0000001F) | ((time.Minute << 5) & 0x000007E0) | ((time.Hour << 11) & 0x0000F800)); Int32 result = (Int32)(((UInt32)(packedDate << 16)) | packedTime); return result; } /// /// Create a pseudo-random filename, suitable for use as a temporary /// file, and open it. /// /// /// /// The System.IO.Path.GetRandomFileName() method is not available on /// the Compact Framework, so this library provides its own substitute /// on NETCF. /// /// /// This method produces a filename of the form /// DotNetZip-xxxxxxxx.tmp, where xxxxxxxx is replaced by randomly /// chosen characters, and creates that file. /// /// public static void CreateAndOpenUniqueTempFile(string dir, out Stream fs, out string filename) { // workitem 9763 // http://dotnet.org.za/markn/archive/2006/04/15/51594.aspx // try 3 times: for (int i = 0; i < 3; i++) { try { filename = Path.Combine(dir, InternalGetTempFileName()); fs = new FileStream(filename, FileMode.CreateNew); return; } catch (IOException) { if (i == 2) throw; } } throw new IOException(); } #if NETCF || SILVERLIGHT public static string InternalGetTempFileName() { return "DotNetZip-" + GenerateRandomStringImpl(8,0) + ".tmp"; } internal static string GenerateRandomStringImpl(int length, int delta) { bool WantMixedCase = (delta == 0); System.Random rnd = new System.Random(); string result = ""; char[] a = new char[length]; for (int i = 0; i < length; i++) { // delta == 65 means uppercase // delta == 97 means lowercase if (WantMixedCase) delta = (rnd.Next(2) == 0) ? 65 : 97; a[i] = (char)(rnd.Next(26) + delta); } result = new System.String(a); return result; } #else public static string InternalGetTempFileName() { return "DotNetZip-" + Path.GetRandomFileName().Substring(0, 8) + ".tmp"; } #endif /// /// Workitem 7889: handle ERROR_LOCK_VIOLATION during read /// /// /// This could be gracefully handled with an extension attribute, but /// This assembly is built for .NET 2.0, so I cannot use them. /// internal static int ReadWithRetry(System.IO.Stream s, byte[] buffer, int offset, int count, string FileName) { int n = 0; bool done = false; #if !NETCF && !SILVERLIGHT int retries = 0; #endif do { try { n = s.Read(buffer, offset, count); done = true; } #if NETCF || SILVERLIGHT catch (System.IO.IOException) { throw; } #else catch (System.IO.IOException ioexc1) { // Check if we can call GetHRForException, // which makes unmanaged code calls. var p = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode); if (p.IsUnrestricted()) { uint hresult = _HRForException(ioexc1); if (hresult != 0x80070021) // ERROR_LOCK_VIOLATION throw new System.IO.IOException(String.Format("Cannot read file {0}", FileName), ioexc1); retries++; if (retries > 10) throw new System.IO.IOException(String.Format("Cannot read file {0}, at offset 0x{1:X8} after 10 retries", FileName, offset), ioexc1); // max time waited on last retry = 250 + 10*550 = 5.75s // aggregate time waited after 10 retries: 250 + 55*550 = 30.5s System.Threading.Thread.Sleep(250 + retries * 550); } else { // The permission.Demand() failed. Therefore, we cannot call // GetHRForException, and cannot do the subtle handling of // ERROR_LOCK_VIOLATION. Just bail. throw; } } #endif } while (!done); return n; } #if !NETCF // workitem 8009 // // This method must remain separate. // // Marshal.GetHRForException() is needed to do special exception handling for // the read. But, that method requires UnmanagedCode permissions, and is marked // with LinkDemand for UnmanagedCode. In an ASP.NET medium trust environment, // where UnmanagedCode is restricted, will generate a SecurityException at the // time of JIT of the method that calls a method that is marked with LinkDemand // for UnmanagedCode. The SecurityException, if it is restricted, will occur // when this method is JITed. // // The Marshal.GetHRForException() is factored out of ReadWithRetry in order to // avoid the SecurityException at JIT compile time. Because _HRForException is // called only when the UnmanagedCode is allowed. This means .NET never // JIT-compiles this method when UnmanagedCode is disallowed, and thus never // generates the JIT-compile time exception. // #endif private static uint _HRForException(System.Exception ex1) { return unchecked((uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex1)); } } /// /// A decorator stream. It wraps another stream, and performs bookkeeping /// to keep track of the stream Position. /// /// /// /// In some cases, it is not possible to get the Position of a stream, let's /// say, on a write-only output stream like ASP.NET's /// Response.OutputStream, or on a different write-only stream /// provided as the destination for the zip by the application. In this /// case, programmers can use this counting stream to count the bytes read /// or written. /// /// /// Consider the scenario of an application that saves a self-extracting /// archive (SFX), that uses a custom SFX stub. /// /// /// Saving to a filesystem file, the application would open the /// filesystem file (getting a FileStream), save the custom sfx stub /// into it, and then call ZipFile.Save(), specifying the same /// FileStream. ZipFile.Save() does the right thing for the zipentry /// offsets, by inquiring the Position of the FileStream before writing /// any data, and then adding that initial offset into any ZipEntry /// offsets in the zip directory. Everything works fine. /// /// /// Now suppose the application is an ASPNET application and it saves /// directly to Response.OutputStream. It's not possible for DotNetZip to /// inquire the Position, so the offsets for the SFX will be wrong. /// /// /// The workaround is for the application to use this class to wrap /// HttpResponse.OutputStream, then write the SFX stub and the ZipFile /// into that wrapper stream. Because ZipFile.Save() can inquire the /// Position, it will then do the right thing with the offsets. /// /// public class CountingStream : System.IO.Stream { // workitem 12374: this class is now public private System.IO.Stream _s; private Int64 _bytesWritten; private Int64 _bytesRead; private Int64 _initialOffset; /// /// The constructor. /// /// The underlying stream public CountingStream(System.IO.Stream stream) : base() { _s = stream; try { _initialOffset = _s.Position; } catch { _initialOffset = 0L; } } /// /// Gets the wrapped stream. /// public Stream WrappedStream { get { return _s; } } /// /// The count of bytes written out to the stream. /// public Int64 BytesWritten { get { return _bytesWritten; } } /// /// the count of bytes that have been read from the stream. /// public Int64 BytesRead { get { return _bytesRead; } } /// /// Adjust the byte count on the stream. /// /// /// /// the number of bytes to subtract from the count. /// /// /// /// /// Subtract delta from the count of bytes written to the stream. /// This is necessary when seeking back, and writing additional data, /// as happens in some cases when saving Zip files. /// /// public void Adjust(Int64 delta) { _bytesWritten -= delta; if (_bytesWritten < 0) throw new InvalidOperationException(); if (_s as CountingStream != null) ((CountingStream)_s).Adjust(delta); } /// /// The read method. /// /// The buffer to hold the data read from the stream. /// the offset within the buffer to copy the first byte read. /// the number of bytes to read. /// the number of bytes read, after decryption and decompression. public override int Read(byte[] buffer, int offset, int count) { int n = _s.Read(buffer, offset, count); _bytesRead += n; return n; } /// /// Write data into the stream. /// /// The buffer holding data to write to the stream. /// the offset within that data array to find the first byte to write. /// the number of bytes to write. public override void Write(byte[] buffer, int offset, int count) { if (count == 0) return; _s.Write(buffer, offset, count); _bytesWritten += count; } /// /// Whether the stream can be read. /// public override bool CanRead { get { return _s.CanRead; } } /// /// Whether it is possible to call Seek() on the stream. /// public override bool CanSeek { get { return _s.CanSeek; } } /// /// Whether it is possible to call Write() on the stream. /// public override bool CanWrite { get { return _s.CanWrite; } } /// /// Flushes the underlying stream. /// public override void Flush() { _s.Flush(); } /// /// The length of the underlying stream. /// public override long Length { get { return _s.Length; } // bytesWritten?? } /// /// Returns the sum of number of bytes written, plus the initial /// offset before writing. /// public long ComputedPosition { get { return _initialOffset + _bytesWritten; } } /// /// The Position of the stream. /// public override long Position { get { return _s.Position; } set { _s.Seek(value, System.IO.SeekOrigin.Begin); // workitem 10178 Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_s); } } /// /// Seek in the stream. /// /// the offset point to seek to /// the reference point from which to seek /// The new position public override long Seek(long offset, System.IO.SeekOrigin origin) { return _s.Seek(offset, origin); } /// /// Set the length of the underlying stream. Be careful with this! /// /// /// the length to set on the underlying stream. public override void SetLength(long value) { _s.SetLength(value); } } }