Skip to content

Commit b5e9bc8

Browse files
committed
remote: add attoparsec based StorePath parser
1 parent aad97d2 commit b5e9bc8

File tree

4 files changed

+56
-9
lines changed

4 files changed

+56
-9
lines changed

hnix-store-core/hnix-store-core.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ library
3131
, System.Nix.StorePathMetadata
3232
, System.Nix.ValidPath
3333
build-depends: base >=4.10 && <5
34+
, attoparsec
3435
, base16-bytestring
3536
, bytestring
3637
, binary
@@ -71,6 +72,7 @@ test-suite format-tests
7172
tests
7273
build-depends:
7374
hnix-store-core
75+
, attoparsec
7476
, base
7577
, base16-bytestring
7678
, base64-bytestring

hnix-store-core/src/System/Nix/Internal/StorePath.hs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import System.Nix.Hash
1919
, decodeBase32
2020
, SomeNamedDigest
2121
)
22+
import System.Nix.Internal.Base32 (digits32)
2223

2324
import Data.Text (Text)
2425
import Data.Text.Encoding (encodeUtf8)
@@ -27,13 +28,15 @@ import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)
2728
import Data.ByteString (ByteString)
2829
import qualified Data.ByteString as BS
2930
import qualified Data.ByteString.Char8 as BC
31+
import qualified Data.Char
3032
import Data.Hashable (Hashable(..))
3133
import Data.HashSet (HashSet)
3234
import Data.Proxy (Proxy(..))
3335

36+
import Data.Attoparsec.ByteString.Char8 (Parser, (<?>))
37+
import qualified Data.Attoparsec.ByteString.Char8 as P
3438
import System.FilePath (splitFileName)
3539

36-
import Data.Char
3740
-- | A path in a Nix store.
3841
--
3942
-- From the Nix thesis: A store path is the full path of a store
@@ -113,20 +116,23 @@ makeStorePathName n = case validStorePathName n of
113116
True -> Right $ StorePathName n
114117
False -> Left $ reasonInvalid n
115118

119+
reasonInvalid :: Text -> String
116120
reasonInvalid n | n == "" = "Empty name"
117121
reasonInvalid n | (T.length n > 211) = "Path too long"
118122
reasonInvalid n | (T.head n == '.') = "Leading dot"
119123
reasonInvalid n | otherwise = "Invalid character"
120124

125+
validStorePathName :: Text -> Bool
121126
validStorePathName "" = False
122127
validStorePathName n = (T.length n <= 211)
123128
&& T.head n /= '.'
124-
&& T.all validChar n
125-
where
126-
validChar c = any ($ c) $
127-
[ isAsciiLower -- 'a'..'z'
128-
, isAsciiUpper -- 'A'..'Z'
129-
, isDigit
129+
&& T.all validStorePathNameChar n
130+
131+
validStorePathNameChar :: Char -> Bool
132+
validStorePathNameChar c = any ($ c) $
133+
[ Data.Char.isAsciiLower -- 'a'..'z'
134+
, Data.Char.isAsciiUpper -- 'A'..'Z'
135+
, Data.Char.isDigit
130136
] ++
131137
map (==) "+-._?="
132138

@@ -186,3 +192,31 @@ parsePath expectedRoot x =
186192
else Left $ unwords $ [ "Root store dir mismatch, expected ", expectedRoot, "got", rootDir']
187193
in
188194
StorePath <$> digest <*> name <*> storeDir
195+
196+
pathParser :: FilePath -> Parser StorePath
197+
pathParser expectedRoot = do
198+
P.string (BC.pack expectedRoot)
199+
<?> "Store root mismatch" -- e.g. /nix/store
200+
201+
P.char '/'
202+
<?> "Expecting path separator"
203+
204+
digest <- decodeBase32 . T.pack . BC.unpack
205+
<$> P.takeWhile1 (\c -> c `elem` digits32)
206+
<?> "Invalid Base32 part"
207+
208+
P.char '-'
209+
<?> "Expecting dash (path name separator)"
210+
211+
c0 <- P.satisfy (\c -> c /= '.' && validStorePathNameChar c)
212+
<?> "Leading path name character is a dot or invalid character"
213+
214+
rest <- P.takeWhile validStorePathNameChar
215+
<?> "Path name contains invalid character"
216+
217+
let name = makeStorePathName
218+
$ T.pack . BC.unpack
219+
$ BC.cons c0 rest
220+
221+
either fail return
222+
$ StorePath <$> digest <*> name <*> pure expectedRoot

hnix-store-core/src/System/Nix/StorePath.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module System.Nix.StorePath
1818
, storePathToFilePath
1919
, storePathToNarInfo
2020
, parsePath
21+
, pathParser
2122
) where
2223

2324
import System.Nix.Internal.StorePath

hnix-store-core/tests/StorePath.hs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,27 @@
44
{-# LANGUAGE TypeApplications #-}
55

66
module StorePath where
7-
import qualified Data.ByteString.Char8 as BSC
7+
8+
import qualified Data.ByteString.Char8 as BSC
9+
import qualified Data.Attoparsec.ByteString.Char8 as P
810

911
import Test.Tasty.QuickCheck
1012

1113
import System.Nix.Internal.StorePath
1214
import Arbitrary
1315

1416
-- | Test that Nix(OS) like paths roundtrip
15-
prop_storePathRoundtrip (x' :: NixLike) = \(NixLike x) ->
17+
prop_storePathRoundtrip (_ :: NixLike) = \(NixLike x) ->
1618
(parsePath "/nix/store" $ storePathToRawFilePath x) === Right x
1719

1820
-- | Test that any `StorePath` roundtrips
1921
prop_storePathRoundtrip' x =
2022
(parsePath (storePathRoot x) $ storePathToRawFilePath x) === Right x
23+
24+
prop_storePathRoundtripParser (_ :: NixLike) = \(NixLike x) ->
25+
(P.parseOnly (pathParser (storePathRoot x))
26+
$ storePathToRawFilePath x) === Right x
27+
28+
prop_storePathRoundtripParser' x =
29+
(P.parseOnly (pathParser (storePathRoot x))
30+
$ storePathToRawFilePath x) === Right x

0 commit comments

Comments
 (0)