packages feed

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 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)