hsec-core 0.3.0.1 → 0.4.0.0
raw patch · 5 files changed
+359/−233 lines, 5 filesdep +containersPVP ok
version bump matches the API change (PVP)
Dependencies added: containers
API changes (from Hackage documentation)
+ Security.Advisories.Core.OsvId: data OsvId
+ Security.Advisories.Core.OsvId: instance GHC.Classes.Eq Security.Advisories.Core.OsvId.OsvId
+ Security.Advisories.Core.OsvId: instance GHC.Classes.Ord Security.Advisories.Core.OsvId.OsvId
+ Security.Advisories.Core.OsvId: instance GHC.Show.Show Security.Advisories.Core.OsvId.OsvId
+ Security.Advisories.Core.OsvId: osvIdPrefix :: OsvId -> Text
+ Security.Advisories.Core.OsvId: parseOsvId :: Text -> Maybe OsvId
+ Security.Advisories.Core.OsvId: printOsvId :: OsvId -> Text
- Security.Advisories.Core.Advisory: Advisory :: HsecId -> UTCTime -> UTCTime -> [CAPEC] -> [CWE] -> [Keyword] -> [Text] -> [Text] -> [Affected] -> [Reference] -> Pandoc -> Text -> Text -> Text -> Advisory
+ Security.Advisories.Core.Advisory: Advisory :: HsecId -> UTCTime -> UTCTime -> [CAPEC] -> [CWE] -> [Keyword] -> [OsvId] -> [OsvId] -> [Affected] -> [Reference] -> Pandoc -> Text -> Text -> Text -> Advisory
- Security.Advisories.Core.Advisory: [advisoryAliases] :: Advisory -> [Text]
+ Security.Advisories.Core.Advisory: [advisoryAliases] :: Advisory -> [OsvId]
- Security.Advisories.Core.Advisory: [advisoryRelated] :: Advisory -> [Text]
+ Security.Advisories.Core.Advisory: [advisoryRelated] :: Advisory -> [OsvId]
Files
- CHANGELOG.md +4/−0
- hsec-core.cabal +26/−15
- src/Security/Advisories/Core/Advisory.hs +63/−59
- src/Security/Advisories/Core/OsvId.hs +106/−0
- test/Spec/QueriesSpec.hs +160/−159
CHANGELOG.md view
@@ -1,3 +1,7 @@+## 0.4.0.0++* Introduce `OsvId` and use it in `Advisory.{aliases,related}`+ ## 0.3.0.2 * Update `cvss` dependency bounds
hsec-core.cabal view
@@ -1,37 +1,48 @@-cabal-version: 2.4-name: hsec-core-version: 0.3.0.1+cabal-version: 2.4+name: hsec-core+version: 0.4.0.0 -- A short (one-line) description of the package.-synopsis: Core package representing Haskell advisories+synopsis: Core package representing Haskell advisories -- A longer description of the package.-description: Core package representing Haskell advisories.+description: Core package representing Haskell advisories. -- A URL where users can report bugs. -- bug-reports: -- The license under which the package is released.-license: BSD-3-Clause-author: Haskell Security Response Team-maintainer: security-advisories@haskell.org+license: BSD-3-Clause+author: Haskell Security Response Team+maintainer: security-advisories@haskell.org -- A copyright notice. -- copyright:-category: Data-extra-doc-files: CHANGELOG.md, README.md+category: Data+extra-doc-files:+ CHANGELOG.md+ README.md tested-with:- GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.6 || ==9.8.3 || ==9.10.1 || ==9.12.1+ GHC ==8.10.7+ || ==9.0.2+ || ==9.2.8+ || ==9.4.8+ || ==9.6.6+ || ==9.8.3+ || ==9.10.1+ || ==9.12.1 library exposed-modules: Security.Advisories.Core.Advisory Security.Advisories.Core.HsecId+ Security.Advisories.Core.OsvId build-depends: , base >=4.14 && <5 , Cabal-syntax >=3.8.1.0 && <3.15+ , containers >=0.6 && <0.8 , cvss >=0.2 && <0.4 , network-uri >=2.6.3.0 && <2.8 , osv >=0.1 && <0.3@@ -39,6 +50,7 @@ , safe >=0.3 && <0.4 , text >=1.2 && <3 , time >=1.9 && <1.15+ hs-source-dirs: src default-language: Haskell2010 ghc-options:@@ -49,15 +61,14 @@ type: exitcode-stdio-1.0 hs-source-dirs: test main-is: Spec.hs- other-modules:- Spec.QueriesSpec+ other-modules: Spec.QueriesSpec build-depends: , base , Cabal-syntax , cvss , hsec-core- , tasty <2- , tasty-hunit <0.11+ , tasty <2+ , tasty-hunit <0.11 , text default-language: Haskell2010
src/Security/Advisories/Core/Advisory.hs view
@@ -1,30 +1,34 @@-{-# LANGUAGE DerivingVia, OverloadedStrings, QuasiQuotes #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-} module Security.Advisories.Core.Advisory- ( Advisory(..)+ ( Advisory (..),+ -- * Supporting types- , Affected(..)- , CAPEC(..)- , CWE(..)- , Architecture(..)- , AffectedVersionRange(..)- , OS(..)- , Keyword(..)- , ComponentIdentifier(..)- , GHCComponent(..)- , RepositoryURL(..)- , RepositoryName(..)- , PackageName- , mkPackageName- , unPackageName- , ghcComponentToText- , ghcComponentFromText- , hackage+ Affected (..),+ CAPEC (..),+ CWE (..),+ Architecture (..),+ AffectedVersionRange (..),+ OS (..),+ Keyword (..),+ ComponentIdentifier (..),+ GHCComponent (..),+ RepositoryURL (..),+ RepositoryName (..),+ PackageName,+ mkPackageName,+ unPackageName,+ ghcComponentToText,+ ghcComponentFromText,+ hackage,+ -- * Queries- , isVersionAffectedBy- , isVersionRangeAffectedBy+ isVersionAffectedBy,+ isVersionRangeAffectedBy, )- where+where import Data.Text (Text) import Data.Time (UTCTime)@@ -34,30 +38,30 @@ import Distribution.Types.VersionRange (VersionRange, anyVersion, earlierVersion, intersectVersionRanges, noVersion, orLaterVersion, unionVersionRanges, withinRange) import Network.URI (URI) import Network.URI.Static (uri)--import Text.Pandoc.Definition (Pandoc)- import Security.Advisories.Core.HsecId (HsecId)+import Security.Advisories.Core.OsvId (OsvId) import qualified Security.CVSS as CVSS import Security.OSV (Reference)+import Text.Pandoc.Definition (Pandoc) data Advisory = Advisory- { advisoryId :: HsecId- , advisoryModified :: UTCTime- , advisoryPublished :: UTCTime- , advisoryCAPECs :: [CAPEC]- , advisoryCWEs :: [CWE]- , advisoryKeywords :: [Keyword]- , advisoryAliases :: [Text]- , advisoryRelated :: [Text]- , advisoryAffected :: [Affected]- , advisoryReferences :: [Reference]- , advisoryPandoc :: Pandoc -- ^ Parsed document, without TOML front matter- , advisoryHtml :: Text- , advisorySummary :: Text- -- ^ A one-line, English textual summary of the vulnerability- , advisoryDetails :: Text- -- ^ Details of the vulnerability (CommonMark), without TOML front matter+ { advisoryId :: HsecId,+ advisoryModified :: UTCTime,+ advisoryPublished :: UTCTime,+ advisoryCAPECs :: [CAPEC],+ advisoryCWEs :: [CWE],+ advisoryKeywords :: [Keyword],+ advisoryAliases :: [OsvId],+ advisoryRelated :: [OsvId],+ advisoryAffected :: [Affected],+ advisoryReferences :: [Reference],+ -- | Parsed document, without TOML front matter+ advisoryPandoc :: Pandoc,+ advisoryHtml :: Text,+ -- | A one-line, English textual summary of the vulnerability+ advisorySummary :: Text,+ -- | Details of the vulnerability (CommonMark), without TOML front matter+ advisoryDetails :: Text } deriving stock (Show) @@ -73,11 +77,11 @@ (RepositoryName "hackage") newtype RepositoryURL- = RepositoryURL { unRepositoryURL :: URI }+ = RepositoryURL {unRepositoryURL :: URI} deriving stock (Eq, Ord, Show) newtype RepositoryName- = RepositoryName { unRepositoryName :: Text }+ = RepositoryName {unRepositoryName :: Text} deriving stock (Eq, Ord, Show) -- Keep this list in sync with the 'ghcComponentFromText' below@@ -114,12 +118,12 @@ -- | An affected package (or package component). An 'Advisory' must -- mention one or more packages. data Affected = Affected- { affectedComponentIdentifier :: ComponentIdentifier- , affectedCVSS :: CVSS.CVSS- , affectedVersions :: [AffectedVersionRange]- , affectedArchitectures :: Maybe [Architecture]- , affectedOS :: Maybe [OS]- , affectedDeclarations :: [(Text, VersionRange)]+ { affectedComponentIdentifier :: ComponentIdentifier,+ affectedCVSS :: CVSS.CVSS,+ affectedVersions :: [AffectedVersionRange],+ affectedArchitectures :: Maybe [Architecture],+ affectedOS :: Maybe [OS],+ affectedDeclarations :: [(Text, VersionRange)] } deriving stock (Eq, Show) @@ -201,17 +205,17 @@ -- | Helper function for 'isVersionAffectedBy' and 'isVersionRangeAffectedBy' isAffectedByHelper :: (a -> VersionRange -> Bool) -> ComponentIdentifier -> a -> Advisory -> Bool isAffectedByHelper checkWithRange queryComponent queryVersionish =- any checkAffected . advisoryAffected- where- checkAffected :: Affected -> Bool- checkAffected affected =- affectedComponentIdentifier affected == queryComponent && checkWithRange queryVersionish (fromAffected affected)+ any checkAffected . advisoryAffected+ where+ checkAffected :: Affected -> Bool+ checkAffected affected =+ affectedComponentIdentifier affected == queryComponent && checkWithRange queryVersionish (fromAffected affected) - fromAffected :: Affected -> VersionRange- fromAffected = foldr (unionVersionRanges . fromAffectedVersionRange) noVersion . affectedVersions+ fromAffected :: Affected -> VersionRange+ fromAffected = foldr (unionVersionRanges . fromAffectedVersionRange) noVersion . affectedVersions - fromAffectedVersionRange :: AffectedVersionRange -> VersionRange- fromAffectedVersionRange avr = intersectVersionRanges+ fromAffectedVersionRange :: AffectedVersionRange -> VersionRange+ fromAffectedVersionRange avr =+ intersectVersionRanges (orLaterVersion (affectedVersionRangeIntroduced avr)) (maybe anyVersion earlierVersion (affectedVersionRangeFixed avr))-
+ src/Security/Advisories/Core/OsvId.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE OverloadedStrings #-}++module Security.Advisories.Core.OsvId+ ( OsvId,+ parseOsvId,+ printOsvId,+ osvIdPrefix,+ )+where++import qualified Data.List as L+import Data.Maybe (listToMaybe)+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as T++newtype OsvId = OsvId Text+ deriving (Eq, Ord, Show)++knownPrefixes :: Set.Set Text+knownPrefixes =+ Set.fromList+ [ "ASB-A",+ "PUB-A",+ "ALSA",+ "ALBA",+ "ALEA",+ "ALPINE",+ "BELL",+ "BIT",+ "CGA",+ "CleanStart",+ "CURL",+ "CVE",+ "DEBIAN",+ "DHI",+ "DRUPAL",+ "DSA",+ "DLA",+ "DTSA",+ "ECHO",+ "EEF",+ "ELA",+ "GHSA",+ "GO",+ "GSD",+ "HSEC",+ "JLSEC",+ "KUBE",+ "LBSEC",+ "LSN",+ "MGASA",+ "MAL",+ "MINI",+ "OESA",+ "OSEC",+ "OSV",+ "PHSA",+ "PSF",+ "PYSEC",+ "RHSA",+ "RHBA",+ "RHEA",+ "RLSA",+ "RXSA",+ "RSEC",+ "ROOT",+ "RUSTSEC",+ "SUSE-SU",+ "SUSE-RU",+ "SUSE-FU",+ "SUSE-OU",+ "openSUSE-SU",+ "UBUNTU",+ "USN",+ "V8"+ ]++sortedPrefixes :: [Text]+sortedPrefixes = L.reverse . L.sortOn T.length $ Set.toList knownPrefixes++parseOsvId :: Text -> Maybe OsvId+parseOsvId t = do+ prefix <- findPrefix t+ let rest = T.drop (T.length prefix + 1) t+ guard (not (T.null rest))+ pure (OsvId t)+ where+ findPrefix txt =+ listToMaybe $+ filter (\p -> T.isPrefixOf (p <> "-") txt) sortedPrefixes++ guard True = Just ()+ guard False = Nothing++printOsvId :: OsvId -> Text+printOsvId (OsvId t) = t++osvIdPrefix :: OsvId -> Text+osvIdPrefix (OsvId t) = case findPrefix t of+ Just p -> p+ Nothing -> T.takeWhile (/= '-') t+ where+ findPrefix txt =+ listToMaybe $+ filter (\p -> T.isPrefixOf (p <> "-") txt) sortedPrefixes
test/Spec/QueriesSpec.hs view
@@ -6,188 +6,189 @@ import Data.Bifunctor (first) import Data.Either (fromRight) import Data.Maybe (fromMaybe)+import qualified Data.Maybe as Maybe import Data.Text (Text) import qualified Data.Text as T import Distribution.Parsec (eitherParsec)-import Distribution.Types.Version (version0, alterVersion)-import Distribution.Types.VersionRange (VersionRange, VersionRangeF(..), anyVersion, projectVersionRange)-import Test.Tasty-import Test.Tasty.HUnit--import Security.CVSS (parseCVSS)+import Distribution.Types.Version (alterVersion, version0)+import Distribution.Types.VersionRange (VersionRange, VersionRangeF (..), anyVersion, projectVersionRange) import Security.Advisories.Core.Advisory import Security.Advisories.Core.HsecId+import Security.Advisories.Core.OsvId (parseOsvId)+import Security.CVSS (parseCVSS)+import Test.Tasty+import Test.Tasty.HUnit spec :: TestTree spec =- testGroup "Queries" [- testGroup "isAffectedBy" $- flip concatMap cases $ map $ \(actual, query, expected) ->- let title x y =- if expected- then show x <> " is vulnerable to " <> show y- else show x <> " is not vulnerable to " <> show y- versionRange x =- either (\e -> error $ "Cannot parse version range " <> show x <> " : " <> show e) id $- parseVersionRange $- if x == ""- then Nothing- else Just x- in testCase (title actual query) $- let query' = versionRange query- affectedVersion' = versionRange actual- in isVersionRangeAffectedBy component query' (mkAdvisory affectedVersion')- @?= expected- ]+ testGroup+ "Queries"+ [ testGroup "isAffectedBy" $+ flip concatMap cases $+ map $ \(actual, query, expected) ->+ let title x y =+ if expected+ then show x <> " is vulnerable to " <> show y+ else show x <> " is not vulnerable to " <> show y+ versionRange x =+ either (\e -> error $ "Cannot parse version range " <> show x <> " : " <> show e) id $+ parseVersionRange $+ if x == ""+ then Nothing+ else Just x+ in testCase (title actual query) $+ let query' = versionRange query+ affectedVersion' = versionRange actual+ in isVersionRangeAffectedBy component query' (mkAdvisory affectedVersion')+ @?= expected+ ] cases :: [[(Text, Text, Bool)]] cases =- [- reversible ("", "", True)- , reversible ("", "==1", True)- , reversible ("==1.1", "<=2", True)- , reversible ("==1.1", ">1", True)- , reversible ("==1", "==1", True)- , reversible ("==2||==1", "==1", True)- , reversible ("==1.1", "<=2&&>1", True)- , reversible ("^>=1", ">1&&<1.2", True)- , reversible (">=1", "==2", True)- , reversible (">=1", "==1", True)- , reversible ("==2", ">=2", True)- , reversible ("==2", ">1", True)- , reversible (">5", ">=2", True)- , reversible (">5", ">2", True)- , reversible ("==5", ">=2", True)- , reversible ("==5", ">2", True)- , reversible (">=5", ">2", True)- , reversible ("<=5", ">2", True)- , reversible ("<=5", "<=2", True)- , reversible ("<5", ">=2", True)- , reversible (">=2", "==5", True)- , reversible (">2", "==5", True)- , reversible (">5", ">=5", True)- , reversible ("^>=1.1", ">1", True)- , reversible ("^>=1.1", "<2", True)- , reversible ("^>=1.1", "<=1.2", True)- , reversible ("^>=1.1", ">1.1", True)- , reversible ("^>=1.1", ">=1.1.5", True)- , reversible ("^>=1.1", ">=1", True)- , reversible ("==1.1", "<1", False)- , reversible ("==2.1", "<=2", False)- , reversible ("==1", ">1.1", False)- , reversible ("==2", "==1", False)- , reversible ("==2||==1", ">3", False)- , notReversible ("<=2&&>1", "==3", True)- , reversible (">=2", "==1.1", False)- , reversible (">=1.1", "==1", False)- , reversible ("==2", ">=2.1", False)- , notReversible (">1", "==1", False)- , reversible ("<2", ">=2", False)- , reversible ("==2", ">=5", False)- , reversible ("==2", ">5", False)- , reversible ("<=2", ">5", False)- , reversible ("<=2", ">5", False)- , reversible ("<2", ">=5", False)- , reversible (">=2", "==1.1", False)- , reversible ("<2", "==5", False)- , reversible ("<5", ">=5", False)- , reversible ("^>=1.1", "<1", False)- , reversible ("^>=1.1", "<1.1", False)- , reversible ("^>=1.1", ">=1.2", False)- , reversible ("^>=1.1", "<=1", False)- , reversible ("^>=1.1", ">2", False)- , reversible ("^>=1", ">=2", False)+ [ reversible ("", "", True),+ reversible ("", "==1", True),+ reversible ("==1.1", "<=2", True),+ reversible ("==1.1", ">1", True),+ reversible ("==1", "==1", True),+ reversible ("==2||==1", "==1", True),+ reversible ("==1.1", "<=2&&>1", True),+ reversible ("^>=1", ">1&&<1.2", True),+ reversible (">=1", "==2", True),+ reversible (">=1", "==1", True),+ reversible ("==2", ">=2", True),+ reversible ("==2", ">1", True),+ reversible (">5", ">=2", True),+ reversible (">5", ">2", True),+ reversible ("==5", ">=2", True),+ reversible ("==5", ">2", True),+ reversible (">=5", ">2", True),+ reversible ("<=5", ">2", True),+ reversible ("<=5", "<=2", True),+ reversible ("<5", ">=2", True),+ reversible (">=2", "==5", True),+ reversible (">2", "==5", True),+ reversible (">5", ">=5", True),+ reversible ("^>=1.1", ">1", True),+ reversible ("^>=1.1", "<2", True),+ reversible ("^>=1.1", "<=1.2", True),+ reversible ("^>=1.1", ">1.1", True),+ reversible ("^>=1.1", ">=1.1.5", True),+ reversible ("^>=1.1", ">=1", True),+ reversible ("==1.1", "<1", False),+ reversible ("==2.1", "<=2", False),+ reversible ("==1", ">1.1", False),+ reversible ("==2", "==1", False),+ reversible ("==2||==1", ">3", False),+ notReversible ("<=2&&>1", "==3", True),+ reversible (">=2", "==1.1", False),+ reversible (">=1.1", "==1", False),+ reversible ("==2", ">=2.1", False),+ notReversible (">1", "==1", False),+ reversible ("<2", ">=2", False),+ reversible ("==2", ">=5", False),+ reversible ("==2", ">5", False),+ reversible ("<=2", ">5", False),+ reversible ("<=2", ">5", False),+ reversible ("<2", ">=5", False),+ reversible (">=2", "==1.1", False),+ reversible ("<2", "==5", False),+ reversible ("<5", ">=5", False),+ reversible ("^>=1.1", "<1", False),+ reversible ("^>=1.1", "<1.1", False),+ reversible ("^>=1.1", ">=1.2", False),+ reversible ("^>=1.1", "<=1", False),+ reversible ("^>=1.1", ">2", False),+ reversible ("^>=1", ">=2", False) ]- where reversible (query, affectedVersion, expected) = [(query, affectedVersion, expected), (query, affectedVersion, expected)]- notReversible (query, affectedVersion, expected) = [(query, affectedVersion, expected), (affectedVersion, query, not expected)]+ where+ reversible (query, affectedVersion, expected) = [(query, affectedVersion, expected), (query, affectedVersion, expected)]+ notReversible (query, affectedVersion, expected) = [(query, affectedVersion, expected), (affectedVersion, query, not expected)] mkAdvisory :: VersionRange -> Advisory mkAdvisory versionRange =- Advisory- { advisoryId = fromMaybe (error "Cannot mkHsecId") $ mkHsecId 2023 42- , advisoryModified = read "2023-01-01T00:00:00"- , advisoryPublished = read "2023-01-01T00:00:00"- , advisoryCAPECs = []- , advisoryCWEs = []- , advisoryKeywords = []- , advisoryAliases = [ "CVE-2022-XXXX" ]- , advisoryRelated = [ "CVE-2022-YYYY" , "CVE-2022-ZZZZ" ]- , advisoryAffected =- [ Affected- { affectedComponentIdentifier = component- , affectedCVSS = cvss- , affectedVersions = mkAffectedVersions versionRange- , affectedArchitectures = Nothing- , affectedOS = Nothing- , affectedDeclarations = []- }- ]- , advisoryReferences = []- , advisoryPandoc = mempty- , advisoryHtml = ""- , advisorySummary = ""- , advisoryDetails = ""- }+ Advisory+ { advisoryId = fromMaybe (error "Cannot mkHsecId") $ mkHsecId 2023 42,+ advisoryModified = read "2023-01-01T00:00:00",+ advisoryPublished = read "2023-01-01T00:00:00",+ advisoryCAPECs = [],+ advisoryCWEs = [],+ advisoryKeywords = [],+ advisoryAliases = Maybe.fromJust . traverse parseOsvId $ ["CVE-2022-1234"],+ advisoryRelated = Maybe.fromJust . traverse parseOsvId $ ["CVE-2022-5678", "CVE-2022-9012"],+ advisoryAffected =+ [ Affected+ { affectedComponentIdentifier = component,+ affectedCVSS = cvss,+ affectedVersions = mkAffectedVersions versionRange,+ affectedArchitectures = Nothing,+ affectedOS = Nothing,+ affectedDeclarations = []+ }+ ],+ advisoryReferences = [],+ advisoryPandoc = mempty,+ advisoryHtml = "",+ advisorySummary = "",+ advisoryDetails = ""+ } where cvss = fromRight (error "Cannot parseCVSS") (parseCVSS "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H") mkAffectedVersions :: VersionRange -> [AffectedVersionRange] mkAffectedVersions vr =- let- fixed from to =- AffectedVersionRange- { affectedVersionRangeIntroduced = from- , affectedVersionRangeFixed = Just to- }- onlyFixed to =- AffectedVersionRange- { affectedVersionRangeIntroduced = version0- , affectedVersionRangeFixed = Just to- }- vulnerable from =- AffectedVersionRange- { affectedVersionRangeIntroduced = from- , affectedVersionRangeFixed = Nothing- }- nextMinor =- \case- [] -> [1]- [x] -> [x, 1]- [x, y] -> [x, y, 1]- [x, y, z] -> [x, y, z, 1]- [w, x, y, z] -> [w, x, y, z + 1]- xs -> xs ++ [1]- previousMinor =- \case- [] -> [0]- [x] -> [x - 1 , 99]- [x, y] -> [x, y - 1, 99]- [x, y, z] -> [x, y, z - 1, 99]- [w, x, y, z] -> [w, x, y, z - 1]- _ -> error "TODO"- mkMajorBoundVersion =- \case- [] -> [0]- [x] -> [x, 1]- (x:y:_) -> [x, y + 1]- in- case projectVersionRange vr of- ThisVersionF x -> [fixed x $ alterVersion (<> [0,0,1]) x]- LaterVersionF x -> [vulnerable $ alterVersion nextMinor x]- OrLaterVersionF x -> [vulnerable x]- EarlierVersionF x -> [onlyFixed $ alterVersion previousMinor x]- OrEarlierVersionF x -> [onlyFixed x]- MajorBoundVersionF x -> [fixed x $ alterVersion mkMajorBoundVersion x]- UnionVersionRangesF x y -> mkAffectedVersions x <> mkAffectedVersions y- IntersectVersionRangesF x y ->- [ low { affectedVersionRangeFixed = affectedVersionRangeFixed high }- | low <- mkAffectedVersions x- , high <- mkAffectedVersions y- ]+ let fixed from to =+ AffectedVersionRange+ { affectedVersionRangeIntroduced = from,+ affectedVersionRangeFixed = Just to+ }+ onlyFixed to =+ AffectedVersionRange+ { affectedVersionRangeIntroduced = version0,+ affectedVersionRangeFixed = Just to+ }+ vulnerable from =+ AffectedVersionRange+ { affectedVersionRangeIntroduced = from,+ affectedVersionRangeFixed = Nothing+ }+ nextMinor =+ \case+ [] -> [1]+ [x] -> [x, 1]+ [x, y] -> [x, y, 1]+ [x, y, z] -> [x, y, z, 1]+ [w, x, y, z] -> [w, x, y, z + 1]+ xs -> xs ++ [1]+ previousMinor =+ \case+ [] -> [0]+ [x] -> [x - 1, 99]+ [x, y] -> [x, y - 1, 99]+ [x, y, z] -> [x, y, z - 1, 99]+ [w, x, y, z] -> [w, x, y, z - 1]+ _ -> error "TODO"+ mkMajorBoundVersion =+ \case+ [] -> [0]+ [x] -> [x, 1]+ (x : y : _) -> [x, y + 1]+ in case projectVersionRange vr of+ ThisVersionF x -> [fixed x $ alterVersion (<> [0, 0, 1]) x]+ LaterVersionF x -> [vulnerable $ alterVersion nextMinor x]+ OrLaterVersionF x -> [vulnerable x]+ EarlierVersionF x -> [onlyFixed $ alterVersion previousMinor x]+ OrEarlierVersionF x -> [onlyFixed x]+ MajorBoundVersionF x -> [fixed x $ alterVersion mkMajorBoundVersion x]+ UnionVersionRangesF x y -> mkAffectedVersions x <> mkAffectedVersions y+ IntersectVersionRangesF x y ->+ [ low {affectedVersionRangeFixed = affectedVersionRangeFixed high}+ | low <- mkAffectedVersions x,+ high <- mkAffectedVersions y+ ] component :: ComponentIdentifier component = hackage $ mkPackageName "package-name" -- | Parse 'VersionRange' as given to the CLI parseVersionRange :: Maybe Text -> Either Text VersionRange-parseVersionRange = maybe (return anyVersion) (first T.pack . eitherParsec . T.unpack)+parseVersionRange = maybe (return anyVersion) (first T.pack . eitherParsec . T.unpack)