Add support for SHA-256 integrity checks on imports (#166)
Related to #162 You can now add `sha256:XXX...XXX` after any import to verify that the import has the expected hash. This allows you to purify import code and protect the code against malicious modifications This is analogous to an IPFS-style import except that the hash is verified directly by the interpreter instead of trusting that the IPFS URL has not been compromised
This commit is contained in:
parent
edacd46a2b
commit
70276b82fb
20
default.nix
20
default.nix
|
@ -1,8 +1,9 @@
|
||||||
{ mkDerivation, ansi-wl-pprint, base, bytestring, case-insensitive
|
{ mkDerivation, ansi-wl-pprint, base, base16-bytestring, bytestring
|
||||||
, charset, containers, contravariant, exceptions, http-client
|
, case-insensitive, charset, containers, contravariant, cryptohash
|
||||||
, http-client-tls, lens, optparse-generic, parsers, prettyprinter
|
, exceptions, http-client, http-client-tls, lens, optparse-generic
|
||||||
, stdenv, system-fileio, system-filepath, tasty, tasty-hunit, text
|
, parsers, prettyprinter, stdenv, system-fileio, system-filepath
|
||||||
, text-format, transformers, trifecta, unordered-containers, vector
|
, tasty, tasty-hunit, text, text-format, transformers, trifecta
|
||||||
|
, unordered-containers, vector
|
||||||
}:
|
}:
|
||||||
mkDerivation {
|
mkDerivation {
|
||||||
pname = "dhall";
|
pname = "dhall";
|
||||||
|
@ -11,10 +12,11 @@ mkDerivation {
|
||||||
isLibrary = true;
|
isLibrary = true;
|
||||||
isExecutable = true;
|
isExecutable = true;
|
||||||
libraryHaskellDepends = [
|
libraryHaskellDepends = [
|
||||||
ansi-wl-pprint base bytestring case-insensitive charset containers
|
ansi-wl-pprint base base16-bytestring bytestring case-insensitive
|
||||||
contravariant exceptions http-client http-client-tls lens parsers
|
charset containers contravariant cryptohash exceptions http-client
|
||||||
prettyprinter system-fileio system-filepath text text-format
|
http-client-tls lens parsers prettyprinter system-fileio
|
||||||
transformers trifecta unordered-containers vector
|
system-filepath text text-format transformers trifecta
|
||||||
|
unordered-containers vector
|
||||||
];
|
];
|
||||||
executableHaskellDepends = [
|
executableHaskellDepends = [
|
||||||
base optparse-generic prettyprinter system-filepath text trifecta
|
base optparse-generic prettyprinter system-filepath text trifecta
|
||||||
|
|
|
@ -93,11 +93,13 @@ Library
|
||||||
Build-Depends:
|
Build-Depends:
|
||||||
base >= 4.9.0.0 && < 5 ,
|
base >= 4.9.0.0 && < 5 ,
|
||||||
ansi-wl-pprint < 0.7 ,
|
ansi-wl-pprint < 0.7 ,
|
||||||
|
base16-bytestring < 0.2 ,
|
||||||
bytestring < 0.11,
|
bytestring < 0.11,
|
||||||
case-insensitive < 1.3 ,
|
case-insensitive < 1.3 ,
|
||||||
charset < 0.4 ,
|
charset < 0.4 ,
|
||||||
containers >= 0.5.0.0 && < 0.6 ,
|
containers >= 0.5.0.0 && < 0.6 ,
|
||||||
contravariant < 1.5 ,
|
contravariant < 1.5 ,
|
||||||
|
cryptohash < 0.12,
|
||||||
exceptions >= 0.8.3 && < 0.9 ,
|
exceptions >= 0.8.3 && < 0.9 ,
|
||||||
http-client >= 0.4.30 && < 0.6 ,
|
http-client >= 0.4.30 && < 0.6 ,
|
||||||
http-client-tls >= 0.2.0 && < 0.4 ,
|
http-client-tls >= 0.2.0 && < 0.4 ,
|
||||||
|
|
|
@ -19,6 +19,7 @@ module Dhall.Core (
|
||||||
Const(..)
|
Const(..)
|
||||||
, HasHome(..)
|
, HasHome(..)
|
||||||
, PathType(..)
|
, PathType(..)
|
||||||
|
, PathHashed(..)
|
||||||
, PathMode(..)
|
, PathMode(..)
|
||||||
, Path(..)
|
, Path(..)
|
||||||
, Var(..)
|
, Var(..)
|
||||||
|
@ -63,6 +64,9 @@ import Numeric.Natural (Natural)
|
||||||
import Prelude hiding (FilePath, succ)
|
import Prelude hiding (FilePath, succ)
|
||||||
|
|
||||||
import qualified Control.Monad
|
import qualified Control.Monad
|
||||||
|
import qualified Data.ByteString
|
||||||
|
import qualified Data.ByteString.Char8
|
||||||
|
import qualified Data.ByteString.Base16
|
||||||
import qualified Data.Char
|
import qualified Data.Char
|
||||||
import qualified Data.HashSet
|
import qualified Data.HashSet
|
||||||
import qualified Data.List
|
import qualified Data.List
|
||||||
|
@ -105,7 +109,7 @@ data HasHome = Home | Homeless deriving (Eq, Ord, Show)
|
||||||
data PathType
|
data PathType
|
||||||
= File HasHome FilePath
|
= File HasHome FilePath
|
||||||
-- ^ Local path
|
-- ^ Local path
|
||||||
| URL Text (Maybe PathType)
|
| URL Text (Maybe PathHashed)
|
||||||
-- ^ URL of emote resource and optional headers stored in a path
|
-- ^ URL of emote resource and optional headers stored in a path
|
||||||
| Env Text
|
| Env Text
|
||||||
-- ^ Environment variable
|
-- ^ Environment variable
|
||||||
|
@ -132,14 +136,27 @@ instance Buildable PathType where
|
||||||
-- | How to interpret the path's contents (i.e. as Dhall code or raw text)
|
-- | How to interpret the path's contents (i.e. as Dhall code or raw text)
|
||||||
data PathMode = Code | RawText deriving (Eq, Ord, Show)
|
data PathMode = Code | RawText deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
data PathHashed = PathHashed
|
||||||
|
{ hash :: Maybe Data.ByteString.ByteString
|
||||||
|
, pathType :: PathType
|
||||||
|
} deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
instance Buildable PathHashed where
|
||||||
|
build (PathHashed Nothing p) = build p
|
||||||
|
build (PathHashed (Just h) p) = build p <> "sha256:" <> build string <> " "
|
||||||
|
where
|
||||||
|
bytes = Data.ByteString.Base16.encode h
|
||||||
|
|
||||||
|
string = Data.ByteString.Char8.unpack bytes
|
||||||
|
|
||||||
-- | Path to an external resource
|
-- | Path to an external resource
|
||||||
data Path = Path
|
data Path = Path
|
||||||
{ pathType :: PathType
|
{ pathHashed :: PathHashed
|
||||||
, pathMode :: PathMode
|
, pathMode :: PathMode
|
||||||
} deriving (Eq, Ord, Show)
|
} deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
instance Buildable Path where
|
instance Buildable Path where
|
||||||
build (Path {..}) = build pathType <> suffix
|
build (Path {..}) = build pathHashed <> suffix
|
||||||
where
|
where
|
||||||
suffix = case pathMode of
|
suffix = case pathMode of
|
||||||
RawText -> "as Text"
|
RawText -> "as Text"
|
||||||
|
|
|
@ -134,7 +134,13 @@ import Data.Traversable (traverse)
|
||||||
import Data.Typeable (Typeable)
|
import Data.Typeable (Typeable)
|
||||||
import Filesystem.Path ((</>), FilePath)
|
import Filesystem.Path ((</>), FilePath)
|
||||||
import Dhall.Core
|
import Dhall.Core
|
||||||
(Expr(..), HasHome(..), PathMode(..), PathType(..), Path(..))
|
( Expr(..)
|
||||||
|
, HasHome(..)
|
||||||
|
, PathHashed(..)
|
||||||
|
, PathMode(..)
|
||||||
|
, PathType(..)
|
||||||
|
, Path(..)
|
||||||
|
)
|
||||||
import Dhall.Parser (Parser(..), ParseError(..), Src(..))
|
import Dhall.Parser (Parser(..), ParseError(..), Src(..))
|
||||||
import Dhall.TypeCheck (X(..))
|
import Dhall.TypeCheck (X(..))
|
||||||
#if MIN_VERSION_http_client(0,5,0)
|
#if MIN_VERSION_http_client(0,5,0)
|
||||||
|
@ -148,7 +154,10 @@ import Text.Trifecta (Result(..))
|
||||||
import Text.Trifecta.Delta (Delta(..))
|
import Text.Trifecta.Delta (Delta(..))
|
||||||
|
|
||||||
import qualified Control.Monad.Trans.State.Strict as State
|
import qualified Control.Monad.Trans.State.Strict as State
|
||||||
|
import qualified Crypto.Hash.SHA256
|
||||||
import qualified Data.ByteString
|
import qualified Data.ByteString
|
||||||
|
import qualified Data.ByteString.Base16
|
||||||
|
import qualified Data.ByteString.Char8
|
||||||
import qualified Data.ByteString.Lazy
|
import qualified Data.ByteString.Lazy
|
||||||
import qualified Data.CaseInsensitive
|
import qualified Data.CaseInsensitive
|
||||||
import qualified Data.List as List
|
import qualified Data.List as List
|
||||||
|
@ -157,6 +166,7 @@ import qualified Data.Text.Encoding
|
||||||
import qualified Data.Text.Lazy as Text
|
import qualified Data.Text.Lazy as Text
|
||||||
import qualified Data.Text.Lazy.Builder as Builder
|
import qualified Data.Text.Lazy.Builder as Builder
|
||||||
import qualified Data.Text.Lazy.Encoding
|
import qualified Data.Text.Lazy.Encoding
|
||||||
|
import qualified Data.Text.Lazy.IO
|
||||||
import qualified Data.Vector
|
import qualified Data.Vector
|
||||||
import qualified Dhall.Core
|
import qualified Dhall.Core
|
||||||
import qualified Dhall.Parser
|
import qualified Dhall.Parser
|
||||||
|
@ -376,7 +386,7 @@ canonicalize (File hasHome0 file0:paths0) =
|
||||||
go currPath (Env _ :_ ) = File Homeless (clean currPath)
|
go currPath (Env _ :_ ) = File Homeless (clean currPath)
|
||||||
go currPath (URL url0 headers:rest ) = combine prefix suffix
|
go currPath (URL url0 headers:rest ) = combine prefix suffix
|
||||||
where
|
where
|
||||||
headers' = fmap (\h -> canonicalize (h:rest)) headers
|
headers' = fmap (onPathType (\h -> canonicalize (h:rest))) headers
|
||||||
|
|
||||||
prefix = parentURL (removeAtFromURL url0)
|
prefix = parentURL (removeAtFromURL url0)
|
||||||
|
|
||||||
|
@ -409,19 +419,29 @@ canonicalize (File hasHome0 file0:paths0) =
|
||||||
file' = Filesystem.parent (removeAtFromFile file) </> currPath
|
file' = Filesystem.parent (removeAtFromFile file) </> currPath
|
||||||
canonicalize (URL path headers:rest) = URL path headers'
|
canonicalize (URL path headers:rest) = URL path headers'
|
||||||
where
|
where
|
||||||
headers' = fmap (\h -> canonicalize (h:rest)) headers
|
headers' = fmap (onPathType (\h -> canonicalize (h:rest))) headers
|
||||||
canonicalize (Env env :_ ) = Env env
|
canonicalize (Env env :_ ) = Env env
|
||||||
|
|
||||||
|
onPathType :: (PathType -> PathType) -> PathHashed -> PathHashed
|
||||||
|
onPathType f (PathHashed a b) = PathHashed a (f b)
|
||||||
|
|
||||||
canonicalizePath :: [Path] -> Path
|
canonicalizePath :: [Path] -> Path
|
||||||
canonicalizePath [] =
|
canonicalizePath [] =
|
||||||
Path
|
Path
|
||||||
{ pathMode = Code
|
{ pathMode = Code
|
||||||
, pathType = canonicalize []
|
, pathHashed = PathHashed
|
||||||
|
{ hash = Nothing
|
||||||
|
, pathType = canonicalize []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
canonicalizePath (path:paths) =
|
canonicalizePath (path:paths) =
|
||||||
Path
|
Path
|
||||||
{ pathMode = pathMode path
|
{ pathMode = pathMode path
|
||||||
, pathType = canonicalize (map pathType (path:paths))
|
, pathHashed = (pathHashed path)
|
||||||
|
{ hash = hash (pathHashed path)
|
||||||
|
, pathType =
|
||||||
|
canonicalize (map (pathType . pathHashed) (path:paths))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parentURL :: Text -> Text
|
parentURL :: Text -> Text
|
||||||
|
@ -503,6 +523,67 @@ instance Show InternalError where
|
||||||
|
|
||||||
instance Exception InternalError
|
instance Exception InternalError
|
||||||
|
|
||||||
|
-- | Exception thrown when an integrity check fails
|
||||||
|
data HashMismatch = HashMismatch
|
||||||
|
{ expectedHash :: Data.ByteString.ByteString
|
||||||
|
, actualHash :: Data.ByteString.ByteString
|
||||||
|
} deriving (Typeable)
|
||||||
|
|
||||||
|
instance Exception HashMismatch
|
||||||
|
|
||||||
|
instance Show HashMismatch where
|
||||||
|
show (HashMismatch {..}) =
|
||||||
|
"\n"
|
||||||
|
<> "\ESC[1;31mError\ESC[0m: Import integrity check failed\n"
|
||||||
|
<> "\n"
|
||||||
|
<> "Expected hash:\n"
|
||||||
|
<> "\n"
|
||||||
|
<> "↳ " <> toString expectedHash <> "\n"
|
||||||
|
<> "\n"
|
||||||
|
<> "Actual hash:\n"
|
||||||
|
<> "\n"
|
||||||
|
<> "↳ " <> toString actualHash <> "\n"
|
||||||
|
where
|
||||||
|
toString =
|
||||||
|
Data.ByteString.Char8.unpack . Data.ByteString.Base16.encode
|
||||||
|
|
||||||
|
assertHash :: Maybe Data.ByteString.ByteString -> Text -> IO ()
|
||||||
|
assertHash Nothing _ =
|
||||||
|
return ()
|
||||||
|
assertHash (Just expectedHash) text =
|
||||||
|
if expectedHash == actualHash
|
||||||
|
then return ()
|
||||||
|
else Control.Exception.throwIO (HashMismatch {..})
|
||||||
|
where
|
||||||
|
actualBytes = Data.Text.Lazy.Encoding.encodeUtf8 text
|
||||||
|
|
||||||
|
actualHash = Crypto.Hash.SHA256.hashlazy actualBytes
|
||||||
|
|
||||||
|
parseFromFileEx
|
||||||
|
:: Maybe Data.ByteString.ByteString
|
||||||
|
-> Text.Trifecta.Parser a
|
||||||
|
-> FilePath
|
||||||
|
-> IO (Text.Trifecta.Result a)
|
||||||
|
parseFromFileEx hash parser path = do
|
||||||
|
text <- Data.Text.Lazy.IO.readFile stringPath
|
||||||
|
assertHash hash text
|
||||||
|
|
||||||
|
let lazyBytes = Data.Text.Lazy.Encoding.encodeUtf8 text
|
||||||
|
|
||||||
|
let strictBytes = Data.ByteString.Lazy.toStrict lazyBytes
|
||||||
|
|
||||||
|
let delta = Directed bytesPath 0 0 0 0
|
||||||
|
|
||||||
|
return (Text.Trifecta.parseByteString parser delta strictBytes)
|
||||||
|
where
|
||||||
|
stringPath = Filesystem.Path.CurrentOS.encodeString path
|
||||||
|
|
||||||
|
textPath = case Filesystem.Path.CurrentOS.toText path of
|
||||||
|
Left text -> text
|
||||||
|
Right text -> text
|
||||||
|
|
||||||
|
bytesPath = Data.Text.Encoding.encodeUtf8 textPath
|
||||||
|
|
||||||
-- | Parse an expression from a `Path` containing a Dhall program
|
-- | Parse an expression from a `Path` containing a Dhall program
|
||||||
exprFromPath :: Manager -> Path -> IO (Expr Src Path)
|
exprFromPath :: Manager -> Path -> IO (Expr Src Path)
|
||||||
exprFromPath m (Path {..}) = case pathType of
|
exprFromPath m (Path {..}) = case pathType of
|
||||||
|
@ -521,23 +602,17 @@ exprFromPath m (Path {..}) = case pathType of
|
||||||
then return ()
|
then return ()
|
||||||
else Control.Exception.throwIO MissingFile
|
else Control.Exception.throwIO MissingFile
|
||||||
|
|
||||||
let string = Filesystem.Path.CurrentOS.encodeString path
|
|
||||||
|
|
||||||
-- Unfortunately, GHC throws an `InappropriateType` exception
|
-- Unfortunately, GHC throws an `InappropriateType` exception
|
||||||
-- when trying to read a directory, but does not export the
|
-- when trying to read a directory, but does not export the
|
||||||
-- exception, so I must resort to a more heavy-handed `catch`
|
-- exception, so I must resort to a more heavy-handed `catch`
|
||||||
let handler :: IOException -> IO (Result (Expr Src Path))
|
let handler :: IOException -> IO (Result (Expr Src Path))
|
||||||
handler e = do
|
handler e = do
|
||||||
let string' =
|
|
||||||
Filesystem.Path.CurrentOS.encodeString
|
|
||||||
(path </> "@")
|
|
||||||
|
|
||||||
-- If the fallback fails, reuse the original exception
|
-- If the fallback fails, reuse the original exception
|
||||||
-- to avoid user confusion
|
-- to avoid user confusion
|
||||||
Text.Trifecta.parseFromFileEx parser string'
|
parseFromFileEx hash parser (path </> "@")
|
||||||
`onException` throwIO e
|
`onException` throwIO e
|
||||||
|
|
||||||
x <- Text.Trifecta.parseFromFileEx parser string `catch` handler
|
x <- parseFromFileEx hash parser path `catch` handler
|
||||||
case x of
|
case x of
|
||||||
Failure errInfo -> do
|
Failure errInfo -> do
|
||||||
throwIO (ParseError (Text.Trifecta._errDoc errInfo))
|
throwIO (ParseError (Text.Trifecta._errDoc errInfo))
|
||||||
|
@ -545,6 +620,7 @@ exprFromPath m (Path {..}) = case pathType of
|
||||||
return expr
|
return expr
|
||||||
RawText -> do
|
RawText -> do
|
||||||
text <- Filesystem.readTextFile path
|
text <- Filesystem.readTextFile path
|
||||||
|
assertHash hash (Text.fromStrict text)
|
||||||
return (TextLit (build text))
|
return (TextLit (build text))
|
||||||
URL url headerPath -> do
|
URL url headerPath -> do
|
||||||
request <- HTTP.parseUrlThrow (Text.unpack url)
|
request <- HTTP.parseUrlThrow (Text.unpack url)
|
||||||
|
@ -605,6 +681,8 @@ exprFromPath m (Path {..}) = case pathType of
|
||||||
Left err -> throwIO err
|
Left err -> throwIO err
|
||||||
Right text -> return text
|
Right text -> return text
|
||||||
|
|
||||||
|
assertHash hash text
|
||||||
|
|
||||||
case pathMode of
|
case pathMode of
|
||||||
Code -> do
|
Code -> do
|
||||||
let urlBytes = Data.Text.Lazy.Encoding.encodeUtf8 url
|
let urlBytes = Data.Text.Lazy.Encoding.encodeUtf8 url
|
||||||
|
@ -641,6 +719,8 @@ exprFromPath m (Path {..}) = case pathType of
|
||||||
x <- System.Environment.lookupEnv (Text.unpack env)
|
x <- System.Environment.lookupEnv (Text.unpack env)
|
||||||
case x of
|
case x of
|
||||||
Just str -> do
|
Just str -> do
|
||||||
|
assertHash hash (Text.pack str)
|
||||||
|
|
||||||
case pathMode of
|
case pathMode of
|
||||||
Code -> do
|
Code -> do
|
||||||
let envBytes = Data.Text.Lazy.Encoding.encodeUtf8 env
|
let envBytes = Data.Text.Lazy.Encoding.encodeUtf8 env
|
||||||
|
@ -654,6 +734,8 @@ exprFromPath m (Path {..}) = case pathType of
|
||||||
RawText -> return (TextLit (build str))
|
RawText -> return (TextLit (build str))
|
||||||
Nothing -> throwIO (MissingEnvironmentVariable env)
|
Nothing -> throwIO (MissingEnvironmentVariable env)
|
||||||
where
|
where
|
||||||
|
PathHashed {..} = pathHashed
|
||||||
|
|
||||||
parser = unParser (do
|
parser = unParser (do
|
||||||
Text.Parser.Token.whiteSpace
|
Text.Parser.Token.whiteSpace
|
||||||
r <- Dhall.Parser.expr
|
r <- Dhall.Parser.expr
|
||||||
|
@ -691,15 +773,15 @@ loadStaticWith :: MonadCatch m => (Path -> m (Expr Src Path))
|
||||||
loadStaticWith from_path ctx path = do
|
loadStaticWith from_path ctx path = do
|
||||||
paths <- zoom stack State.get
|
paths <- zoom stack State.get
|
||||||
|
|
||||||
let local (Path (URL url _) _) =
|
let local (Path (PathHashed _ (URL url _)) _) =
|
||||||
case HTTP.parseUrlThrow (Text.unpack url) of
|
case HTTP.parseUrlThrow (Text.unpack url) of
|
||||||
Nothing -> False
|
Nothing -> False
|
||||||
Just request -> case HTTP.host request of
|
Just request -> case HTTP.host request of
|
||||||
"127.0.0.1" -> True
|
"127.0.0.1" -> True
|
||||||
"localhost" -> True
|
"localhost" -> True
|
||||||
_ -> False
|
_ -> False
|
||||||
local (Path (File _ _ ) _) = True
|
local (Path (PathHashed _ (File _ _ )) _) = True
|
||||||
local (Path (Env _ ) _) = True
|
local (Path (PathHashed _ (Env _ )) _) = True
|
||||||
|
|
||||||
let parent = canonicalizePath paths
|
let parent = canonicalizePath paths
|
||||||
let here = canonicalizePath (path:paths)
|
let here = canonicalizePath (path:paths)
|
||||||
|
|
|
@ -43,6 +43,7 @@ import Text.Trifecta
|
||||||
import Text.Trifecta.Delta (Delta)
|
import Text.Trifecta.Delta (Delta)
|
||||||
|
|
||||||
import qualified Control.Monad
|
import qualified Control.Monad
|
||||||
|
import qualified Data.ByteString.Base16.Lazy
|
||||||
import qualified Data.Char
|
import qualified Data.Char
|
||||||
import qualified Data.HashSet
|
import qualified Data.HashSet
|
||||||
import qualified Data.Map
|
import qualified Data.Map
|
||||||
|
@ -931,7 +932,7 @@ http = do
|
||||||
whitespace
|
whitespace
|
||||||
b <- optional (do
|
b <- optional (do
|
||||||
_using
|
_using
|
||||||
pathType_ )
|
pathHashed_ )
|
||||||
return (URL (Data.Text.Lazy.Builder.toLazyText a) b)
|
return (URL (Data.Text.Lazy.Builder.toLazyText a) b)
|
||||||
|
|
||||||
env :: Parser PathType
|
env :: Parser PathType
|
||||||
|
@ -1493,10 +1494,27 @@ exprA = completeExpression
|
||||||
pathType_ :: Parser PathType
|
pathType_ :: Parser PathType
|
||||||
pathType_ = choice [ file, http, env ]
|
pathType_ = choice [ file, http, env ]
|
||||||
|
|
||||||
|
pathHashed_ :: Parser PathHashed
|
||||||
|
pathHashed_ = do
|
||||||
|
pathType <- pathType_
|
||||||
|
hash <- optional pathHash_
|
||||||
|
return (PathHashed {..})
|
||||||
|
where
|
||||||
|
pathHash_ = do
|
||||||
|
_ <- Text.Parser.Char.text "sha256:"
|
||||||
|
builder <- count 64 (satisfy hexdig <?> "hex digit")
|
||||||
|
whitespace
|
||||||
|
let lazyText = Data.Text.Lazy.Builder.toLazyText builder
|
||||||
|
let lazyBytes = Data.Text.Lazy.Encoding.encodeUtf8 lazyText
|
||||||
|
let (hash, suffix) = Data.ByteString.Base16.Lazy.decode lazyBytes
|
||||||
|
if Data.ByteString.Lazy.null suffix
|
||||||
|
then return (Data.ByteString.Lazy.toStrict hash)
|
||||||
|
else fail "Invalid sha256 hash"
|
||||||
|
|
||||||
import_ :: Parser Path
|
import_ :: Parser Path
|
||||||
import_ = (do
|
import_ = (do
|
||||||
pathType <- pathType_
|
pathHashed <- pathHashed_
|
||||||
pathMode <- alternative <|> pure Code
|
pathMode <- alternative <|> pure Code
|
||||||
return (Path {..}) ) <?> "import"
|
return (Path {..}) ) <?> "import"
|
||||||
where
|
where
|
||||||
alternative = do
|
alternative = do
|
||||||
|
|
Loading…
Reference in New Issue
Block a user