From c6295a3afd87a605f30f1de8b09d7885eb08fedb Mon Sep 17 00:00:00 2001
From: Tom Bereknyei <tom@rebelliondefense.com>
Date: Fri, 13 Dec 2019 03:29:33 -0500
Subject: [PATCH] Initial gzip support

Closes #3256
---
 configure.ac               |  5 +++
 release-common.nix         |  2 +-
 src/libutil/compression.cc | 64 ++++++++++++++++++++++++++++++++++++++
 src/libutil/tarfile.cc     |  1 +
 tests/tarball.sh           | 17 ++++++++++
 5 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/configure.ac b/configure.ac
index 9dd0acd86..99248b90c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -209,6 +209,11 @@ PKG_CHECK_MODULES([LIBLZMA], [liblzma], [CXXFLAGS="$LIBLZMA_CFLAGS $CXXFLAGS"])
 AC_CHECK_LIB([lzma], [lzma_stream_encoder_mt],
   [AC_DEFINE([HAVE_LZMA_MT], [1], [xz multithreaded compression support])])
 
+# Look for zlib, a required dependency.
+PKG_CHECK_MODULES([ZLIB], [zlib], [CXXFLAGS="$ZLIB_CFLAGS $CXXFLAGS"])
+AC_CHECK_HEADER([zlib.h],[:],[AC_MSG_ERROR([could not find the zlib.h header])])
+LDFLAGS="-lz $LDFLAGS"
+
 # Look for libbrotli{enc,dec}.
 PKG_CHECK_MODULES([LIBBROTLI], [libbrotlienc libbrotlidec], [CXXFLAGS="$LIBBROTLI_CFLAGS $CXXFLAGS"])
 
diff --git a/release-common.nix b/release-common.nix
index dd5f939d9..3765e61b3 100644
--- a/release-common.nix
+++ b/release-common.nix
@@ -47,7 +47,7 @@ rec {
 
   buildDeps =
     [ curl
-      bzip2 xz brotli editline
+      bzip2 xz brotli zlib editline
       openssl pkgconfig sqlite boehmgc
       boost
       nlohmann_json
diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc
index 0dd84e320..17b506d5d 100644
--- a/src/libutil/compression.cc
+++ b/src/libutil/compression.cc
@@ -11,6 +11,8 @@
 #include <brotli/decode.h>
 #include <brotli/encode.h>
 
+#include <zlib.h>
+
 #include <iostream>
 
 namespace nix {
@@ -42,6 +44,66 @@ struct NoneSink : CompressionSink
     void write(const unsigned char * data, size_t len) override { nextSink(data, len); }
 };
 
+struct GzipDecompressionSink : CompressionSink
+{
+    Sink & nextSink;
+    z_stream strm;
+    bool finished = false;
+    uint8_t outbuf[BUFSIZ];
+
+    GzipDecompressionSink(Sink & nextSink) : nextSink(nextSink)
+    {
+        strm.zalloc = Z_NULL;
+        strm.zfree = Z_NULL;
+        strm.opaque = Z_NULL;
+        strm.avail_in = 0;
+        strm.next_in = Z_NULL;
+        strm.next_out = outbuf;
+        strm.avail_out = sizeof(outbuf);
+
+        // Enable gzip and zlib decoding (+32) with 15 windowBits
+        int ret = inflateInit2(&strm,15+32);
+        if (ret != Z_OK)
+            throw CompressionError("unable to initialise gzip encoder");
+    }
+
+    ~GzipDecompressionSink()
+    {
+        inflateEnd(&strm);
+    }
+
+    void finish() override
+    {
+        CompressionSink::flush();
+        write(nullptr, 0);
+    }
+
+    void write(const unsigned char * data, size_t len) override
+    {
+        assert(len <= std::numeric_limits<decltype(strm.avail_in)>::max());
+
+        strm.next_in = (Bytef *) data;
+        strm.avail_in = len;
+
+        while (!finished && (!data || strm.avail_in)) {
+            checkInterrupt();
+
+            int ret = inflate(&strm,Z_SYNC_FLUSH);
+            if (ret != Z_OK && ret != Z_STREAM_END)
+                throw CompressionError("error while decompressing gzip file: %d: %d: %d",ret, len, strm.avail_in);
+
+
+            finished = ret == Z_STREAM_END;
+
+            if (strm.avail_out < sizeof(outbuf) || strm.avail_in == 0) {
+                nextSink(outbuf, sizeof(outbuf) - strm.avail_out);
+                strm.next_out = (Bytef *) outbuf;
+                strm.avail_out = sizeof(outbuf);
+            }
+        }
+    }
+};
+
 struct XzDecompressionSink : CompressionSink
 {
     Sink & nextSink;
@@ -215,6 +277,8 @@ ref<CompressionSink> makeDecompressionSink(const std::string & method, Sink & ne
         return make_ref<XzDecompressionSink>(nextSink);
     else if (method == "bzip2")
         return make_ref<BzipDecompressionSink>(nextSink);
+    else if (method == "gzip")
+        return make_ref<GzipDecompressionSink>(nextSink);
     else if (method == "br")
         return make_ref<BrotliDecompressionSink>(nextSink);
     else
diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc
index 262bc655f..c00673182 100644
--- a/src/libutil/tarfile.cc
+++ b/src/libutil/tarfile.cc
@@ -24,6 +24,7 @@ void unpackTarfile(const Path & tarFile, const Path & destDir,
         auto decompressor =
             // FIXME: add .gz support
             hasSuffix(*baseName, ".bz2") ? makeDecompressionSink("bzip2", sink) :
+            hasSuffix(*baseName, ".gz") ? makeDecompressionSink("gzip", sink) :
             hasSuffix(*baseName, ".xz") ? makeDecompressionSink("xz", sink) :
             makeDecompressionSink("none", sink);
         readFile(tarFile, *decompressor);
diff --git a/tests/tarball.sh b/tests/tarball.sh
index ba534c626..ec810b4d7 100644
--- a/tests/tarball.sh
+++ b/tests/tarball.sh
@@ -26,3 +26,20 @@ nix-instantiate --eval -E 'with <fnord/xyzzy>; 1 + 2' -I fnord=file://no-such-ta
 (! nix-instantiate --eval -E '<fnord/xyzzy> 1' -I fnord=file://no-such-tarball.tar.xz)
 
 nix-instantiate --eval -E '<fnord/config.nix>' -I fnord=file://no-such-tarball.tar.xz -I fnord=.
+
+tarball=$TEST_ROOT/tarball.tar.gz
+(cd $TEST_ROOT && tar c tarball) | gzip > $tarball
+
+nix-env -f file://$tarball -qa --out-path | grep -q dependencies
+
+nix-build -o $TEST_ROOT/result file://$tarball
+
+nix-build -o $TEST_ROOT/result '<foo>' -I foo=file://$tarball
+
+nix-build -o $TEST_ROOT/result -E "import (fetchTarball file://$tarball)"
+
+nix-instantiate --eval -E '1 + 2' -I fnord=file://no-such-tarball.tar.gz
+nix-instantiate --eval -E 'with <fnord/xyzzy>; 1 + 2' -I fnord=file://no-such-tarball.tar.gz
+(! nix-instantiate --eval -E '<fnord/xyzzy> 1' -I fnord=file://no-such-tarball.tar.gz)
+
+nix-instantiate --eval -E '<fnord/config.nix>' -I fnord=file://no-such-tarball.tar.gz -I fnord=.
-- 
GitLab