{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Security.Advisories.Generate.HTML
( renderAdvisoriesIndex,
)
where
import Control.Monad (forM_)
import Data.List (sortOn)
import Data.List.Extra (groupSort)
import qualified Data.Map.Strict as Map
import Data.Ord (Down (..))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Text.Lazy as TL
import Data.Time (ZonedTime, zonedTimeToUTC)
import Data.Time.Format.ISO8601
import System.Directory (createDirectoryIfMissing)
import System.Exit (exitFailure)
import System.FilePath ((</>))
import System.IO (hPrint, stderr)
import Distribution.Pretty (prettyShow)
import Lucid
import Safe (maximumMay)
import qualified Text.Atom.Feed as Feed
import qualified Text.Atom.Feed.Export as FeedExport
import Validation (Validation (..))
import qualified Security.Advisories as Advisories
import Security.Advisories.Filesystem (listAdvisories)
renderAdvisoriesIndex :: FilePath -> FilePath -> IO ()
renderAdvisoriesIndex :: FilePath -> FilePath -> IO ()
renderAdvisoriesIndex FilePath
src FilePath
dst = do
[Advisory]
advisories <-
FilePath -> IO (Validation [ParseAdvisoryError] [Advisory])
forall (m :: * -> *).
MonadIO m =>
FilePath -> m (Validation [ParseAdvisoryError] [Advisory])
listAdvisories FilePath
src IO (Validation [ParseAdvisoryError] [Advisory])
-> (Validation [ParseAdvisoryError] [Advisory] -> IO [Advisory])
-> IO [Advisory]
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Failure [ParseAdvisoryError]
errors -> do
Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
"Cannot parse some advisories"
[ParseAdvisoryError] -> (ParseAdvisoryError -> IO ()) -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [ParseAdvisoryError]
errors ((ParseAdvisoryError -> IO ()) -> IO ())
-> (ParseAdvisoryError -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$
Handle -> ParseAdvisoryError -> IO ()
forall a. Show a => Handle -> a -> IO ()
hPrint Handle
stderr
IO [Advisory]
forall a. IO a
exitFailure
Success [Advisory]
advisories ->
[Advisory] -> IO [Advisory]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [Advisory]
advisories
let renderHTMLToFile :: FilePath -> Html a -> IO ()
renderHTMLToFile FilePath
path Html a
content = do
FilePath -> IO ()
putStrLn (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath
"Rendering " FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
path
FilePath -> Html a -> IO ()
forall a. FilePath -> Html a -> IO ()
renderToFile FilePath
path Html a
content
Bool -> FilePath -> IO ()
createDirectoryIfMissing Bool
False FilePath
dst
let indexAdvisories :: [AdvisoryR]
indexAdvisories = (Advisory -> AdvisoryR) -> [Advisory] -> [AdvisoryR]
forall a b. (a -> b) -> [a] -> [b]
map Advisory -> AdvisoryR
toAdvisoryR [Advisory]
advisories
FilePath -> HtmlT Identity () -> IO ()
forall a. FilePath -> Html a -> IO ()
renderHTMLToFile (FilePath
dst FilePath -> FilePath -> FilePath
</> FilePath
"by-dates.html") (HtmlT Identity () -> IO ()) -> HtmlT Identity () -> IO ()
forall a b. (a -> b) -> a -> b
$ [AdvisoryR] -> HtmlT Identity ()
listByDates [AdvisoryR]
indexAdvisories
FilePath -> HtmlT Identity () -> IO ()
forall a. FilePath -> Html a -> IO ()
renderHTMLToFile (FilePath
dst FilePath -> FilePath -> FilePath
</> FilePath
"by-packages.html") (HtmlT Identity () -> IO ()) -> HtmlT Identity () -> IO ()
forall a b. (a -> b) -> a -> b
$ [AdvisoryR] -> HtmlT Identity ()
listByPackages [AdvisoryR]
indexAdvisories
let advisoriesDir :: FilePath
advisoriesDir = FilePath
dst FilePath -> FilePath -> FilePath
</> FilePath
"advisory"
Bool -> FilePath -> IO ()
createDirectoryIfMissing Bool
False FilePath
advisoriesDir
[Advisory] -> (Advisory -> IO ()) -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Advisory]
advisories ((Advisory -> IO ()) -> IO ()) -> (Advisory -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \Advisory
advisory ->
FilePath -> HtmlT Identity () -> IO ()
forall a. FilePath -> Html a -> IO ()
renderHTMLToFile (FilePath
advisoriesDir FilePath -> FilePath -> FilePath
</> HsecId -> FilePath
advisoryHtmlFilename (Advisory -> HsecId
Advisories.advisoryId Advisory
advisory)) (HtmlT Identity () -> IO ()) -> HtmlT Identity () -> IO ()
forall a b. (a -> b) -> a -> b
$
NavigationPage -> HtmlT Identity () -> HtmlT Identity ()
inPage NavigationPage
PageAdvisory (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"pure-u-1"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtmlRaw (Advisory -> Text
Advisories.advisoryHtml Advisory
advisory)
FilePath -> IO ()
putStrLn (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath
"Rendering " FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> (FilePath
dst FilePath -> FilePath -> FilePath
</> FilePath
"atom.xml")
FilePath -> FilePath -> IO ()
writeFile (FilePath
dst FilePath -> FilePath -> FilePath
</> FilePath
"atom.xml") (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ Text -> FilePath
T.unpack (Text -> FilePath) -> Text -> FilePath
forall a b. (a -> b) -> a -> b
$ [Advisory] -> Text
renderFeed [Advisory]
advisories
data AdvisoryR = AdvisoryR
{ AdvisoryR -> HsecId
advisoryId :: Advisories.HsecId,
AdvisoryR -> Text
advisorySummary :: Text,
AdvisoryR -> [AffectedPackageR]
advisoryAffected :: [AffectedPackageR],
AdvisoryR -> ZonedTime
advisoryModified :: ZonedTime
}
deriving stock (Int -> AdvisoryR -> FilePath -> FilePath
[AdvisoryR] -> FilePath -> FilePath
AdvisoryR -> FilePath
(Int -> AdvisoryR -> FilePath -> FilePath)
-> (AdvisoryR -> FilePath)
-> ([AdvisoryR] -> FilePath -> FilePath)
-> Show AdvisoryR
forall a.
(Int -> a -> FilePath -> FilePath)
-> (a -> FilePath) -> ([a] -> FilePath -> FilePath) -> Show a
$cshowsPrec :: Int -> AdvisoryR -> FilePath -> FilePath
showsPrec :: Int -> AdvisoryR -> FilePath -> FilePath
$cshow :: AdvisoryR -> FilePath
show :: AdvisoryR -> FilePath
$cshowList :: [AdvisoryR] -> FilePath -> FilePath
showList :: [AdvisoryR] -> FilePath -> FilePath
Show)
data AffectedPackageR = AffectedPackageR
{ AffectedPackageR -> Text
packageName :: Text,
AffectedPackageR -> Text
introduced :: Text,
AffectedPackageR -> Maybe Text
fixed :: Maybe Text
}
deriving stock (AffectedPackageR -> AffectedPackageR -> Bool
(AffectedPackageR -> AffectedPackageR -> Bool)
-> (AffectedPackageR -> AffectedPackageR -> Bool)
-> Eq AffectedPackageR
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AffectedPackageR -> AffectedPackageR -> Bool
== :: AffectedPackageR -> AffectedPackageR -> Bool
$c/= :: AffectedPackageR -> AffectedPackageR -> Bool
/= :: AffectedPackageR -> AffectedPackageR -> Bool
Eq, Int -> AffectedPackageR -> FilePath -> FilePath
[AffectedPackageR] -> FilePath -> FilePath
AffectedPackageR -> FilePath
(Int -> AffectedPackageR -> FilePath -> FilePath)
-> (AffectedPackageR -> FilePath)
-> ([AffectedPackageR] -> FilePath -> FilePath)
-> Show AffectedPackageR
forall a.
(Int -> a -> FilePath -> FilePath)
-> (a -> FilePath) -> ([a] -> FilePath -> FilePath) -> Show a
$cshowsPrec :: Int -> AffectedPackageR -> FilePath -> FilePath
showsPrec :: Int -> AffectedPackageR -> FilePath -> FilePath
$cshow :: AffectedPackageR -> FilePath
show :: AffectedPackageR -> FilePath
$cshowList :: [AffectedPackageR] -> FilePath -> FilePath
showList :: [AffectedPackageR] -> FilePath -> FilePath
Show)
listByDates :: [AdvisoryR] -> Html ()
listByDates :: [AdvisoryR] -> HtmlT Identity ()
listByDates [AdvisoryR]
advisories =
NavigationPage -> HtmlT Identity () -> HtmlT Identity ()
inPage NavigationPage
PageListByDates (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"pure-u-1"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"advisories"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
table_ [Text -> Attribute
class_ Text
"pure-table pure-table-horizontal"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
thead_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ HtmlT Identity ()
"#"
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ HtmlT Identity ()
"Package(s)"
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ HtmlT Identity ()
"Summary"
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tbody_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
let sortedAdvisories :: [(AdvisoryR, [Attribute])]
sortedAdvisories =
[AdvisoryR] -> [[Attribute]] -> [(AdvisoryR, [Attribute])]
forall a b. [a] -> [b] -> [(a, b)]
zip
((AdvisoryR -> Down HsecId) -> [AdvisoryR] -> [AdvisoryR]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (HsecId -> Down HsecId
forall a. a -> Down a
Down (HsecId -> Down HsecId)
-> (AdvisoryR -> HsecId) -> AdvisoryR -> Down HsecId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AdvisoryR -> HsecId
advisoryId) [AdvisoryR]
advisories)
([[Attribute]] -> [[Attribute]]
forall a. HasCallStack => [a] -> [a]
cycle [[], [Text -> Attribute
class_ Text
"pure-table-odd"]])
[(AdvisoryR, [Attribute])]
-> ((AdvisoryR, [Attribute]) -> HtmlT Identity ())
-> HtmlT Identity ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [(AdvisoryR, [Attribute])]
sortedAdvisories (((AdvisoryR, [Attribute]) -> HtmlT Identity ())
-> HtmlT Identity ())
-> ((AdvisoryR, [Attribute]) -> HtmlT Identity ())
-> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ \(AdvisoryR
advisory, [Attribute]
trClasses) ->
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ [Attribute]
trClasses (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ Text
"advisory-id"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ [Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
a_ [Text -> Attribute
href_ (Text -> Attribute) -> Text -> Attribute
forall a b. (a -> b) -> a -> b
$ HsecId -> Text
advisoryLink (AdvisoryR -> HsecId
advisoryId AdvisoryR
advisory)] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ FilePath -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => FilePath -> HtmlT m ()
toHtml (HsecId -> FilePath
Advisories.printHsecId (AdvisoryR -> HsecId
advisoryId AdvisoryR
advisory))
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ Text
"advisory-packages"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml (Text -> HtmlT Identity ()) -> Text -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> [Text] -> Text
T.intercalate Text
"," ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ AffectedPackageR -> Text
packageName (AffectedPackageR -> Text) -> [AffectedPackageR] -> [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> AdvisoryR -> [AffectedPackageR]
advisoryAffected AdvisoryR
advisory
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ Text
"advisory-summary"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml (Text -> HtmlT Identity ()) -> Text -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ AdvisoryR -> Text
advisorySummary AdvisoryR
advisory
listByPackages :: [AdvisoryR] -> Html ()
listByPackages :: [AdvisoryR] -> HtmlT Identity ()
listByPackages [AdvisoryR]
advisories =
NavigationPage -> HtmlT Identity () -> HtmlT Identity ()
inPage NavigationPage
PageListByPackages (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"pure-u-1"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
let byPackage :: Map.Map Text [(AdvisoryR, AffectedPackageR)]
byPackage :: Map Text [(AdvisoryR, AffectedPackageR)]
byPackage =
[(Text, [(AdvisoryR, AffectedPackageR)])]
-> Map Text [(AdvisoryR, AffectedPackageR)]
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(Text, [(AdvisoryR, AffectedPackageR)])]
-> Map Text [(AdvisoryR, AffectedPackageR)])
-> [(Text, [(AdvisoryR, AffectedPackageR)])]
-> Map Text [(AdvisoryR, AffectedPackageR)]
forall a b. (a -> b) -> a -> b
$
[(Text, (AdvisoryR, AffectedPackageR))]
-> [(Text, [(AdvisoryR, AffectedPackageR)])]
forall k v. Ord k => [(k, v)] -> [(k, [v])]
groupSort
[ (AffectedPackageR -> Text
packageName AffectedPackageR
package, (AdvisoryR
advisory, AffectedPackageR
package))
| AdvisoryR
advisory <- [AdvisoryR]
advisories,
AffectedPackageR
package <- AdvisoryR -> [AffectedPackageR]
advisoryAffected AdvisoryR
advisory
]
[(Text, [(AdvisoryR, AffectedPackageR)])]
-> ((Text, [(AdvisoryR, AffectedPackageR)]) -> HtmlT Identity ())
-> HtmlT Identity ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Map Text [(AdvisoryR, AffectedPackageR)]
-> [(Text, [(AdvisoryR, AffectedPackageR)])]
forall k a. Map k a -> [(k, a)]
Map.toList Map Text [(AdvisoryR, AffectedPackageR)]
byPackage) (((Text, [(AdvisoryR, AffectedPackageR)]) -> HtmlT Identity ())
-> HtmlT Identity ())
-> ((Text, [(AdvisoryR, AffectedPackageR)]) -> HtmlT Identity ())
-> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ \(Text
currentPackageName, [(AdvisoryR, AffectedPackageR)]
perPackageAdvisory) -> do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
h2_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml Text
currentPackageName
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"advisories"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
table_ [Text -> Attribute
class_ Text
"pure-table pure-table-horizontal"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
thead_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ HtmlT Identity ()
"#"
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ HtmlT Identity ()
"Introduced"
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ HtmlT Identity ()
"Fixed"
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
th_ HtmlT Identity ()
"Summary"
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tbody_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
let sortedAdvisories :: [((AdvisoryR, AffectedPackageR), [Attribute])]
sortedAdvisories =
[(AdvisoryR, AffectedPackageR)]
-> [[Attribute]] -> [((AdvisoryR, AffectedPackageR), [Attribute])]
forall a b. [a] -> [b] -> [(a, b)]
zip
(((AdvisoryR, AffectedPackageR) -> Down HsecId)
-> [(AdvisoryR, AffectedPackageR)]
-> [(AdvisoryR, AffectedPackageR)]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (HsecId -> Down HsecId
forall a. a -> Down a
Down (HsecId -> Down HsecId)
-> ((AdvisoryR, AffectedPackageR) -> HsecId)
-> (AdvisoryR, AffectedPackageR)
-> Down HsecId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. AdvisoryR -> HsecId
advisoryId (AdvisoryR -> HsecId)
-> ((AdvisoryR, AffectedPackageR) -> AdvisoryR)
-> (AdvisoryR, AffectedPackageR)
-> HsecId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (AdvisoryR, AffectedPackageR) -> AdvisoryR
forall a b. (a, b) -> a
fst) [(AdvisoryR, AffectedPackageR)]
perPackageAdvisory)
([[Attribute]] -> [[Attribute]]
forall a. HasCallStack => [a] -> [a]
cycle [[], [Text -> Attribute
class_ Text
"pure-table-odd"]])
[((AdvisoryR, AffectedPackageR), [Attribute])]
-> (((AdvisoryR, AffectedPackageR), [Attribute])
-> HtmlT Identity ())
-> HtmlT Identity ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [((AdvisoryR, AffectedPackageR), [Attribute])]
sortedAdvisories ((((AdvisoryR, AffectedPackageR), [Attribute])
-> HtmlT Identity ())
-> HtmlT Identity ())
-> (((AdvisoryR, AffectedPackageR), [Attribute])
-> HtmlT Identity ())
-> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ \((AdvisoryR
advisory, AffectedPackageR
package), [Attribute]
trClasses) ->
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
tr_ [Attribute]
trClasses (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ Text
"advisory-id"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ [Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
a_ [Text -> Attribute
href_ (Text -> Attribute) -> Text -> Attribute
forall a b. (a -> b) -> a -> b
$ HsecId -> Text
advisoryLink (HsecId -> Text) -> HsecId -> Text
forall a b. (a -> b) -> a -> b
$ AdvisoryR -> HsecId
advisoryId AdvisoryR
advisory] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ FilePath -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => FilePath -> HtmlT m ()
toHtml (HsecId -> FilePath
Advisories.printHsecId (HsecId -> FilePath) -> HsecId -> FilePath
forall a b. (a -> b) -> a -> b
$ AdvisoryR -> HsecId
advisoryId AdvisoryR
advisory)
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ Text
"advisory-introduced"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml (Text -> HtmlT Identity ()) -> Text -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ AffectedPackageR -> Text
introduced AffectedPackageR
package
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ Text
"advisory-fixed"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ HtmlT Identity ()
-> (Text -> HtmlT Identity ()) -> Maybe Text -> HtmlT Identity ()
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (() -> HtmlT Identity ()
forall a. a -> HtmlT Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return ()) Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml (Maybe Text -> HtmlT Identity ())
-> Maybe Text -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ AffectedPackageR -> Maybe Text
fixed AffectedPackageR
package
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
td_ [Text -> Attribute
class_ Text
"advisory-summary"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ Text -> HtmlT Identity ()
forall a (m :: * -> *). (ToHtml a, Monad m) => a -> HtmlT m ()
forall (m :: * -> *). Monad m => Text -> HtmlT m ()
toHtml (Text -> HtmlT Identity ()) -> Text -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ AdvisoryR -> Text
advisorySummary AdvisoryR
advisory
data NavigationPage
= PageListByDates
| PageListByPackages
| PageAdvisory
deriving stock (NavigationPage -> NavigationPage -> Bool
(NavigationPage -> NavigationPage -> Bool)
-> (NavigationPage -> NavigationPage -> Bool) -> Eq NavigationPage
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: NavigationPage -> NavigationPage -> Bool
== :: NavigationPage -> NavigationPage -> Bool
$c/= :: NavigationPage -> NavigationPage -> Bool
/= :: NavigationPage -> NavigationPage -> Bool
Eq, Int -> NavigationPage -> FilePath -> FilePath
[NavigationPage] -> FilePath -> FilePath
NavigationPage -> FilePath
(Int -> NavigationPage -> FilePath -> FilePath)
-> (NavigationPage -> FilePath)
-> ([NavigationPage] -> FilePath -> FilePath)
-> Show NavigationPage
forall a.
(Int -> a -> FilePath -> FilePath)
-> (a -> FilePath) -> ([a] -> FilePath -> FilePath) -> Show a
$cshowsPrec :: Int -> NavigationPage -> FilePath -> FilePath
showsPrec :: Int -> NavigationPage -> FilePath -> FilePath
$cshow :: NavigationPage -> FilePath
show :: NavigationPage -> FilePath
$cshowList :: [NavigationPage] -> FilePath -> FilePath
showList :: [NavigationPage] -> FilePath -> FilePath
Show)
baseUrlForPage :: NavigationPage -> Text
baseUrlForPage :: NavigationPage -> Text
baseUrlForPage = \case
NavigationPage
PageListByDates -> Text
"."
NavigationPage
PageListByPackages -> Text
"."
NavigationPage
PageAdvisory -> Text
".."
inPage :: NavigationPage -> Html () -> Html ()
inPage :: NavigationPage -> HtmlT Identity () -> HtmlT Identity ()
inPage NavigationPage
page HtmlT Identity ()
content =
HtmlT Identity () -> HtmlT Identity ()
forall (m :: * -> *) a. Applicative m => HtmlT m a -> HtmlT m a
doctypehtml_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
html_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
head_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity ()
forall (m :: * -> *). Applicative m => [Attribute] -> HtmlT m ()
meta_ [Text -> Attribute
charset_ Text
"UTF-8"]
[Attribute] -> HtmlT Identity ()
forall (m :: * -> *). Applicative m => [Attribute] -> HtmlT m ()
base_ [Text -> Attribute
href_ (Text -> Attribute) -> Text -> Attribute
forall a b. (a -> b) -> a -> b
$ NavigationPage -> Text
baseUrlForPage NavigationPage
page]
[Attribute] -> HtmlT Identity ()
forall (m :: * -> *). Applicative m => [Attribute] -> HtmlT m ()
link_ [Text -> Attribute
rel_ Text
"alternate", Text -> Attribute
type_ Text
"application/atom+xml", Text -> Attribute
href_ Text
atomFeedUrl]
[Attribute] -> HtmlT Identity ()
forall (m :: * -> *). Applicative m => [Attribute] -> HtmlT m ()
link_ [Text -> Attribute
rel_ Text
"stylesheet", Text -> Attribute
href_ Text
"https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css", Text -> Attribute
integrity_ Text
"sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls", Text -> Attribute
crossorigin_ Text
"anonymous"]
[Attribute] -> HtmlT Identity ()
forall (m :: * -> *). Applicative m => [Attribute] -> HtmlT m ()
meta_ [Text -> Attribute
name_ Text
"viewport", Text -> Attribute
content_ Text
"width=device-width, initial-scale=1"]
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
title_ HtmlT Identity ()
"Haskell Security.Advisories.Core"
Text -> HtmlT Identity ()
forall arg result. TermRaw arg result => arg -> result
style_ (Text -> HtmlT Identity ()) -> Text -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
Text -> [Text] -> Text
T.intercalate
Text
"\n"
[ Text
".advisories, .content {",
Text
" margin: 1em;",
Text
"}",
Text
"a {",
Text
" text-decoration: none;",
Text
"}",
Text
"a:visited {",
Text
" text-decoration: none;",
Text
" color: darkblue;",
Text
"}",
Text
"pre {",
Text
" background: lightgrey;",
Text
"}"
]
HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
body_ (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"pure-u-1"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"pure-menu pure-menu-horizontal"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
let selectedOn :: NavigationPage -> a -> a
selectedOn NavigationPage
p a
cls =
if NavigationPage
page NavigationPage -> NavigationPage -> Bool
forall a. Eq a => a -> a -> Bool
== NavigationPage
p
then a
cls a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
" pure-menu-selected"
else a
cls
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
span_ [Text -> Attribute
class_ Text
"pure-menu-heading pure-menu-link"] HtmlT Identity ()
"Advisories list"
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
ul_ [Text -> Attribute
class_ Text
"pure-menu-list"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$ do
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
li_ [Text -> Attribute
class_ (Text -> Attribute) -> Text -> Attribute
forall a b. (a -> b) -> a -> b
$ NavigationPage -> Text -> Text
forall {a}. (Semigroup a, IsString a) => NavigationPage -> a -> a
selectedOn NavigationPage
PageListByDates Text
"pure-menu-item"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
a_ [Text -> Attribute
href_ Text
"by-dates.html", Text -> Attribute
class_ Text
"pure-menu-link"] HtmlT Identity ()
"by date"
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
li_ [Text -> Attribute
class_ (Text -> Attribute) -> Text -> Attribute
forall a b. (a -> b) -> a -> b
$ NavigationPage -> Text -> Text
forall {a}. (Semigroup a, IsString a) => NavigationPage -> a -> a
selectedOn NavigationPage
PageListByPackages Text
"pure-menu-item"] (HtmlT Identity () -> HtmlT Identity ())
-> HtmlT Identity () -> HtmlT Identity ()
forall a b. (a -> b) -> a -> b
$
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
a_ [Text -> Attribute
href_ Text
"by-packages.html", Text -> Attribute
class_ Text
"pure-menu-link"] HtmlT Identity ()
"by package"
[Attribute] -> HtmlT Identity () -> HtmlT Identity ()
forall arg result. Term arg result => arg -> result
div_ [Text -> Attribute
class_ Text
"content"] HtmlT Identity ()
content
advisoryHtmlFilename :: Advisories.HsecId -> FilePath
advisoryHtmlFilename :: HsecId -> FilePath
advisoryHtmlFilename HsecId
advisoryId' = HsecId -> FilePath
Advisories.printHsecId HsecId
advisoryId' FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
".html"
advisoryLink :: Advisories.HsecId -> Text
advisoryLink :: HsecId -> Text
advisoryLink HsecId
advisoryId' = Text
"advisory/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Text
T.pack (HsecId -> FilePath
advisoryHtmlFilename HsecId
advisoryId')
toAdvisoryR :: Advisories.Advisory -> AdvisoryR
toAdvisoryR :: Advisory -> AdvisoryR
toAdvisoryR Advisory
x =
AdvisoryR
{ advisoryId :: HsecId
advisoryId = Advisory -> HsecId
Advisories.advisoryId Advisory
x,
advisorySummary :: Text
advisorySummary = Advisory -> Text
Advisories.advisorySummary Advisory
x,
advisoryAffected :: [AffectedPackageR]
advisoryAffected = (Affected -> [AffectedPackageR])
-> [Affected] -> [AffectedPackageR]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Affected -> [AffectedPackageR]
toAffectedPackageR ([Affected] -> [AffectedPackageR])
-> [Affected] -> [AffectedPackageR]
forall a b. (a -> b) -> a -> b
$ Advisory -> [Affected]
Advisories.advisoryAffected Advisory
x,
advisoryModified :: ZonedTime
advisoryModified = Advisory -> ZonedTime
Advisories.advisoryModified Advisory
x
}
where
toAffectedPackageR :: Advisories.Affected -> [AffectedPackageR]
toAffectedPackageR :: Affected -> [AffectedPackageR]
toAffectedPackageR Affected
p =
((AffectedVersionRange -> AffectedPackageR)
-> [AffectedVersionRange] -> [AffectedPackageR])
-> [AffectedVersionRange]
-> (AffectedVersionRange -> AffectedPackageR)
-> [AffectedPackageR]
forall a b c. (a -> b -> c) -> b -> a -> c
flip (AffectedVersionRange -> AffectedPackageR)
-> [AffectedVersionRange] -> [AffectedPackageR]
forall a b. (a -> b) -> [a] -> [b]
map (Affected -> [AffectedVersionRange]
Advisories.affectedVersions Affected
p) ((AffectedVersionRange -> AffectedPackageR) -> [AffectedPackageR])
-> (AffectedVersionRange -> AffectedPackageR) -> [AffectedPackageR]
forall a b. (a -> b) -> a -> b
$ \AffectedVersionRange
versionRange ->
AffectedPackageR
{ packageName :: Text
packageName = Affected -> Text
Advisories.affectedPackage Affected
p,
introduced :: Text
introduced = FilePath -> Text
T.pack (FilePath -> Text) -> FilePath -> Text
forall a b. (a -> b) -> a -> b
$ Version -> FilePath
forall a. Pretty a => a -> FilePath
prettyShow (Version -> FilePath) -> Version -> FilePath
forall a b. (a -> b) -> a -> b
$ AffectedVersionRange -> Version
Advisories.affectedVersionRangeIntroduced AffectedVersionRange
versionRange,
fixed :: Maybe Text
fixed = FilePath -> Text
T.pack (FilePath -> Text) -> (Version -> FilePath) -> Version -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Version -> FilePath
forall a. Pretty a => a -> FilePath
prettyShow (Version -> Text) -> Maybe Version -> Maybe Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> AffectedVersionRange -> Maybe Version
Advisories.affectedVersionRangeFixed AffectedVersionRange
versionRange
}
feed :: [Advisories.Advisory] -> Feed.Feed
feed :: [Advisory] -> Feed
feed [Advisory]
advisories =
( Text -> TextContent -> Text -> Feed
Feed.nullFeed
Text
atomFeedUrl
(Text -> TextContent
Feed.TextString Text
"Haskell Security Advisory DB")
(Text -> (UTCTime -> Text) -> Maybe UTCTime -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"" (FilePath -> Text
T.pack (FilePath -> Text) -> (UTCTime -> FilePath) -> UTCTime -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> FilePath
forall t. ISO8601 t => t -> FilePath
iso8601Show) (Maybe UTCTime -> Text)
-> ([Advisory] -> Maybe UTCTime) -> [Advisory] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [UTCTime] -> Maybe UTCTime
forall a. Ord a => [a] -> Maybe a
maximumMay ([UTCTime] -> Maybe UTCTime)
-> ([Advisory] -> [UTCTime]) -> [Advisory] -> Maybe UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Advisory -> UTCTime) -> [Advisory] -> [UTCTime]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (ZonedTime -> UTCTime
zonedTimeToUTC (ZonedTime -> UTCTime)
-> (Advisory -> ZonedTime) -> Advisory -> UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Advisory -> ZonedTime
Advisories.advisoryModified) ([Advisory] -> Text) -> [Advisory] -> Text
forall a b. (a -> b) -> a -> b
$ [Advisory]
advisories)
)
{ Feed.feedEntries = fmap toEntry advisories
, Feed.feedLinks = [(Feed.nullLink atomFeedUrl) { Feed.linkRel = Just (Left "self") }]
, Feed.feedAuthors = [Feed.nullPerson { Feed.personName = "Haskell Security Response Team" }]
}
where
toEntry :: Advisory -> Entry
toEntry Advisory
advisory =
( Text -> TextContent -> Text -> Entry
Feed.nullEntry
(Advisory -> Text
toUrl Advisory
advisory)
(Advisory -> TextContent
mkSummary Advisory
advisory)
(FilePath -> Text
T.pack (FilePath -> Text) -> (ZonedTime -> FilePath) -> ZonedTime -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ZonedTime -> FilePath
forall t. ISO8601 t => t -> FilePath
iso8601Show (ZonedTime -> Text) -> ZonedTime -> Text
forall a b. (a -> b) -> a -> b
$ Advisory -> ZonedTime
Advisories.advisoryModified Advisory
advisory)
)
{ Feed.entryLinks = [(Feed.nullLink (toUrl advisory)) { Feed.linkRel = Just (Left "alternate") }]
, Feed.entryContent = Just (Feed.HTMLContent (Advisories.advisoryHtml advisory))
}
mkSummary :: Advisory -> TextContent
mkSummary Advisory
advisory =
Text -> TextContent
Feed.TextString (Text -> TextContent) -> Text -> TextContent
forall a b. (a -> b) -> a -> b
$
FilePath -> Text
T.pack (HsecId -> FilePath
Advisories.printHsecId (Advisory -> HsecId
Advisories.advisoryId Advisory
advisory))
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" - "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Advisory -> Text
Advisories.advisorySummary Advisory
advisory
toUrl :: Advisory -> Text
toUrl Advisory
advisory = Text
advisoriesRootUrl Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> HsecId -> Text
advisoryLink (Advisory -> HsecId
Advisories.advisoryId Advisory
advisory)
renderFeed :: [Advisories.Advisory] -> Text
renderFeed :: [Advisory] -> Text
renderFeed =
Text -> (Text -> Text) -> Maybe Text -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (FilePath -> Text
forall a. HasCallStack => FilePath -> a
error FilePath
"Cannot render atom feed") Text -> Text
TL.toStrict
(Maybe Text -> Text)
-> ([Advisory] -> Maybe Text) -> [Advisory] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Feed -> Maybe Text
FeedExport.textFeed
(Feed -> Maybe Text)
-> ([Advisory] -> Feed) -> [Advisory] -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Advisory] -> Feed
feed
advisoriesRootUrl :: T.Text
advisoriesRootUrl :: Text
advisoriesRootUrl = Text
"https://haskell.github.io/security-advisories"
atomFeedUrl :: T.Text
atomFeedUrl :: Text
atomFeedUrl = Text
advisoriesRootUrl Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/atom.xml"