graphql-engine/server/src-lib/Data/Parser/CacheControl.hs
Robert bdacf1bd23 server: remove ApplicativeDo from default extensions
I spent half the day reducing a weird compile failure here https://github.com/hasura/graphql-engine-mono/pull/1593/files#r713102990 to this https://gitlab.haskell.org/ghc/ghc/-/issues/17768#note_378004. Seems ApplicativeDo makes a mess of non-applicative monadic do in some cases. Given our rather localized use of ApplicativeDo, seemed a good idea to remove it from the list of default extensions.

It appears that ApplicativeDo also buries some unused return value warnings, so this PR also silences those. We should check that none of those warnings were warranted though.

https://github.com/hasura/graphql-engine-mono/pull/2413

GitOrigin-RevId: 1874c1a82230431849265755b1407beebc947041
2021-09-22 15:35:54 +00:00

169 lines
5.8 KiB
Haskell

{-| Functions related to parsing @Cache-Control@ header as defined in
https://tools.ietf.org/html/rfc7234#section-5.2
To get @max-age@/@s-maxage@ from @Cache-Control@ header, use 'parseMaxAge'. If you need to check
other directives use 'parseCacheControl'.
Rules which starts with @obs-@ is not required to implement because they are maked as "obsolete" as
per https://tools.ietf.org/html/rfc7230#section-1.2
-}
module Data.Parser.CacheControl
( CacheControl
, CacheControlDirective (..)
, parseCacheControl
, parseMaxAge
)
where
import Hasura.Prelude
import Hasura.Server.Utils (fmapL)
import qualified Data.Attoparsec.Text as AT
import qualified Data.Text as T
type CacheControl = [CacheControlDirective]
data CacheControlDirective
= CCDOnlyToken !Text
| CCDTokenWithVal !Text !Text
deriving (Show, Eq)
-- | Tries to parse the @max-age@ or @s-maxage@ present in the value of @Cache-Control@ header
parseMaxAge :: Integral a => Text -> Either String a
parseMaxAge t = do
cc <- parseCacheControl t
case find checkMaxAgeToken cc of
Nothing -> Left parseErr
Just d -> case d of
CCDOnlyToken _ -> Left parseErr
CCDTokenWithVal _ val -> fmapL (const parseErr) $ AT.parseOnly AT.decimal val
where
parseErr = "could not find max-age/s-maxage"
checkMaxAgeToken = \case
CCDOnlyToken token -> token == "max-age" || token == "s-maxage"
CCDTokenWithVal token _ -> token == "max-age" || token == "s-maxage"
-- | Parses a @Cache-Control@ header and returns a list of directives
parseCacheControl :: Text -> Either String CacheControl
parseCacheControl = AT.parseOnly cacheControlParser
-- ABNF: cache-control = *( "," OWS) cache-directive *( OWS "," [OWS cache-directive])
-- https://tools.ietf.org/html/rfc7234#appendix-C
cacheControlParser :: AT.Parser CacheControl
cacheControlParser = do
void $ AT.many' ("," *> optionalWhitespaceParser)
cd <- cacheDirectiveParser
cds <- AT.many' $ optionalWhitespaceParser *> "," *> AT.option Nothing (pure <$> optionalDirective)
return $ cd : catMaybes cds
where
optionalDirective = optionalWhitespaceParser *> cacheDirectiveParser
-- ABNF: OWS = *( SP / HTAB ) ; optional whitespace
-- https://tools.ietf.org/html/rfc7230#section-3.2.3
optionalWhitespaceParser :: AT.Parser (Maybe Char)
optionalWhitespaceParser = AT.option Nothing (pure <$> AT.space)
-- ABNF: cache-directive = token [ "=" ( token / quoted-string ) ]
-- https://tools.ietf.org/html/rfc7230#section-3.2.6
cacheDirectiveParser :: AT.Parser CacheControlDirective
cacheDirectiveParser = tokenWithValue <|> onlyToken
where
onlyToken = CCDOnlyToken <$> tokenParser
tokenWithValue = do
tok <- tokenParser
void $ AT.char '='
val <- tokenParser <|> quotedStringParser
return $ CCDTokenWithVal tok val
-- ABNF: 1*tchar
-- https://tools.ietf.org/html/rfc7230#section-3.2.6
tokenParser :: AT.Parser Text
tokenParser = T.pack <$> AT.many1 tcharParser
-- ABNF: tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|"
-- / "~" / DIGIT / ALPHA ; any VCHAR, except delimiters
-- https://tools.ietf.org/html/rfc7230#section-3.2.6
tcharParser :: AT.Parser Char
tcharParser = AT.char '!'
<|> AT.char '#'
<|> AT.char '$'
<|> AT.char '%'
<|> AT.char '&'
<|> AT.char '\''
<|> AT.char '*'
<|> AT.char '+'
<|> AT.char '-'
<|> AT.char '.'
<|> AT.char '^'
<|> AT.char '_'
<|> AT.char '`'
<|> AT.char '|'
<|> AT.char '~'
<|> AT.digit
<|> AT.letter
-- ABNF: DQUOTE = %x22 ; " (Double Quote)
-- https://tools.ietf.org/html/rfc5234#appendix-B.1
dquoteParser :: AT.Parser Char
dquoteParser = AT.char '"'
-- ABNF: VCHAR = %x21-7E ; visible (printing) characters
-- https://tools.ietf.org/html/rfc5234#appendix-B.1
vcharParser :: AT.Parser Char
vcharParser = AT.anyChar
-- ABNF: quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
-- https://tools.ietf.org/html/rfc7230#section-3.2.6
quotedStringParser :: AT.Parser Text
quotedStringParser =
dquoteParser *> fmap T.pack (AT.many' (qdTextParser <|> quotedPairParser)) <* dquoteParser
-- ABNF: quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
-- https://tools.ietf.org/html/rfc7230#section-3.2.6
quotedPairParser :: AT.Parser Char
quotedPairParser =
AT.string "\\" *> (AT.space <|> vcharParser)
-- ABNF: qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
-- https://tools.ietf.org/html/rfc7230#section-3.2.6
qdTextParser :: AT.Parser Char
qdTextParser = AT.space
<|> AT.char '!' -- %x21
-- skip %x22 as it is '"'
<|> AT.char '#' -- %x23
<|> AT.char '$' -- %x24
<|> AT.char '%' -- %x25
<|> AT.char '&' -- %x26
<|> AT.char '\'' -- %x27 single quote
<|> AT.char '(' -- %x28
<|> AT.char ')' -- %x29
<|> AT.char '*' -- %x2A
<|> AT.char '+' -- %x2B
<|> AT.char ',' -- %x2C
<|> AT.char '-' -- %x2D
<|> AT.char '.' -- %x2E
<|> AT.char '/' -- %x2F
<|> AT.digit -- %x30-39
<|> AT.char ':' -- %x3A
<|> AT.char ';' -- %x3B
<|> AT.char '<' -- %x3C
<|> AT.char '=' -- %x3D
<|> AT.char '>' -- %x3E
<|> AT.char '?' -- %x3F
<|> AT.char '@' -- %x40
<|> AT.letter -- %x41-5A / %x61-7A
<|> AT.char '[' -- %x5B
-- skip %x5C as it is '\'
<|> AT.char ']' -- %x5D
<|> AT.char '^' -- %x5E
<|> AT.char '_' -- %x5F
<|> AT.char '`' -- %x60
<|> AT.char '{' -- %x7B
<|> AT.char '|' -- %x7C
<|> AT.char '}' -- %x7D
<|> AT.char '~' -- %x7E