| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Runtime.InteropServices; |
| | | 6 | | using System.Runtime.InteropServices.Marshalling; |
| | | 7 | | using System.Security; |
| | | 8 | | |
| | | 9 | | namespace System.IO.Compression |
| | | 10 | | { |
| | | 11 | | /// <summary> |
| | | 12 | | /// This class provides declaration for constants and PInvokes as well as some basic tools for exposing the |
| | | 13 | | /// native System.IO.Compression.Native.dll (effectively, ZLib) library to managed code. |
| | | 14 | | /// |
| | | 15 | | /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para> |
| | | 16 | | /// </summary> |
| | | 17 | | /// <seealso href="https://www.zlib.net/manual.html">ZLib manual</seealso> |
| | | 18 | | internal static partial class ZLibNative |
| | | 19 | | { |
| | | 20 | | // This is the NULL pointer for using with ZLib pointers; |
| | | 21 | | // we prefer it to IntPtr.Zero to mimic the definition of Z_NULL in zlib.h: |
| | 0 | 22 | | internal static readonly IntPtr ZNullPtr = IntPtr.Zero; |
| | | 23 | | |
| | | 24 | | public enum FlushCode : int |
| | | 25 | | { |
| | | 26 | | NoFlush = 0, |
| | | 27 | | SyncFlush = 2, |
| | | 28 | | Finish = 4, |
| | | 29 | | Block = 5 |
| | | 30 | | } |
| | | 31 | | |
| | | 32 | | public enum ErrorCode : int |
| | | 33 | | { |
| | | 34 | | Ok = 0, |
| | | 35 | | StreamEnd = 1, |
| | | 36 | | StreamError = -2, |
| | | 37 | | DataError = -3, |
| | | 38 | | MemError = -4, |
| | | 39 | | BufError = -5, |
| | | 40 | | VersionError = -6 |
| | | 41 | | } |
| | | 42 | | |
| | | 43 | | /// <summary> |
| | | 44 | | /// <para><strong>From the ZLib manual:</strong><br /> |
| | | 45 | | /// <see cref="CompressionStrategy" /> is used to tune the compression algorithm.<br /> |
| | | 46 | | /// Use the value <see cref="DefaultStrategy" /> for normal data, <see cref="Filtered" /> for data produced by a |
| | | 47 | | /// <see cref="HuffmanOnly" /> to force Huffman encoding only (no string match), or <see cref="RunLengthEncoding |
| | | 48 | | /// (run-length encoding). Filtered data consists mostly of small values with a somewhat random distribution. In |
| | | 49 | | /// compression algorithm is tuned to compress them better. The effect of <see cref="Filtered" /> is to force mo |
| | | 50 | | /// less string matching; it is somewhat intermediate between <see cref="DefaultStrategy" /> and <see cref="Huff |
| | | 51 | | /// <see cref="RunLengthEncoding" /> is designed to be almost as fast as <see cref="HuffmanOnly" />, but give be |
| | | 52 | | /// The strategy parameter only affects the compression ratio but not the correctness of the compressed output e |
| | | 53 | | /// appropriately. <see cref="Fixed" /> prevents the use of dynamic Huffman codes, allowing for a simpler decode |
| | | 54 | | /// |
| | | 55 | | /// <para><strong>For .NET Framework use:</strong><br /> |
| | | 56 | | /// We have investigated compression scenarios for a bunch of different frequently occurring compression data an |
| | | 57 | | /// cases we investigated so far, <see cref="DefaultStrategy" /> provided best results</para> |
| | | 58 | | /// |
| | | 59 | | /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para> |
| | | 60 | | /// </summary> |
| | | 61 | | public enum CompressionStrategy : int |
| | | 62 | | { |
| | | 63 | | DefaultStrategy = 0, |
| | | 64 | | Filtered = 1, |
| | | 65 | | HuffmanOnly = 2, |
| | | 66 | | RunLengthEncoding = 3, |
| | | 67 | | Fixed = 4 |
| | | 68 | | } |
| | | 69 | | |
| | | 70 | | /// <summary> |
| | | 71 | | /// In version 2.2.1, zlib-ng provides only the <see cref="Deflated" /> <see cref="CompressionMethod" />. |
| | | 72 | | /// </summary> |
| | | 73 | | public enum CompressionMethod : int |
| | | 74 | | { |
| | | 75 | | Deflated = 8 |
| | | 76 | | } |
| | | 77 | | |
| | | 78 | | /// <summary> |
| | | 79 | | /// <para><strong>From the ZLib manual:</strong><br /> |
| | | 80 | | /// ZLib's <c>windowBits</c> parameter is the base two logarithm of the window size (the size of the history buf |
| | | 81 | | /// It should be in the range 8..15 for this version of the library. Larger values of this parameter result in b |
| | | 82 | | /// at the expense of memory usage. The default value is 15 if <c>deflateInit</c> is used instead.</para> |
| | | 83 | | /// |
| | | 84 | | /// <para><strong>Note</strong>: <c>windowBits</c> can also be -8..-15 for raw deflate. In this case, -windowBit |
| | | 85 | | /// <c>Deflate</c> will then generate raw deflate data with no ZLib header or trailer, and will not compute an a |
| | | 86 | | /// |
| | | 87 | | /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para> |
| | | 88 | | /// </summary> |
| | | 89 | | public const int Deflate_DefaultWindowBits = -15; // Legal values are 8..15 and -8..-15. 15 is the window size, |
| | | 90 | | // negative val causes deflate to produce raw deflate data (no |
| | | 91 | | |
| | | 92 | | /// <summary> |
| | | 93 | | /// <para><strong>From the ZLib manual:</strong><br /> |
| | | 94 | | /// ZLib's <c>windowBits</c> parameter is the base two logarithm of the window size (the size of the history buf |
| | | 95 | | /// It should be in the range 8..15 for this version of the library. Larger values of this parameter result in b |
| | | 96 | | /// at the expense of memory usage. The default value is 15 if <c>deflateInit</c> is used instead.</para> |
| | | 97 | | /// </summary> |
| | | 98 | | public const int ZLib_DefaultWindowBits = 15; |
| | | 99 | | |
| | | 100 | | /// <summary> |
| | | 101 | | /// <para>ZLib's <c>windowBits</c> parameter is the base two logarithm of the window size (the size of the histo |
| | | 102 | | /// For GZip header encoding, <c>windowBits</c> should be equal to a value between 8..15 (to specify Window Size |
| | | 103 | | /// 16. The range of values for GZip encoding is therefore 24..31.</para> |
| | | 104 | | /// <para><strong>Note</strong>:<br /> |
| | | 105 | | /// The GZip header will have no file name, no extra data, no comment, no modification time (set to zero), no he |
| | | 106 | | /// the operating system will be set based on the OS that the ZLib library was compiled to. <c>ZStream.adler</c> |
| | | 107 | | /// is a crc32 instead of an adler32.</para> |
| | | 108 | | /// </summary> |
| | | 109 | | public const int GZip_DefaultWindowBits = 31; |
| | | 110 | | |
| | | 111 | | /// <summary> |
| | | 112 | | /// The minimum value for the base-2 logarithm of the history buffer (window) size. |
| | | 113 | | /// A value of 8 corresponds to a 256-byte window. |
| | | 114 | | /// </summary> |
| | | 115 | | public const int MinWindowLog = 8; |
| | | 116 | | |
| | | 117 | | /// <summary> |
| | | 118 | | /// The maximum value for the base-2 logarithm of the history buffer (window) size. |
| | | 119 | | /// A value of 15 corresponds to a 32KB window, which provides the best compression ratio. |
| | | 120 | | /// </summary> |
| | | 121 | | public const int MaxWindowLog = 15; |
| | | 122 | | |
| | | 123 | | /// <summary> |
| | | 124 | | /// The default value for the base-2 logarithm of the history buffer (window) size. |
| | | 125 | | /// Defaults to <see cref="MaxWindowLog"/> (15) for optimal compression. |
| | | 126 | | /// </summary> |
| | | 127 | | public const int DefaultWindowLog = MaxWindowLog; |
| | | 128 | | |
| | | 129 | | /// <summary> |
| | | 130 | | /// The minimum compression quality level. A value of 0 means no compression (store only). |
| | | 131 | | /// </summary> |
| | | 132 | | public const int MinQuality = 0; |
| | | 133 | | |
| | | 134 | | /// <summary> |
| | | 135 | | /// The maximum compression quality level. A value of 9 provides the best compression ratio |
| | | 136 | | /// but is the slowest. |
| | | 137 | | /// </summary> |
| | | 138 | | public const int MaxQuality = 9; |
| | | 139 | | |
| | | 140 | | /// <summary> |
| | | 141 | | /// The default compression quality level. A value of 6 provides a good balance between |
| | | 142 | | /// compression ratio and speed. |
| | | 143 | | /// </summary> |
| | | 144 | | public const int DefaultQuality = 6; |
| | | 145 | | |
| | | 146 | | /// <summary> |
| | | 147 | | /// <para><strong>From the ZLib manual:</strong><br /> |
| | | 148 | | /// The <c>memLevel</c> parameter specifies how much memory should be allocated for the internal compression sta |
| | | 149 | | /// <c>memLevel</c> = 1 uses minimum memory but is slow and reduces compression ratio; <c>memLevel</c> = 9 uses |
| | | 150 | | /// memory for optimal speed. The default value is 8.</para> |
| | | 151 | | /// |
| | | 152 | | /// <para>See also: How to choose a compression level (in comments to <see cref="CompressionLevel" />.)</para> |
| | | 153 | | /// </summary> |
| | | 154 | | public const int Deflate_DefaultMemLevel = 8; // Memory usage by deflate. Legal range: [1..9]. 8 is ZLib def |
| | | 155 | | // More is faster and better compression with more memory usag |
| | | 156 | | public const int Deflate_NoCompressionMemLevel = 7; |
| | | 157 | | |
| | | 158 | | public const byte GZip_Header_ID1 = 31; |
| | | 159 | | public const byte GZip_Header_ID2 = 139; |
| | | 160 | | |
| | | 161 | | public sealed class ZLibStreamHandle : SafeHandle |
| | | 162 | | { |
| | | 163 | | public enum State |
| | | 164 | | { |
| | | 165 | | NotInitialized, |
| | | 166 | | InitializedForDeflate, |
| | | 167 | | InitializedForInflate, |
| | | 168 | | Disposed |
| | | 169 | | } |
| | | 170 | | |
| | | 171 | | private ZStream _zStream; |
| | | 172 | | |
| | | 173 | | private volatile State _initializationState; |
| | | 174 | | |
| | | 175 | | public ZLibStreamHandle() |
| | 0 | 176 | | : base(new IntPtr(-1), true) |
| | 0 | 177 | | { |
| | 0 | 178 | | _initializationState = State.NotInitialized; |
| | 0 | 179 | | SetHandle(IntPtr.Zero); |
| | 0 | 180 | | } |
| | | 181 | | |
| | | 182 | | public static ZLibStreamHandle CreateForDeflate(CompressionLevel level, int windowBits, |
| | | 183 | | int memLevel, CompressionStrategy strategy) |
| | 0 | 184 | | { |
| | 0 | 185 | | ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle(); |
| | | 186 | | |
| | | 187 | | try |
| | 0 | 188 | | { |
| | 0 | 189 | | zLibStreamHandle.DeflateInit2_(level, windowBits, memLevel, strategy); |
| | 0 | 190 | | } |
| | 0 | 191 | | catch (Exception) |
| | 0 | 192 | | { |
| | 0 | 193 | | zLibStreamHandle.Dispose(); |
| | 0 | 194 | | throw; |
| | | 195 | | } |
| | | 196 | | |
| | 0 | 197 | | return zLibStreamHandle; |
| | 0 | 198 | | } |
| | | 199 | | |
| | | 200 | | public static ZLibStreamHandle CreateForInflate(int windowBits) |
| | 0 | 201 | | { |
| | 0 | 202 | | ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle(); |
| | | 203 | | |
| | | 204 | | try |
| | 0 | 205 | | { |
| | 0 | 206 | | zLibStreamHandle.InflateInit2_(windowBits); |
| | 0 | 207 | | } |
| | 0 | 208 | | catch (Exception) |
| | 0 | 209 | | { |
| | 0 | 210 | | zLibStreamHandle.Dispose(); |
| | 0 | 211 | | throw; |
| | | 212 | | } |
| | | 213 | | |
| | 0 | 214 | | return zLibStreamHandle; |
| | 0 | 215 | | } |
| | | 216 | | |
| | | 217 | | public override bool IsInvalid |
| | | 218 | | { |
| | 0 | 219 | | get { return handle == new IntPtr(-1); } |
| | | 220 | | } |
| | | 221 | | |
| | | 222 | | public State InitializationState |
| | | 223 | | { |
| | 0 | 224 | | get { return _initializationState; } |
| | | 225 | | } |
| | | 226 | | |
| | | 227 | | protected override bool ReleaseHandle() => |
| | 0 | 228 | | InitializationState switch |
| | 0 | 229 | | { |
| | 0 | 230 | | State.NotInitialized => true, |
| | 0 | 231 | | State.InitializedForDeflate => (DeflateEnd() == ErrorCode.Ok), |
| | 0 | 232 | | State.InitializedForInflate => (InflateEnd() == ErrorCode.Ok), |
| | 0 | 233 | | State.Disposed => true, |
| | 0 | 234 | | _ => false, // This should never happen. Did we forget one of the State enum values in the switch? |
| | 0 | 235 | | }; |
| | | 236 | | |
| | | 237 | | public IntPtr NextIn |
| | | 238 | | { |
| | 0 | 239 | | get { return _zStream.nextIn; } |
| | 0 | 240 | | set { _zStream.nextIn = value; } |
| | | 241 | | } |
| | | 242 | | |
| | | 243 | | public uint AvailIn |
| | | 244 | | { |
| | 0 | 245 | | get { return _zStream.availIn; } |
| | 0 | 246 | | set { _zStream.availIn = value; } |
| | | 247 | | } |
| | | 248 | | |
| | | 249 | | public IntPtr NextOut |
| | | 250 | | { |
| | | 251 | | get { return _zStream.nextOut; } |
| | 0 | 252 | | set { _zStream.nextOut = value; } |
| | | 253 | | } |
| | | 254 | | |
| | | 255 | | public uint AvailOut |
| | | 256 | | { |
| | 0 | 257 | | get { return _zStream.availOut; } |
| | 0 | 258 | | set { _zStream.availOut = value; } |
| | | 259 | | } |
| | | 260 | | |
| | | 261 | | private void EnsureState(State requiredState) |
| | 0 | 262 | | { |
| | 0 | 263 | | if (InitializationState != requiredState) |
| | 0 | 264 | | throw new InvalidOperationException("InitializationState != " + requiredState.ToString()); |
| | 0 | 265 | | } |
| | | 266 | | |
| | | 267 | | private void EnsureNativeHandleInitialized(ErrorCode zlibErrorCode, string zlibErrorContext) |
| | 0 | 268 | | { |
| | 0 | 269 | | Debug.Assert(zlibErrorContext is "deflateInit2_" or "inflateInit2_"); |
| | | 270 | | |
| | 0 | 271 | | if (zlibErrorCode is not ErrorCode.Ok) |
| | 0 | 272 | | { |
| | 0 | 273 | | string zlibErrorMessage = GetErrorMessage(); |
| | 0 | 274 | | string exceptionMessage = zlibErrorCode switch |
| | 0 | 275 | | { |
| | 0 | 276 | | // Not enough memory |
| | 0 | 277 | | ErrorCode.MemError => SR.ZLibErrorNotEnoughMemory, |
| | 0 | 278 | | |
| | 0 | 279 | | // zlib library is incompatible with the version assumed |
| | 0 | 280 | | ErrorCode.VersionError => SR.ZLibErrorVersionMismatch, |
| | 0 | 281 | | |
| | 0 | 282 | | // Parameters are invalid |
| | 0 | 283 | | ErrorCode.StreamError => SR.ZLibErrorIncorrectInitParameters, |
| | 0 | 284 | | |
| | 0 | 285 | | _ => SR.Format(SR.ZLibErrorUnexpected, (int)zlibErrorCode) |
| | 0 | 286 | | }; |
| | | 287 | | |
| | 0 | 288 | | throw new ZLibException(exceptionMessage, zlibErrorContext, (int)zlibErrorCode, zlibErrorMessage); |
| | | 289 | | } |
| | 0 | 290 | | } |
| | | 291 | | |
| | | 292 | | private unsafe void DeflateInit2_(CompressionLevel level, int windowBits, int memLevel, CompressionStrategy |
| | 0 | 293 | | { |
| | 0 | 294 | | Debug.Assert(InitializationState == State.NotInitialized); |
| | | 295 | | |
| | | 296 | | ErrorCode errC; |
| | | 297 | | |
| | | 298 | | try |
| | 0 | 299 | | { |
| | 0 | 300 | | fixed (ZStream* stream = &_zStream) |
| | 0 | 301 | | { |
| | 0 | 302 | | errC = Interop.ZLib.DeflateInit2_(stream, level, CompressionMethod.Deflated, windowBits, memLeve |
| | 0 | 303 | | } |
| | 0 | 304 | | } |
| | 0 | 305 | | catch (Exception e) // Could not load the ZLib dll |
| | 0 | 306 | | { |
| | 0 | 307 | | throw new ZLibException(SR.ZLibErrorDLLLoadError, e); |
| | | 308 | | } |
| | | 309 | | |
| | 0 | 310 | | EnsureNativeHandleInitialized(errC, "deflateInit2_"); |
| | 0 | 311 | | _initializationState = State.InitializedForDeflate; |
| | 0 | 312 | | } |
| | | 313 | | |
| | | 314 | | public unsafe ErrorCode Deflate(FlushCode flush) |
| | 0 | 315 | | { |
| | 0 | 316 | | bool refAdded = false; |
| | | 317 | | try |
| | 0 | 318 | | { |
| | 0 | 319 | | DangerousAddRef(ref refAdded); |
| | 0 | 320 | | EnsureState(State.InitializedForDeflate); |
| | | 321 | | |
| | 0 | 322 | | fixed (ZStream* stream = &_zStream) |
| | 0 | 323 | | { |
| | 0 | 324 | | return Interop.ZLib.Deflate(stream, flush); |
| | | 325 | | } |
| | | 326 | | } |
| | | 327 | | finally |
| | 0 | 328 | | { |
| | 0 | 329 | | if (refAdded) |
| | 0 | 330 | | { |
| | 0 | 331 | | DangerousRelease(); |
| | 0 | 332 | | } |
| | 0 | 333 | | } |
| | 0 | 334 | | } |
| | | 335 | | |
| | | 336 | | private unsafe ErrorCode DeflateEnd() |
| | 0 | 337 | | { |
| | 0 | 338 | | EnsureState(State.InitializedForDeflate); |
| | | 339 | | |
| | 0 | 340 | | fixed (ZStream* stream = &_zStream) |
| | 0 | 341 | | { |
| | 0 | 342 | | ErrorCode errC = Interop.ZLib.DeflateEnd(stream); |
| | 0 | 343 | | _initializationState = State.Disposed; |
| | | 344 | | |
| | 0 | 345 | | return errC; |
| | | 346 | | } |
| | 0 | 347 | | } |
| | | 348 | | |
| | | 349 | | private unsafe void InflateInit2_(int windowBits) |
| | 0 | 350 | | { |
| | 0 | 351 | | Debug.Assert(InitializationState == State.NotInitialized); |
| | | 352 | | |
| | | 353 | | ErrorCode errC; |
| | | 354 | | |
| | | 355 | | try |
| | 0 | 356 | | { |
| | 0 | 357 | | fixed (ZStream* stream = &_zStream) |
| | 0 | 358 | | { |
| | 0 | 359 | | errC = Interop.ZLib.InflateInit2_(stream, windowBits); |
| | 0 | 360 | | } |
| | 0 | 361 | | } |
| | 0 | 362 | | catch (Exception e) // Could not load the ZLib dll |
| | 0 | 363 | | { |
| | 0 | 364 | | throw new ZLibException(SR.ZLibErrorDLLLoadError, e); |
| | | 365 | | } |
| | | 366 | | |
| | 0 | 367 | | EnsureNativeHandleInitialized(errC, "inflateInit2_"); |
| | 0 | 368 | | _initializationState = State.InitializedForInflate; |
| | 0 | 369 | | } |
| | | 370 | | |
| | | 371 | | public unsafe ErrorCode InflateReset2_(int windowBits) |
| | 0 | 372 | | { |
| | 0 | 373 | | bool refAdded = false; |
| | | 374 | | try |
| | 0 | 375 | | { |
| | 0 | 376 | | DangerousAddRef(ref refAdded); |
| | 0 | 377 | | EnsureState(State.InitializedForInflate); |
| | | 378 | | |
| | 0 | 379 | | fixed (ZStream* stream = &_zStream) |
| | 0 | 380 | | { |
| | 0 | 381 | | return Interop.ZLib.InflateReset2_(stream, windowBits); |
| | | 382 | | } |
| | | 383 | | } |
| | | 384 | | finally |
| | 0 | 385 | | { |
| | 0 | 386 | | if (refAdded) |
| | 0 | 387 | | { |
| | 0 | 388 | | DangerousRelease(); |
| | 0 | 389 | | } |
| | 0 | 390 | | } |
| | 0 | 391 | | } |
| | | 392 | | |
| | | 393 | | public unsafe ErrorCode Inflate(FlushCode flush) |
| | 0 | 394 | | { |
| | 0 | 395 | | bool refAdded = false; |
| | | 396 | | try |
| | 0 | 397 | | { |
| | 0 | 398 | | DangerousAddRef(ref refAdded); |
| | 0 | 399 | | EnsureState(State.InitializedForInflate); |
| | | 400 | | |
| | 0 | 401 | | fixed (ZStream* stream = &_zStream) |
| | 0 | 402 | | { |
| | 0 | 403 | | return Interop.ZLib.Inflate(stream, flush); |
| | | 404 | | } |
| | | 405 | | } |
| | | 406 | | finally |
| | 0 | 407 | | { |
| | 0 | 408 | | if (refAdded) |
| | 0 | 409 | | { |
| | 0 | 410 | | DangerousRelease(); |
| | 0 | 411 | | } |
| | 0 | 412 | | } |
| | 0 | 413 | | } |
| | | 414 | | |
| | | 415 | | private unsafe ErrorCode InflateEnd() |
| | 0 | 416 | | { |
| | 0 | 417 | | EnsureState(State.InitializedForInflate); |
| | | 418 | | |
| | 0 | 419 | | fixed (ZStream* stream = &_zStream) |
| | 0 | 420 | | { |
| | 0 | 421 | | ErrorCode errC = Interop.ZLib.InflateEnd(stream); |
| | 0 | 422 | | _initializationState = State.Disposed; |
| | | 423 | | |
| | 0 | 424 | | return errC; |
| | | 425 | | } |
| | 0 | 426 | | } |
| | | 427 | | |
| | | 428 | | // This can work even after XxflateEnd(). Gets the error message from the native library. |
| | 0 | 429 | | public unsafe string GetErrorMessage() => Utf8StringMarshaller.ConvertToManaged(_zStream.msg) ?? string.Empt |
| | | 430 | | } |
| | | 431 | | } |
| | | 432 | | } |