diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2014-04-02 19:57:05 +0200 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2014-04-02 19:57:05 +0200 |
commit | 843693d676a3ff9e7609c0f623edfdce37f8a007 (patch) | |
tree | 4e6c267d00b86c7131f63b5a53d3cd2c275b288b /lib | |
parent | cc9230824a9dd957b9dec5341da665952fbe6445 (diff) | |
parent | 20e55fc5d411dbe92e839fcaa8474ecb6e92e96a (diff) | |
download | Nim-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.nim | 126 |
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 |