{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
module Network.Wai.Logger.Apache (
IPAddrSource (..),
apacheLogStr,
serverpushLogStr,
) where
#ifndef MIN_VERSION_base
#define MIN_VERSION_base(x,y,z) 1
#endif
#ifndef MIN_VERSION_wai
#define MIN_VERSION_wai(x,y,z) 1
#endif
import qualified Data.ByteString as B
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Unsafe as BU
import Data.Char (isSpace)
import Data.List (find)
import Data.Maybe (fromMaybe)
import Data.Word (Word8)
import Foreign.Ptr (Ptr)
import Foreign.Storable (pokeByteOff)
#if MIN_VERSION_base(4,5,0)
import Data.Monoid ((<>), First (..))
#else
import Data.Monoid (mappend)
#endif
import Network.HTTP.Types (Status, statusCode)
import Network.HTTP.Types.Header (HeaderName)
import Network.Wai (Request (..))
import Network.Wai.Logger.IP
import System.Log.FastLogger
data IPAddrSource
=
FromSocket
|
|
[HeaderName]
|
FromFallback
|
FromRequest (Request -> ByteString)
apacheLogStr
:: ToLogStr user
=> IPAddrSource
-> (Request -> Maybe user)
-> FormattedTime
-> Request
-> Status
-> Maybe Integer
-> LogStr
apacheLogStr :: forall user.
ToLogStr user =>
IPAddrSource
-> (Request -> Maybe user)
-> ByteString
-> Request
-> Status
-> Maybe Integer
-> LogStr
apacheLogStr IPAddrSource
ipsrc Request -> Maybe user
userget ByteString
tmstr Request
req Status
status Maybe Integer
msize =
ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem (IPAddrSource -> Request -> ByteString
getSourceIP IPAddrSource
ipsrc Request
req))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" - "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr -> (user -> LogStr) -> Maybe user -> LogStr
forall b a. b -> (a -> b) -> Maybe a -> b
maybe LogStr
"-" user -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
escapeUser (Request -> Maybe user
userget Request
req)
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" ["
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr ByteString
tmstr
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
"] \""
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem (Request -> ByteString
requestMethod Request
req))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem ByteString
path)
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> String -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (HttpVersion -> String
forall a. Show a => a -> String
show (Request -> HttpVersion
httpVersion Request
req))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
"\" "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> String -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (Int -> String
forall a. Show a => a -> String
show (Status -> Int
statusCode Status
status))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> String -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (String -> (Integer -> String) -> Maybe Integer -> String
forall b a. b -> (a -> b) -> Maybe a -> b
maybe String
"-" Integer -> String
forall a. Show a => a -> String
show Maybe Integer
msize)
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" \""
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem (ByteString -> Maybe ByteString -> ByteString
forall a. a -> Maybe a -> a
fromMaybe ByteString
"" Maybe ByteString
mr))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
"\" \""
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem (ByteString -> Maybe ByteString -> ByteString
forall a. a -> Maybe a -> a
fromMaybe ByteString
"" Maybe ByteString
mua))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
"\"\n"
where
path :: ByteString
path = Request -> ByteString
rawPathInfo Request
req ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Request -> ByteString
rawQueryString Request
req
#if !MIN_VERSION_base(4,5,0)
(<>) = mappend
#endif
#if MIN_VERSION_wai(3,2,0)
mr :: Maybe ByteString
mr = Request -> Maybe ByteString
requestHeaderReferer Request
req
mua :: Maybe ByteString
mua = Request -> Maybe ByteString
requestHeaderUserAgent Request
req
#else
mr = lookup "referer" $ requestHeaders req
mua = lookup "user-agent" $ requestHeaders req
#endif
serverpushLogStr
:: ToLogStr user
=> IPAddrSource
-> (Request -> Maybe user)
-> FormattedTime
-> Request
-> ByteString
-> Integer
-> LogStr
serverpushLogStr :: forall user.
ToLogStr user =>
IPAddrSource
-> (Request -> Maybe user)
-> ByteString
-> Request
-> ByteString
-> Integer
-> LogStr
serverpushLogStr IPAddrSource
ipsrc Request -> Maybe user
userget ByteString
tmstr Request
req ByteString
path Integer
size =
ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem (IPAddrSource -> Request -> ByteString
getSourceIP IPAddrSource
ipsrc Request
req))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" - "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr -> (user -> LogStr) -> Maybe user -> LogStr
forall b a. b -> (a -> b) -> Maybe a -> b
maybe LogStr
"-" user -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
escapeUser (Request -> Maybe user
userget Request
req)
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" ["
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr ByteString
tmstr
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
"] \"PUSH "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem ByteString
path)
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" HTTP/2\" 200 "
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> String -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (Integer -> String
forall a. Show a => a -> String
show Integer
size)
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
" \""
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem ByteString
ref)
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
"\" \""
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> ByteString
escapeLogItem (ByteString -> Maybe ByteString -> ByteString
forall a. a -> Maybe a -> a
fromMaybe ByteString
"" Maybe ByteString
mua))
LogStr -> LogStr -> LogStr
forall a. Semigroup a => a -> a -> a
<> LogStr
"\"\n"
where
ref :: ByteString
ref = Request -> ByteString
rawPathInfo Request
req
#if !MIN_VERSION_base(4,5,0)
(<>) = mappend
#endif
#if MIN_VERSION_wai(3,2,0)
mua :: Maybe ByteString
mua = Request -> Maybe ByteString
requestHeaderUserAgent Request
req
#else
mua = lookup "user-agent" $ requestHeaders req
#endif
escapeUser :: ToLogStr user => user -> LogStr
escapeUser :: forall msg. ToLogStr msg => msg -> LogStr
escapeUser = ByteString -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr (ByteString -> LogStr) -> (user -> ByteString) -> user -> LogStr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
escapeLogItem (ByteString -> ByteString)
-> (user -> ByteString) -> user -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. LogStr -> ByteString
fromLogStr (LogStr -> ByteString) -> (user -> LogStr) -> user -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. user -> LogStr
forall msg. ToLogStr msg => msg -> LogStr
toLogStr
escapeLogItem :: ByteString -> ByteString
escapeLogItem :: ByteString -> ByteString
escapeLogItem ByteString
bs
| Int
extra Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = ByteString
bs
| Bool
otherwise = Int -> (Ptr Word8 -> IO ()) -> ByteString
BI.unsafeCreate (Int
len Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
extra) ((Ptr Word8 -> IO ()) -> ByteString)
-> (Ptr Word8 -> IO ()) -> ByteString
forall a b. (a -> b) -> a -> b
$ \Ptr Word8
p -> Ptr Word8 -> Int -> Int -> IO ()
fill Ptr Word8
p Int
0 Int
0
where
len :: Int
len = ByteString -> Int
B.length ByteString
bs
extra :: Int
extra = (Int -> Word8 -> Int) -> Int -> ByteString -> Int
forall a. (a -> Word8 -> a) -> a -> ByteString -> a
B.foldl' (\Int
n Word8
w -> Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Word8 -> Int
escapeLen Word8
w) Int
0 ByteString
bs
fill :: Ptr Word8 -> Int -> Int -> IO ()
fill :: Ptr Word8 -> Int -> Int -> IO ()
fill Ptr Word8
p Int
i Int
j
| Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
len = () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
| Bool
otherwise = do
let w :: Word8
w = ByteString -> Int -> Word8
BU.unsafeIndex ByteString
bs Int
i
case Word8 -> Int
escapeLen Word8
w of
Int
0 -> do
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p Int
j Word8
w
Ptr Word8 -> Int -> Int -> IO ()
fill Ptr Word8
p (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
Int
1 -> do
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p Int
j Word8
backslash
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Word8 -> Word8
escapeChar Word8
w)
Ptr Word8 -> Int -> Int -> IO ()
fill Ptr Word8
p (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2)
Int
_ -> do
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p Int
j Word8
backslash
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Word8
0x78 :: Word8)
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2) (Word8 -> Word8
hex (Word8
w Word8 -> Word8 -> Word8
forall a. Integral a => a -> a -> a
`div` Word8
16))
Ptr Word8 -> Int -> Word8 -> IO ()
forall b. Ptr b -> Int -> Word8 -> IO ()
forall a b. Storable a => Ptr b -> Int -> a -> IO ()
pokeByteOff Ptr Word8
p (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3) (Word8 -> Word8
hex (Word8
w Word8 -> Word8 -> Word8
forall a. Integral a => a -> a -> a
`mod` Word8
16))
Ptr Word8 -> Int -> Int -> IO ()
fill Ptr Word8
p (Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Int
j Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4)
backslash :: Word8
backslash = Word8
0x5c :: Word8
hex :: Word8 -> Word8
hex :: Word8 -> Word8
hex Word8
n
| Word8
n Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
< Word8
10 = Word8
0x30 Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ Word8
n
| Bool
otherwise = Word8
0x57 Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ Word8
n
escapeLen :: Word8 -> Int
escapeLen :: Word8 -> Int
escapeLen Word8
w
| Word8
w Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x22 Bool -> Bool -> Bool
|| Word8
w Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x5c = Int
1
| Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x20 Bool -> Bool -> Bool
&& Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
< Word8
0x7f = Int
0
| Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0x08 Bool -> Bool -> Bool
&& Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0x0d Bool -> Bool -> Bool
&& Word8
w Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
/= Word8
0x0c = Int
1
| Bool
otherwise = Int
3
escapeChar :: Word8 -> Word8
escapeChar :: Word8 -> Word8
escapeChar Word8
0x08 = Word8
0x62
escapeChar Word8
0x09 = Word8
0x74
escapeChar Word8
0x0a = Word8
0x6e
escapeChar Word8
0x0b = Word8
0x76
escapeChar Word8
0x0d = Word8
0x72
escapeChar Word8
w = Word8
w
getSourceIP :: IPAddrSource -> Request -> ByteString
getSourceIP :: IPAddrSource -> Request -> ByteString
getSourceIP IPAddrSource
FromSocket = Request -> ByteString
getSourceFromSocket
getSourceIP IPAddrSource
FromHeader = Request -> ByteString
getSourceFromHeader
getSourceIP IPAddrSource
FromFallback = Request -> ByteString
getSourceFromFallback
getSourceIP (FromHeaderCustom [HeaderName]
hs) = ByteString -> Maybe ByteString -> ByteString
forall a. a -> Maybe a -> a
fromMaybe ByteString
"-" (Maybe ByteString -> ByteString)
-> (Request -> Maybe ByteString) -> Request -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [HeaderName] -> Request -> Maybe ByteString
getSourceFromHeaderCustom [HeaderName]
hs
getSourceIP (FromRequest Request -> ByteString
fromReq) = Request -> ByteString
fromReq
getSourceFromSocket :: Request -> ByteString
getSourceFromSocket :: Request -> ByteString
getSourceFromSocket = String -> ByteString
BS.pack (String -> ByteString)
-> (Request -> String) -> Request -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SockAddr -> String
showSockAddr (SockAddr -> String) -> (Request -> SockAddr) -> Request -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> SockAddr
remoteHost
getSourceFromHeader :: Request -> ByteString
= ByteString -> Maybe ByteString -> ByteString
forall a. a -> Maybe a -> a
fromMaybe ByteString
"-" (Maybe ByteString -> ByteString)
-> (Request -> Maybe ByteString) -> Request -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> Maybe ByteString
getSource
getSourceFromFallback :: Request -> ByteString
getSourceFromFallback :: Request -> ByteString
getSourceFromFallback Request
req = ByteString -> Maybe ByteString -> ByteString
forall a. a -> Maybe a -> a
fromMaybe (Request -> ByteString
getSourceFromSocket Request
req) (Maybe ByteString -> ByteString) -> Maybe ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Request -> Maybe ByteString
getSource Request
req
getSource :: Request -> Maybe ByteString
getSource :: Request -> Maybe ByteString
getSource =
[(HeaderName, ByteString -> ByteString)]
-> Request -> Maybe ByteString
getSourceFromHeaders
[(HeaderName
"x-real-ip", ByteString -> ByteString
firstIpInXFF), (HeaderName
"x-forwarded-for", ByteString -> ByteString
firstIpInXFF)]
firstIpInXFF :: ByteString -> ByteString
firstIpInXFF :: ByteString -> ByteString
firstIpInXFF = (Char -> Bool) -> ByteString -> ByteString
BS.takeWhile (\Char
c -> Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
',' Bool -> Bool -> Bool
&& Bool -> Bool
not (Char -> Bool
isSpace Char
c)) (ByteString -> ByteString)
-> (ByteString -> ByteString) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ByteString -> ByteString
BS.dropWhile Char -> Bool
isSpace
getSourceFromHeaders
:: [(HeaderName, ByteString -> ByteString)] -> Request -> Maybe ByteString
[(HeaderName, ByteString -> ByteString)]
headerNamesAndPostProc Request
req = First ByteString -> Maybe ByteString
forall a. First a -> Maybe a
getFirst (First ByteString -> Maybe ByteString)
-> First ByteString -> Maybe ByteString
forall a b. (a -> b) -> a -> b
$ (Header -> First ByteString) -> [Header] -> First ByteString
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap Header -> First ByteString
f ([Header] -> First ByteString) -> [Header] -> First ByteString
forall a b. (a -> b) -> a -> b
$ Request -> [Header]
requestHeaders Request
req
where
f :: (HeaderName, ByteString) -> First ByteString
f :: Header -> First ByteString
f (HeaderName
headerNameFromReq, ByteString
headerValFromReq) =
let maybePostProc :: Maybe (HeaderName, ByteString -> ByteString)
maybePostProc =
((HeaderName, ByteString -> ByteString) -> Bool)
-> [(HeaderName, ByteString -> ByteString)]
-> Maybe (HeaderName, ByteString -> ByteString)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find
(\(HeaderName
headerNameFromPostProc, ByteString -> ByteString
_) -> HeaderName
headerNameFromReq HeaderName -> HeaderName -> Bool
forall a. Eq a => a -> a -> Bool
== HeaderName
headerNameFromPostProc)
[(HeaderName, ByteString -> ByteString)]
headerNamesAndPostProc
in Maybe ByteString -> First ByteString
forall a. Maybe a -> First a
First (Maybe ByteString -> First ByteString)
-> Maybe ByteString -> First ByteString
forall a b. (a -> b) -> a -> b
$ ((HeaderName, ByteString -> ByteString) -> ByteString)
-> Maybe (HeaderName, ByteString -> ByteString) -> Maybe ByteString
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\(HeaderName
_, ByteString -> ByteString
postProc) -> ByteString -> ByteString
postProc ByteString
headerValFromReq) Maybe (HeaderName, ByteString -> ByteString)
maybePostProc
getSourceFromHeaderCustom :: [HeaderName] -> Request -> Maybe ByteString
[HeaderName]
hs = [(HeaderName, ByteString -> ByteString)]
-> Request -> Maybe ByteString
getSourceFromHeaders ((HeaderName -> (HeaderName, ByteString -> ByteString))
-> [HeaderName] -> [(HeaderName, ByteString -> ByteString)]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (,ByteString -> ByteString
firstIpInXFF) [HeaderName]
hs)