summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2014-04-02 19:57:05 +0200
committerAndreas Rumpf <rumpf_a@web.de>2014-04-02 19:57:05 +0200
commit843693d676a3ff9e7609c0f623edfdce37f8a007 (patch)
tree4e6c267d00b86c7131f63b5a53d3cd2c275b288b /lib
parentcc9230824a9dd957b9dec5341da665952fbe6445 (diff)
parent20e55fc5d411dbe92e839fcaa8474ecb6e92e96a (diff)
downloadNim-843693d676a3ff9e7609c0f623edfdce37f8a007.tar.gz
Merge pull request #1048 from gradha/pr_adds_convenience_uncompress_to_zlib
Adds convenience uncompress and inflate procs to zlib.
Diffstat (limited to 'lib')
-rw-r--r--lib/wrappers/zip/zlib.nim126
1 files changed, 126 insertions, 0 deletions
diff --git a/lib/wrappers/zip/zlib.nim b/lib/wrappers/zip/zlib.nim
index f505b95a7..cb61783d2 100644
--- a/lib/wrappers/zip/zlib.nim
+++ b/lib/wrappers/zip/zlib.nim
@@ -182,3 +182,129 @@ proc zlibAllocMem*(AppData: Pointer, Items, Size: int): Pointer {.cdecl.} =
 
 proc zlibFreeMem*(AppData, `Block`: Pointer) {.cdecl.} = 
   dealloc(`Block`)
+
+proc uncompress*(sourceBuf: cstring, sourceLen: int): string =
+  ## Given a deflated cstring returns its inflated version.
+  ##
+  ## Passing a nil cstring will crash this proc in release mode and assert in
+  ## debug mode.
+  ##
+  ## Returns nil on problems. Failure is a very loose concept, it could be you
+  ## passing a non deflated string, or it could mean not having enough memory
+  ## for the inflated version.
+  ##
+  ## The uncompression algorithm is based on
+  ## http://stackoverflow.com/questions/17820664 but does ignore some of the
+  ## original signed/unsigned checks, so may fail with big chunks of data
+  ## exceeding the positive size of an int32. The algorithm can deal with
+  ## concatenated deflated values properly.
+  assert (not sourceBuf.isNil)
+
+  var z: TZStream
+  # Initialize input.
+  z.next_in = sourceBuf
+
+  # Input left to decompress.
+  var left = zlib.Uint(sourceLen)
+  if left < 1:
+    # Incomplete gzip stream, or overflow?
+    return
+
+  # Create starting space for output (guess double the input size, will grow if
+  # needed -- in an extreme case, could end up needing more than 1000 times the
+  # input size)
+  var space = zlib.Uint(left shl 1)
+  if space < left:
+    space = left
+
+  var decompressed = newStringOfCap(space)
+
+  # Initialize output.
+  z.next_out = addr(decompressed[0])
+  # Output generated so far.
+  var have = 0
+
+  # Set up for gzip decoding.
+  z.avail_in = 0;
+  var status = inflateInit2(z, (15+16))
+  if status != Z_OK:
+    # Out of memory.
+    return
+
+  # Make sure memory allocated by inflateInit2() is freed eventually.
+  finally: discard inflateEnd(z)
+
+  # Decompress all of self.
+  while true:
+    # Allow for concatenated gzip streams (per RFC 1952).
+    if status == Z_STREAM_END:
+      discard inflateReset(z)
+
+    # Provide input for inflate.
+    if z.avail_in == 0:
+      # This only makes sense in the C version using unsigned values.
+      z.avail_in = left
+      left -= z.avail_in
+
+    # Decompress the available input.
+    while true:
+      # Allocate more output space if none left.
+      if space == have:
+        # Double space, handle overflow.
+        space = space shl 1
+        if space < have:
+          # Space was likely already maxed out.
+          discard inflateEnd(z)
+          return
+
+        # Increase space.
+        decompressed.setLen(space)
+        # Update output pointer (might have moved).
+        z.next_out = addr(decompressed[have])
+
+      # Provide output space for inflate.
+      z.avail_out = zlib.Uint(space - have)
+      have += z.avail_out;
+
+      # Inflate and update the decompressed size.
+      status = inflate(z, Z_SYNC_FLUSH);
+      have -= z.avail_out;
+
+      # Bail out if any errors.
+      if status != Z_OK and status != Z_BUF_ERROR and status != Z_STREAM_END:
+        # Invalid gzip stream.
+        discard inflateEnd(z)
+        return
+
+      # Repeat until all output is generated from provided input (note
+      # that even if z.avail_in is zero, there may still be pending
+      # output -- we're not done until the output buffer isn't filled)
+      if z.avail_out != 0:
+        break
+    # Continue until all input consumed.
+    if left == 0 and z.avail_in == 0:
+      break
+
+  # Verify that the input is a valid gzip stream.
+  if status != Z_STREAM_END:
+    # Incomplete gzip stream.
+    return
+
+  decompressed.setLen(have)
+  swap(result, decompressed)
+
+
+proc inflate*(buffer: var string): bool {.discardable.} =
+  ## Convenience proc which inflates a string containing compressed data.
+  ##
+  ## Passing a nil string will crash this proc in release mode and assert in
+  ## debug mode. It is ok to pass a buffer which doesn't contain deflated data,
+  ## in this case the proc won't modify the buffer.
+  ##
+  ## Returns true if `buffer` was successfully inflated.
+  assert (not buffer.isNil)
+  if buffer.len < 1: return
+  var temp = uncompress(addr(buffer[0]), buffer.len)
+  if not temp.isNil:
+    swap(buffer, temp)
+    result = true