{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- |
-- Module : Network.Wai.Middleware.EnforceHTTPS
-- Copyright : (c) Marek Fajkus
-- License : BSD3
--
-- Maintainer : marek.faj@gmail.com
--
-- Parsing and Serialization of [Forwarded](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded)
-- HTTP header values.
module Network.HTTP.Forwarded
( Forwarded(..)
, parseForwarded
, serializeForwarded
) where
import Data.ByteString (ByteString)
import Data.CaseInsensitive (CI)
import Data.Maybe (catMaybes)
import Data.Monoid ((<>))
import Data.Word (Word8)
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif
import qualified Data.ByteString as ByteString
import qualified Data.CaseInsensitive as CaseInsensitive
import qualified Data.Char as Char
-- | Representation of Forwarded header data
-- All field are optional
data Forwarded = Forwarded
{ forwardedBy :: Maybe ByteString
, forwardedFor :: Maybe ByteString
, forwardedHost :: Maybe ByteString
, forwardedProto :: Maybe (CI ByteString)
} deriving (Eq, Show)
empty :: Forwarded
empty = Forwarded
{ forwardedBy = Nothing
, forwardedFor = Nothing
, forwardedHost = Nothing
, forwardedProto = Nothing
}
-- | Parse @ByteString@ to Forwarded header
-- Note that this function works with the values
-- of the header only. Extraction of value
-- from header depends what representation of headers
-- you're using.
--
-- In case of Wai you can extract headers as following:
--
-- > :set -XOverloadedStrings
-- > import Network.Wai
-- > import Network.HTTP.Forwarded
-- > getForwarded req = parseForwarded <$> "forwarded" `lookup` requestHeaders req
-- > :t getForwarded
-- > getForwarded :: Request -> Maybe Forwarded
parseForwarded :: ByteString -> Forwarded
parseForwarded = foldr accumulate empty . parseForwarded'
where
accumulate (key, val) acc =
case key of
"by" -> acc { forwardedBy = Just val }
"for" -> acc { forwardedFor = Just val }
"host" -> acc { forwardedHost = Just val }
"proto" -> acc { forwardedProto = Just $ CaseInsensitive.mk val }
_ -> acc
parseForwarded' :: ByteString -> [ (ByteString, ByteString) ]
parseForwarded' s
| ByteString.null s = []
| otherwise =
let (x, y) = breakDiscard 59 s -- semicolon
in parsePart x : parseForwarded' y
parsePart :: ByteString -> (ByteString, ByteString)
parsePart s = (key', value)
where
(key, value) =
breakDiscard 61 s -- equals sign
key' =
ByteString.dropWhile (== 32) key -- space
breakDiscard :: Word8 -> ByteString -> (ByteString, ByteString)
breakDiscard w s = (x, ByteString.drop 1 y)
where
(x, y) =
ByteString.break (== w) s
-- | Serialize `Forwarded` data type back
-- to ByteString representation.
serializeForwarded :: Forwarded -> ByteString
serializeForwarded Forwarded { .. } =
ByteString.intercalate "; " $ catMaybes xs
where
xs =
[ strVal "by" forwardedBy
, strVal "for" forwardedFor
, strVal "host" forwardedHost
, strVal "proto" $ CaseInsensitive.original <$> forwardedProto
]
strVal _ Nothing = Nothing
strVal key (Just val) = Just $ key <> "=" <> val