kopia lustrzana https://github.com/dl2alf/AirScout
902 wiersze
33 KiB
C#
902 wiersze
33 KiB
C#
// 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
|
|
{
|
|
/// <summary>
|
|
/// Collects general purpose utility methods.
|
|
/// </summary>
|
|
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
|
|
/// <summary>
|
|
/// Round the given DateTime value to an even second value.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Round up in the case of an odd second value. The rounding does not consider
|
|
/// fractional seconds.
|
|
/// </para>
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <param name="source">The DateTime value to round</param>
|
|
/// <returns>The ruonded DateTime value</returns>
|
|
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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="pathName">source path.</param>
|
|
/// <returns>transformed path</returns>
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Scans through 64k at a time.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// </remarks>
|
|
///
|
|
/// <param name="stream">The stream to search</param>
|
|
/// <param name="SignatureToFind">The 4-byte signature to find</param>
|
|
/// <returns>The number of bytes read</returns>
|
|
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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Create a pseudo-random filename, suitable for use as a temporary
|
|
/// file, and open it.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The System.IO.Path.GetRandomFileName() method is not available on
|
|
/// the Compact Framework, so this library provides its own substitute
|
|
/// on NETCF.
|
|
/// </para>
|
|
/// <para>
|
|
/// This method produces a filename of the form
|
|
/// DotNetZip-xxxxxxxx.tmp, where xxxxxxxx is replaced by randomly
|
|
/// chosen characters, and creates that file.
|
|
/// </para>
|
|
/// </remarks>
|
|
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
|
|
|
|
|
|
/// <summary>
|
|
/// Workitem 7889: handle ERROR_LOCK_VIOLATION during read
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This could be gracefully handled with an extension attribute, but
|
|
/// This assembly is built for .NET 2.0, so I cannot use them.
|
|
/// </remarks>
|
|
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));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// A decorator stream. It wraps another stream, and performs bookkeeping
|
|
/// to keep track of the stream Position.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// 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
|
|
/// <c>Response.OutputStream</c>, 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.
|
|
/// </para>
|
|
/// <para>
|
|
/// Consider the scenario of an application that saves a self-extracting
|
|
/// archive (SFX), that uses a custom SFX stub.
|
|
/// </para>
|
|
/// <para>
|
|
/// Saving to a filesystem file, the application would open the
|
|
/// filesystem file (getting a <c>FileStream</c>), save the custom sfx stub
|
|
/// into it, and then call <c>ZipFile.Save()</c>, specifying the same
|
|
/// FileStream. <c>ZipFile.Save()</c> does the right thing for the zipentry
|
|
/// offsets, by inquiring the Position of the <c>FileStream</c> before writing
|
|
/// any data, and then adding that initial offset into any ZipEntry
|
|
/// offsets in the zip directory. Everything works fine.
|
|
/// </para>
|
|
/// <para>
|
|
/// Now suppose the application is an ASPNET application and it saves
|
|
/// directly to <c>Response.OutputStream</c>. It's not possible for DotNetZip to
|
|
/// inquire the <c>Position</c>, so the offsets for the SFX will be wrong.
|
|
/// </para>
|
|
/// <para>
|
|
/// The workaround is for the application to use this class to wrap
|
|
/// <c>HttpResponse.OutputStream</c>, then write the SFX stub and the ZipFile
|
|
/// into that wrapper stream. Because <c>ZipFile.Save()</c> can inquire the
|
|
/// <c>Position</c>, it will then do the right thing with the offsets.
|
|
/// </para>
|
|
/// </remarks>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// The constructor.
|
|
/// </summary>
|
|
/// <param name="stream">The underlying stream</param>
|
|
public CountingStream(System.IO.Stream stream)
|
|
: base()
|
|
{
|
|
_s = stream;
|
|
try
|
|
{
|
|
_initialOffset = _s.Position;
|
|
}
|
|
catch
|
|
{
|
|
_initialOffset = 0L;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the wrapped stream.
|
|
/// </summary>
|
|
public Stream WrappedStream
|
|
{
|
|
get
|
|
{
|
|
return _s;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The count of bytes written out to the stream.
|
|
/// </summary>
|
|
public Int64 BytesWritten
|
|
{
|
|
get { return _bytesWritten; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// the count of bytes that have been read from the stream.
|
|
/// </summary>
|
|
public Int64 BytesRead
|
|
{
|
|
get { return _bytesRead; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adjust the byte count on the stream.
|
|
/// </summary>
|
|
///
|
|
/// <param name='delta'>
|
|
/// the number of bytes to subtract from the count.
|
|
/// </param>
|
|
///
|
|
/// <remarks>
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// </remarks>
|
|
public void Adjust(Int64 delta)
|
|
{
|
|
_bytesWritten -= delta;
|
|
if (_bytesWritten < 0)
|
|
throw new InvalidOperationException();
|
|
if (_s as CountingStream != null)
|
|
((CountingStream)_s).Adjust(delta);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The read method.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to hold the data read from the stream.</param>
|
|
/// <param name="offset">the offset within the buffer to copy the first byte read.</param>
|
|
/// <param name="count">the number of bytes to read.</param>
|
|
/// <returns>the number of bytes read, after decryption and decompression.</returns>
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
int n = _s.Read(buffer, offset, count);
|
|
_bytesRead += n;
|
|
return n;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write data into the stream.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
|
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
|
/// <param name="count">the number of bytes to write.</param>
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
if (count == 0) return;
|
|
_s.Write(buffer, offset, count);
|
|
_bytesWritten += count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the stream can be read.
|
|
/// </summary>
|
|
public override bool CanRead
|
|
{
|
|
get { return _s.CanRead; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether it is possible to call Seek() on the stream.
|
|
/// </summary>
|
|
public override bool CanSeek
|
|
{
|
|
get { return _s.CanSeek; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether it is possible to call Write() on the stream.
|
|
/// </summary>
|
|
public override bool CanWrite
|
|
{
|
|
get { return _s.CanWrite; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flushes the underlying stream.
|
|
/// </summary>
|
|
public override void Flush()
|
|
{
|
|
_s.Flush();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The length of the underlying stream.
|
|
/// </summary>
|
|
public override long Length
|
|
{
|
|
get { return _s.Length; } // bytesWritten??
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the sum of number of bytes written, plus the initial
|
|
/// offset before writing.
|
|
/// </summary>
|
|
public long ComputedPosition
|
|
{
|
|
get { return _initialOffset + _bytesWritten; }
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// The Position of the stream.
|
|
/// </summary>
|
|
public override long Position
|
|
{
|
|
get { return _s.Position; }
|
|
set
|
|
{
|
|
_s.Seek(value, System.IO.SeekOrigin.Begin);
|
|
// workitem 10178
|
|
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_s);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seek in the stream.
|
|
/// </summary>
|
|
/// <param name="offset">the offset point to seek to</param>
|
|
/// <param name="origin">the reference point from which to seek</param>
|
|
/// <returns>The new position</returns>
|
|
public override long Seek(long offset, System.IO.SeekOrigin origin)
|
|
{
|
|
return _s.Seek(offset, origin);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the length of the underlying stream. Be careful with this!
|
|
/// </summary>
|
|
///
|
|
/// <param name='value'>the length to set on the underlying stream.</param>
|
|
public override void SetLength(long value)
|
|
{
|
|
_s.SetLength(value);
|
|
}
|
|
}
|
|
|
|
|
|
}
|