@@ -13,6 +13,7 @@ module Security.Advisories.Filesystem
13
13
(
14
14
dirNameAdvisories
15
15
, dirNameReserved
16
+ , dirNamePublished
16
17
, isSecurityAdvisoriesRepo
17
18
, getReservedIds
18
19
, getAdvisoryIds
@@ -36,12 +37,10 @@ import Data.Semigroup (Max(Max, getMax))
36
37
import Data.Traversable (for )
37
38
38
39
import Control.Monad.IO.Class (MonadIO , liftIO )
39
- import Control.Monad.Writer.Strict (execWriterT , tell )
40
40
import qualified Data.Text as T
41
41
import qualified Data.Text.IO as T
42
- import System.FilePath ((</>) , takeBaseName , splitDirectories )
43
- import System.Directory (doesDirectoryExist , pathIsSymbolicLink )
44
- import System.Directory.PathWalk
42
+ import System.FilePath ((</>) , dropExtension , splitDirectories )
43
+ import System.Directory (doesDirectoryExist , listDirectory )
45
44
import Validation (Validation (.. ))
46
45
47
46
import Security.Advisories (Advisory , AttributeOverridePolicy (NoOverrides ), OutOfBandAttributes (.. ), ParseAdvisoryError , parseAdvisory , ComponentIdentifier (.. ))
@@ -51,13 +50,15 @@ import Control.Monad.Except (runExceptT, ExceptT (ExceptT), withExceptT)
51
50
import Security.Advisories.Parse (OOBError (GitHasNoOOB , PathHasNoComponentIdentifier ))
52
51
import Security.Advisories.Core.Advisory (ghcComponentFromText )
53
52
54
-
55
53
dirNameAdvisories :: FilePath
56
54
dirNameAdvisories = " advisories"
57
55
58
56
dirNameReserved :: FilePath
59
57
dirNameReserved = " reserved"
60
58
59
+ dirNamePublished :: FilePath
60
+ dirNamePublished = " published"
61
+
61
62
-- | Check whether the directory appears to be the root of a
62
63
-- /security-advisories/ filesystem. Only checks that the
63
64
-- @advisories@ subdirectory exists.
@@ -109,7 +110,7 @@ forReserved
109
110
:: (MonadIO m , Monoid r )
110
111
=> FilePath -> (FilePath -> HsecId -> m r ) -> m r
111
112
forReserved root =
112
- _forFiles (root </> dirNameAdvisories </> dirNameReserved)
113
+ _forFilesByYear (root </> dirNameAdvisories </> dirNameReserved)
113
114
114
115
-- | Invoke a callback for each HSEC ID under each of the advisory
115
116
-- subdirectories, excluding the @reserved@ directory. The results
@@ -121,23 +122,17 @@ forReserved root =
121
122
forAdvisory
122
123
:: (MonadIO m , Monoid r )
123
124
=> FilePath -> (FilePath -> HsecId -> m r ) -> m r
124
- forAdvisory root go = do
125
- let dir = root </> dirNameAdvisories
126
- subdirs <- filter (/= dirNameReserved) <$> _getSubdirs dir
127
- fmap fold $ for subdirs $ \ subdir -> _forFiles (dir </> subdir) go
125
+ forAdvisory root =
126
+ _forFilesByYear (root </> dirNameAdvisories </> dirNamePublished)
128
127
129
- -- | List deduplicated parsed Advisories
128
+ -- | List parsed Advisories
130
129
listAdvisories
131
130
:: (MonadIO m )
132
131
=> FilePath -> m (Validation [(FilePath , ParseAdvisoryError )] [Advisory ])
133
132
listAdvisories root =
134
- forAdvisory root $ \ advisoryPath _advisoryId -> do
135
- isSym <- liftIO $ pathIsSymbolicLink advisoryPath
136
- if isSym
137
- then return $ pure []
138
- else
139
- bimap (\ err -> [(advisoryPath, err)]) pure
140
- <$> advisoryFromFile advisoryPath
133
+ forAdvisory root $ \ advisoryPath _advisoryId ->
134
+ bimap (\ err -> [(advisoryPath, err)]) pure
135
+ <$> advisoryFromFile advisoryPath
141
136
142
137
-- | Parse an advisory from a file system path
143
138
advisoryFromFile
@@ -158,28 +153,25 @@ advisoryFromFile advisoryPath = do
158
153
$ either Failure Success
159
154
$ parseAdvisory NoOverrides oob fileContent
160
155
161
- -- | Get names (not paths) of subdirectories of the given directory
162
- -- (one level). There's no monoidal, interruptible variant of
163
- -- @pathWalk@ so we use @WriterT@ to smuggle the result out.
164
- --
165
- _getSubdirs :: (MonadIO m ) => FilePath -> m [FilePath ]
166
- _getSubdirs root =
167
- execWriterT $
168
- pathWalkInterruptible root $ \ _ subdirs _ -> do
169
- tell subdirs
170
- pure Stop
171
-
172
- _forFiles
156
+ _forFilesByYear
173
157
:: (MonadIO m , Monoid r )
174
158
=> FilePath -- ^ (sub)directory name
175
159
-> (FilePath -> HsecId -> m r )
176
160
-> m r
177
- _forFiles root go =
178
- pathWalkAccumulate root $ \ dir _ files ->
179
- fmap fold $ for files $ \ file ->
180
- case parseHsecId (takeBaseName file) of
181
- Nothing -> pure mempty
182
- Just hsid -> go (dir </> file) hsid
161
+ _forFilesByYear root go = do
162
+ yearsFile <- liftIO $ listDirectory root
163
+ fmap (foldMap fold) $
164
+ for yearsFile $ \ year -> do
165
+ let yearDir = root </> year
166
+ isYear <- liftIO $ doesDirectoryExist yearDir
167
+ if isYear
168
+ then do
169
+ files <- liftIO $ listDirectory yearDir
170
+ for files $ \ file ->
171
+ case parseHsecId (" HSEC-" <> year <> " -" <> dropExtension file) of
172
+ Nothing -> pure mempty
173
+ Just hsid -> go (yearDir </> file) hsid
174
+ else pure mempty
183
175
184
176
parseComponentIdentifier :: Monad m => FilePath -> ExceptT OOBError m (Maybe ComponentIdentifier )
185
177
parseComponentIdentifier fp = ExceptT . pure $ case drop 1 $ reverse $ splitDirectories fp of
0 commit comments