about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/libexpr/primops.cc10
-rw-r--r--src/libstore/download.cc35
-rw-r--r--src/libstore/download.hh4
3 files changed, 37 insertions, 12 deletions
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 565ed69ae7..25736ebff0 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -1680,9 +1680,8 @@ static void prim_compareVersions(EvalState & state, const Pos & pos, Value * * a
 void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
     const string & who, bool unpack)
 {
-    if (state.restricted) throw Error(format("‘%1%’ is not allowed in restricted mode") % who);
-
     string url;
+    Hash expectedHash;
 
     state.forceValue(*args[0]);
 
@@ -1694,6 +1693,8 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
             string name(attr.name);
             if (name == "url")
                 url = state.forceStringNoCtx(*attr.value, *attr.pos);
+            else if (name == "sha256")
+                expectedHash = parseHash16or32(htSHA256, state.forceStringNoCtx(*attr.value, *attr.pos));
             else
                 throw EvalError(format("unsupported argument ‘%1%’ to ‘%2%’, at %3%") % attr.name % who % attr.pos);
         }
@@ -1704,7 +1705,10 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
     } else
         url = state.forceStringNoCtx(*args[0], pos);
 
-    Path res = makeDownloader()->downloadCached(state.store, url, unpack);
+    if (state.restricted && !expectedHash)
+        throw Error(format("‘%1%’ is not allowed in restricted mode") % who);
+
+    Path res = makeDownloader()->downloadCached(state.store, url, unpack, expectedHash);
     mkString(v, res, PathSet({res}));
 }
 
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 04a2b325c6..f099268953 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -3,6 +3,7 @@
 #include "globals.hh"
 #include "hash.hh"
 #include "store-api.hh"
+#include "archive.hh"
 
 #include <curl/curl.h>
 
@@ -221,10 +222,21 @@ ref<Downloader> makeDownloader()
     return make_ref<CurlDownloader>();
 }
 
-Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpack)
+Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpack, const Hash & expectedHash)
 {
     auto url = resolveUri(url_);
 
+    string name;
+    auto p = url.rfind('/');
+    if (p != string::npos) name = string(url, p + 1);
+
+    Path expectedStorePath;
+    if (expectedHash) {
+        expectedStorePath = store->makeFixedOutputPath(unpack, expectedHash.type, expectedHash, name);
+        if (store->isValidPath(expectedStorePath))
+            return expectedStorePath;
+    }
+
     Path cacheDir = getCacheDir() + "/nix/tarballs";
     createDirs(cacheDir);
 
@@ -258,10 +270,6 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
             storePath = "";
     }
 
-    string name;
-    auto p = url.rfind('/');
-    if (p != string::npos) name = string(url, p + 1);
-
     if (!skip) {
 
         try {
@@ -269,8 +277,16 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
             options.expectedETag = expectedETag;
             auto res = download(url, options);
 
-            if (!res.cached)
-                storePath = store->addTextToStore(name, *res.data, PathSet(), false);
+            if (!res.cached) {
+                ValidPathInfo info;
+                StringSink sink;
+                dumpString(*res.data, sink);
+                Hash hash = hashString(expectedHash ? expectedHash.type : htSHA256, *res.data);
+                info.path = store->makeFixedOutputPath(false, hash.type, hash, name);
+                info.narHash = hashString(htSHA256, *sink.s);
+                store->addToStore(info, *sink.s, false, true);
+                storePath = info.path;
+            }
 
             assert(!storePath.empty());
             replaceSymlink(storePath, fileLink);
@@ -300,9 +316,12 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
             unpackedStorePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, false);
         }
         replaceSymlink(unpackedStorePath, unpackedLink);
-        return unpackedStorePath;
+        storePath = unpackedStorePath;
     }
 
+    if (expectedStorePath != "" && storePath != expectedStorePath)
+        throw nix::Error(format("hash mismatch in file downloaded from ‘%s’") % url);
+
     return storePath;
 }
 
diff --git a/src/libstore/download.hh b/src/libstore/download.hh
index eb2b76678a..efddc55281 100644
--- a/src/libstore/download.hh
+++ b/src/libstore/download.hh
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "types.hh"
+#include "hash.hh"
 
 #include <string>
 
@@ -27,7 +28,8 @@ struct Downloader
 {
     virtual DownloadResult download(string url, const DownloadOptions & options) = 0;
 
-    Path downloadCached(ref<Store> store, const string & url, bool unpack);
+    Path downloadCached(ref<Store> store, const string & url, bool unpack,
+        const Hash & expectedHash = Hash());
 
     enum Error { NotFound, Forbidden, Misc };
 };