-- | Common utilities.
module Network.HTTP.Media.Utils
  ( breakChar,
    trimBS,
    mediaChars,
    isMediaChar,
    tokenChars,
    isTokenChar,
    isValidToken,
  )
where

import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as BS
import Data.Char (isControl)

-- | Equivalent to 'Data.ByteString.break' (on equality against the given
-- character), but leaves out the byte that the string is broken on.
breakChar :: Char -> ByteString -> Maybe (ByteString, ByteString)
breakChar :: Char -> ByteString -> Maybe (ByteString, ByteString)
breakChar Char
c = forall {a}. (a, ByteString) -> Maybe (a, ByteString)
safeTail forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ByteString -> (ByteString, ByteString)
BS.break (forall a. Eq a => a -> a -> Bool
== Char
c)
  where
    safeTail :: (a, ByteString) -> Maybe (a, ByteString)
safeTail (a
a, ByteString
b)
      | ByteString -> Bool
BS.null ByteString
b = forall a. Maybe a
Nothing
      | Bool
otherwise = forall a. a -> Maybe a
Just (a
a, HasCallStack => ByteString -> ByteString
BS.tail ByteString
b)

-- | Trims tab and space characters from both ends of a ByteString.
trimBS :: ByteString -> ByteString
trimBS :: ByteString -> ByteString
trimBS = forall a b. (a, b) -> a
fst forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ByteString -> (ByteString, ByteString)
BS.spanEnd Char -> Bool
isLWS forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ByteString -> ByteString
BS.dropWhile Char -> Bool
isLWS
  where
    isLWS :: Char -> Bool
isLWS Char
c = Char
c forall a. Eq a => a -> a -> Bool
== Char
' ' Bool -> Bool -> Bool
|| Char
c forall a. Eq a => a -> a -> Bool
== Char
'\t'

-- | List of the valid characters for a media-type `reg-name` as per RFC 4288.
mediaChars :: [Char]
mediaChars :: [Char]
mediaChars = [Char
'A' .. Char
'Z'] forall a. [a] -> [a] -> [a]
++ [Char
'a' .. Char
'z'] forall a. [a] -> [a] -> [a]
++ [Char
'0' .. Char
'9'] forall a. [a] -> [a] -> [a]
++ [Char]
"!#$&.+-^_"

-- | Evaluates whether the given character is valid in a media type `reg-name`
-- as per RFC 4288.
isMediaChar :: Char -> Bool
isMediaChar :: Char -> Bool
isMediaChar = (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char]
mediaChars)

-- | Evaluates whether the given character is valid in an HTTP header token as
-- per RFC 2616.
isTokenChar :: Char -> Bool
isTokenChar :: Char -> Bool
isTokenChar = Bool -> Bool -> Bool
(||) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isControl forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Char]
separators)
  where
    separators :: [Char]
separators =
      [ Char
'(',
        Char
')',
        Char
'<',
        Char
'>',
        Char
'@',
        Char
',',
        Char
';',
        Char
':',
        Char
'\\',
        Char
'"',
        Char
'/',
        Char
'[',
        Char
']',
        Char
'?',
        Char
'=',
        Char
'{',
        Char
'}',
        Char
' '
      ]

-- | HTTP header token characters as per RFC 2616.
tokenChars :: [Char]
tokenChars :: [Char]
tokenChars = forall a. (a -> Bool) -> [a] -> [a]
filter Char -> Bool
isTokenChar [Char
'\0' .. Char
'\127']

-- | Evaluates whether the given ASCII string is valid as an HTTP header token
-- as per RFC 2616.
isValidToken :: ByteString -> Bool
isValidToken :: ByteString -> Bool
isValidToken = Bool -> Bool -> Bool
(&&) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Bool
BS.null forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Char -> Bool) -> ByteString -> Bool
BS.all Char -> Bool
isTokenChar