diff --git a/src/libstore/fetchers/fetchers.cc b/src/libstore/fetchers/fetchers.cc index 7f82d5af0..efc18dbb8 100644 --- a/src/libstore/fetchers/fetchers.cc +++ b/src/libstore/fetchers/fetchers.cc @@ -39,6 +39,10 @@ std::pair> Input::fetchTree(ref store) if (input->narHash) assert(input->narHash == tree.narHash); + if (narHash && narHash != input->narHash) + throw Error("NAR hash mismatch in input '%s', expected '%s', got '%s'", + to_string(), narHash->to_string(SRI), input->narHash->to_string(SRI)); + return {std::move(tree), input}; } diff --git a/src/libstore/fetchers/fetchers.hh b/src/libstore/fetchers/fetchers.hh index 1f3be8c2b..ccc1683ba 100644 --- a/src/libstore/fetchers/fetchers.hh +++ b/src/libstore/fetchers/fetchers.hh @@ -25,7 +25,7 @@ struct Tree struct Input : std::enable_shared_from_this { std::string type; - std::optional narHash; + std::optional narHash; // FIXME: implement virtual ~Input() { } diff --git a/src/libstore/fetchers/tarball.cc b/src/libstore/fetchers/tarball.cc new file mode 100644 index 000000000..e82066089 --- /dev/null +++ b/src/libstore/fetchers/tarball.cc @@ -0,0 +1,91 @@ +#include "fetchers.hh" +#include "download.hh" +#include "globals.hh" +#include "parse.hh" +#include "store-api.hh" + +namespace nix::fetchers { + +struct TarballInput : Input +{ + ParsedURL url; + std::optional hash; + + bool operator ==(const Input & other) const override + { + auto other2 = dynamic_cast(&other); + return + other2 + && to_string() == other2->to_string() + && hash == other2->hash; + } + + bool isImmutable() const override + { + return (bool) hash; + } + + std::string to_string() const override + { + auto url2(url); + if (narHash) + url2.query.insert_or_assign("narHash", narHash->to_string(SRI)); + return url2.to_string(); + } + + std::pair> fetchTreeInternal(nix::ref store) const override + { + CachedDownloadRequest request(url.to_string()); + request.unpack = true; + request.getLastModified = true; + request.name = "source"; + + auto res = getDownloader()->downloadCached(store, request); + + auto input = std::make_shared(*this); + + auto storePath = store->parseStorePath(res.storePath); + + input->narHash = store->queryPathInfo(storePath)->narHash; + + return { + Tree { + .actualPath = res.path, + .storePath = std::move(storePath), + .lastModified = *res.lastModified + }, + input + }; + } +}; + +struct TarballInputScheme : InputScheme +{ + std::unique_ptr inputFromURL(const ParsedURL & url) override + { + if (url.scheme != "file" && url.scheme != "http" && url.scheme != "https") return nullptr; + + if (!hasSuffix(url.path, ".zip") + && !hasSuffix(url.path, ".tar") + && !hasSuffix(url.path, ".tar.gz") + && !hasSuffix(url.path, ".tar.xz") + && !hasSuffix(url.path, ".tar.bz2")) + return nullptr; + + auto input = std::make_unique(); + input->type = "tarball"; + input->url = url; + + auto narHash = url.query.find("narHash"); + if (narHash != url.query.end()) { + // FIXME: require SRI hash. + input->narHash = Hash(narHash->second); + } + + return input; + } +}; + +static auto r1 = OnStartup([] { registerInputScheme(std::make_unique()); }); + +} diff --git a/tests/flakes.sh b/tests/flakes.sh index 5b844a784..41f123883 100644 --- a/tests/flakes.sh +++ b/tests/flakes.sh @@ -573,3 +573,17 @@ nix flake info --flake-registry $registry --json hg+file://$flake5Dir [[ $(nix flake info --flake-registry $registry --json hg+file://$flake5Dir | jq -e -r .revCount) = 1 ]] nix build -o $TEST_ROOT/result hg+file://$flake5Dir --no-registries --no-allow-dirty + +# Test tarball flakes +tar cfz $TEST_ROOT/flake.tar.gz -C $TEST_ROOT flake5 + +nix build -o $TEST_ROOT/result file://$TEST_ROOT/flake.tar.gz + +# Building with a tarball URL containing a SRI hash should also work. +url=$(nix flake info --json file://$TEST_ROOT/flake.tar.gz | jq -r .url) +[[ $url =~ sha256- ]] + +nix build -o $TEST_ROOT/result $url + +# Building with an incorrect SRI hash should fail. +nix build -o $TEST_ROOT/result "file://$TEST_ROOT/flake.tar.gz?narHash=sha256-qQ2Zz4DNHViCUrp6gTS7EE4+RMqFQtUfWF2UNUtJKS0=" 2>&1 | grep 'NAR hash mismatch'