module Ecluse.Core.Server.Conditional (
ETag,
mkStrongETag,
renderETag,
etagHeader,
Conditional (..),
evaluateETag,
forwardValidators,
isNotModified,
) where
import Crypto.Hash (Digest, SHA256)
import Data.ByteArray.Encoding (Base (Base16), convertToBase)
import Data.Text qualified as T
import Network.HTTP.Types (Header, RequestHeaders, Status, statusCode)
import Network.HTTP.Types.Header (hETag, hIfModifiedSince, hIfNoneMatch)
newtype ETag = ETag Text
deriving stock (ETag -> ETag -> Bool
(ETag -> ETag -> Bool) -> (ETag -> ETag -> Bool) -> Eq ETag
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ETag -> ETag -> Bool
== :: ETag -> ETag -> Bool
$c/= :: ETag -> ETag -> Bool
/= :: ETag -> ETag -> Bool
Eq, Eq ETag
Eq ETag =>
(ETag -> ETag -> Ordering)
-> (ETag -> ETag -> Bool)
-> (ETag -> ETag -> Bool)
-> (ETag -> ETag -> Bool)
-> (ETag -> ETag -> Bool)
-> (ETag -> ETag -> ETag)
-> (ETag -> ETag -> ETag)
-> Ord ETag
ETag -> ETag -> Bool
ETag -> ETag -> Ordering
ETag -> ETag -> ETag
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: ETag -> ETag -> Ordering
compare :: ETag -> ETag -> Ordering
$c< :: ETag -> ETag -> Bool
< :: ETag -> ETag -> Bool
$c<= :: ETag -> ETag -> Bool
<= :: ETag -> ETag -> Bool
$c> :: ETag -> ETag -> Bool
> :: ETag -> ETag -> Bool
$c>= :: ETag -> ETag -> Bool
>= :: ETag -> ETag -> Bool
$cmax :: ETag -> ETag -> ETag
max :: ETag -> ETag -> ETag
$cmin :: ETag -> ETag -> ETag
min :: ETag -> ETag -> ETag
Ord, Int -> ETag -> ShowS
[ETag] -> ShowS
ETag -> String
(Int -> ETag -> ShowS)
-> (ETag -> String) -> ([ETag] -> ShowS) -> Show ETag
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ETag -> ShowS
showsPrec :: Int -> ETag -> ShowS
$cshow :: ETag -> String
show :: ETag -> String
$cshowList :: [ETag] -> ShowS
showList :: [ETag] -> ShowS
Show)
mkStrongETag :: Digest SHA256 -> ETag
mkStrongETag :: Digest SHA256 -> ETag
mkStrongETag Digest SHA256
digest = Text -> ETag
ETag (Text
"\"" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
hex Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\"")
where
hex :: Text
hex :: Text
hex = ByteString -> Text
forall a b. ConvertUtf8 a b => b -> a
decodeUtf8 (Base -> Digest SHA256 -> ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
Base -> bin -> bout
convertToBase Base
Base16 Digest SHA256
digest :: ByteString)
renderETag :: ETag -> Text
renderETag :: ETag -> Text
renderETag (ETag Text
t) = Text
t
etagHeader :: ETag -> Header
ETag
etag = (HeaderName
hETag, Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 (ETag -> Text
renderETag ETag
etag))
data Conditional
=
NotModified ETag
|
Modified ETag
deriving stock (Conditional -> Conditional -> Bool
(Conditional -> Conditional -> Bool)
-> (Conditional -> Conditional -> Bool) -> Eq Conditional
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Conditional -> Conditional -> Bool
== :: Conditional -> Conditional -> Bool
$c/= :: Conditional -> Conditional -> Bool
/= :: Conditional -> Conditional -> Bool
Eq, Int -> Conditional -> ShowS
[Conditional] -> ShowS
Conditional -> String
(Int -> Conditional -> ShowS)
-> (Conditional -> String)
-> ([Conditional] -> ShowS)
-> Show Conditional
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Conditional -> ShowS
showsPrec :: Int -> Conditional -> ShowS
$cshow :: Conditional -> String
show :: Conditional -> String
$cshowList :: [Conditional] -> ShowS
showList :: [Conditional] -> ShowS
Show)
evaluateETag :: RequestHeaders -> ETag -> Conditional
evaluateETag :: RequestHeaders -> ETag -> Conditional
evaluateETag RequestHeaders
headers ETag
etag
| Bool
matches = ETag -> Conditional
NotModified ETag
etag
| Bool
otherwise = ETag -> Conditional
Modified ETag
etag
where
matches :: Bool
matches :: Bool
matches = (ByteString -> Bool) -> [ByteString] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ByteString -> Bool
clientMatches (HeaderName -> RequestHeaders -> [ByteString]
forall a b. Eq a => a -> [(a, b)] -> [b]
lookupAll HeaderName
hIfNoneMatch RequestHeaders
headers)
clientMatches :: ByteString -> Bool
clientMatches :: ByteString -> Bool
clientMatches ByteString
raw =
let value :: Text
value = Text -> Text
T.strip (ByteString -> Text
forall a b. ConvertUtf8 a b => b -> a
decodeUtf8 ByteString
raw)
in Text
value Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"*"
Bool -> Bool -> Bool
|| Text
ours Text -> [Text] -> Bool
forall (f :: * -> *) a.
(Foldable f, DisallowElem f, Eq a) =>
a -> f a -> Bool
`elem` (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
normaliseTag (Text -> [Text]
splitTags Text
value)
ours :: Text
ours :: Text
ours = Text -> Text
normaliseTag (ETag -> Text
renderETag ETag
etag)
splitTags :: Text -> [Text]
splitTags :: Text -> [Text]
splitTags = (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Text -> Bool) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null) ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
T.strip ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HasCallStack => Text -> Text -> [Text]
Text -> Text -> [Text]
T.splitOn Text
","
normaliseTag :: Text -> Text
normaliseTag :: Text -> Text
normaliseTag Text
t = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
t (Text -> Text -> Maybe Text
T.stripPrefix Text
"W/" Text
t)
forwardValidators :: RequestHeaders -> RequestHeaders
forwardValidators :: RequestHeaders -> RequestHeaders
forwardValidators = (Header -> Bool) -> RequestHeaders -> RequestHeaders
forall a. (a -> Bool) -> [a] -> [a]
filter (HeaderName -> Bool
isValidator (HeaderName -> Bool) -> (Header -> HeaderName) -> Header -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Header -> HeaderName
forall a b. (a, b) -> a
fst)
where
isValidator :: HeaderName -> Bool
isValidator HeaderName
name = HeaderName
name HeaderName -> HeaderName -> Bool
forall a. Eq a => a -> a -> Bool
== HeaderName
hIfNoneMatch Bool -> Bool -> Bool
|| HeaderName
name HeaderName -> HeaderName -> Bool
forall a. Eq a => a -> a -> Bool
== HeaderName
hIfModifiedSince
isNotModified :: Status -> Bool
isNotModified :: Status -> Bool
isNotModified Status
s = Status -> Int
statusCode Status
s Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
304
lookupAll :: (Eq a) => a -> [(a, b)] -> [b]
lookupAll :: forall a b. Eq a => a -> [(a, b)] -> [b]
lookupAll a
name = ((a, b) -> b) -> [(a, b)] -> [b]
forall a b. (a -> b) -> [a] -> [b]
map (a, b) -> b
forall a b. (a, b) -> b
snd ([(a, b)] -> [b]) -> ([(a, b)] -> [(a, b)]) -> [(a, b)] -> [b]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((a, b) -> Bool) -> [(a, b)] -> [(a, b)]
forall a. (a -> Bool) -> [a] -> [a]
filter ((a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
name) (a -> Bool) -> ((a, b) -> a) -> (a, b) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a, b) -> a
forall a b. (a, b) -> a
fst)