{-# LANGUAGE OverloadedStrings #-}
module Keter.RateLimiter.RequestUtils
(
ipv4ToString
, ipv6ToString
, getClientIP
, getRequestPath
, getRequestMethod
, getRequestHost
, getRequestUserAgent
, byIP
, byIPAndPath
, byIPAndUserAgent
, byHeaderAndIP
) where
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Encoding.Error as TEE
import Network.Wai (Request)
import qualified Network.Wai as WAI
import Network.Socket
( SockAddr(..)
, HostAddress
, HostAddress6
, hostAddressToTuple
)
import Network.HTTP.Types.Header (HeaderName, hHost, hUserAgent)
import Data.Bits ((.&.), shiftR)
import Data.CaseInsensitive (mk)
import Numeric (showHex)
ipv4ToString :: HostAddress -> Text
ipv4ToString :: Word32 -> Text
ipv4ToString Word32
addr =
let (Word8
o1, Word8
o2, Word8
o3, Word8
o4) = Word32 -> (Word8, Word8, Word8, Word8)
hostAddressToTuple Word32
addr
in Text -> [Text] -> Text
T.intercalate Text
"." ((Word8 -> Text) -> [Word8] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (String -> Text
T.pack (String -> Text) -> (Word8 -> String) -> Word8 -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> String
forall a. Show a => a -> String
show) [Word8
o1, Word8
o2, Word8
o3, Word8
o4])
ipv6ToString :: HostAddress6 -> Text
ipv6ToString :: HostAddress6 -> Text
ipv6ToString (Word32
w1, Word32
w2, Word32
w3, Word32
w4) =
Text -> [Text] -> Text
T.intercalate Text
":" ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ (Word32 -> Text) -> [Word32] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (String -> Text
T.pack (String -> Text) -> (Word32 -> String) -> Word32 -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
pad4 (String -> String) -> (Word32 -> String) -> Word32 -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word32 -> String -> String
forall a. Integral a => a -> String -> String
`showHex` String
"")) [Word32]
words16
where
words16 :: [Word32]
words16 =
[ Word32
w1 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftR` Int
16, Word32
w1 Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0xFFFF
, Word32
w2 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftR` Int
16, Word32
w2 Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0xFFFF
, Word32
w3 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftR` Int
16, Word32
w3 Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0xFFFF
, Word32
w4 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftR` Int
16, Word32
w4 Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0xFFFF
]
pad4 :: String -> String
pad4 String
s = Int -> Char -> String
forall a. Int -> a -> [a]
replicate (Int
4 Int -> Int -> Int
forall a. Num a => a -> a -> a
- String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
s) Char
'0' String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s
getClientIP :: Request -> IO Text
getClientIP :: Request -> IO Text
getClientIP Request
req = do
let safeDecode :: ByteString -> Text
safeDecode = OnDecodeError -> ByteString -> Text
TE.decodeUtf8With OnDecodeError
TEE.lenientDecode
ipTxt :: Text
ipTxt = case HeaderName -> [(HeaderName, ByteString)] -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup (ByteString -> HeaderName
forall s. FoldCase s => s -> CI s
mk ByteString
"x-forwarded-for") (Request -> [(HeaderName, ByteString)]
WAI.requestHeaders Request
req) of
Just ByteString
xff -> (Char -> Bool) -> Text -> Text
T.takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
',') (Text -> Text) -> (ByteString -> Text) -> ByteString -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Text
safeDecode (ByteString -> Text) -> ByteString -> Text
forall a b. (a -> b) -> a -> b
$ ByteString
xff
Maybe ByteString
Nothing -> case HeaderName -> [(HeaderName, ByteString)] -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup (ByteString -> HeaderName
forall s. FoldCase s => s -> CI s
mk ByteString
"x-real-ip") (Request -> [(HeaderName, ByteString)]
WAI.requestHeaders Request
req) of
Just ByteString
rip -> ByteString -> Text
safeDecode ByteString
rip
Maybe ByteString
Nothing -> case Request -> SockAddr
WAI.remoteHost Request
req of
SockAddrInet PortNumber
_ Word32
addr -> Word32 -> Text
ipv4ToString Word32
addr
SockAddrInet6 PortNumber
_ Word32
_ HostAddress6
addr Word32
_ -> HostAddress6 -> Text
ipv6ToString HostAddress6
addr
SockAddrUnix String
path -> String -> Text
T.pack String
path
Text -> IO Text
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Text
ipTxt
getRequestPath :: Request -> Text
getRequestPath :: Request -> Text
getRequestPath = OnDecodeError -> ByteString -> Text
TE.decodeUtf8With OnDecodeError
TEE.lenientDecode (ByteString -> Text) -> (Request -> ByteString) -> Request -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> ByteString
WAI.rawPathInfo
getRequestMethod :: Request -> Text
getRequestMethod :: Request -> Text
getRequestMethod = ByteString -> Text
TE.decodeUtf8 (ByteString -> Text) -> (Request -> ByteString) -> Request -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> ByteString
WAI.requestMethod
getRequestHost :: Request -> Maybe Text
getRequestHost :: Request -> Maybe Text
getRequestHost = (ByteString -> Text) -> Maybe ByteString -> Maybe Text
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (OnDecodeError -> ByteString -> Text
TE.decodeUtf8With OnDecodeError
TEE.lenientDecode) (Maybe ByteString -> Maybe Text)
-> (Request -> Maybe ByteString) -> Request -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HeaderName -> [(HeaderName, ByteString)] -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
hHost ([(HeaderName, ByteString)] -> Maybe ByteString)
-> (Request -> [(HeaderName, ByteString)])
-> Request
-> Maybe ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> [(HeaderName, ByteString)]
WAI.requestHeaders
getRequestUserAgent :: Request -> Maybe Text
getRequestUserAgent :: Request -> Maybe Text
getRequestUserAgent = (ByteString -> Text) -> Maybe ByteString -> Maybe Text
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (OnDecodeError -> ByteString -> Text
TE.decodeUtf8With OnDecodeError
TEE.lenientDecode) (Maybe ByteString -> Maybe Text)
-> (Request -> Maybe ByteString) -> Request -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HeaderName -> [(HeaderName, ByteString)] -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
hUserAgent ([(HeaderName, ByteString)] -> Maybe ByteString)
-> (Request -> [(HeaderName, ByteString)])
-> Request
-> Maybe ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> [(HeaderName, ByteString)]
WAI.requestHeaders
byIP :: Request -> IO (Maybe Text)
byIP :: Request -> IO (Maybe Text)
byIP Request
req = Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> IO Text -> IO (Maybe Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Request -> IO Text
getClientIP Request
req
byIPAndPath :: Request -> IO (Maybe Text)
byIPAndPath :: Request -> IO (Maybe Text)
byIPAndPath Request
req = do
Text
ip <- Request -> IO Text
getClientIP Request
req
Maybe Text -> IO (Maybe Text)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Text -> IO (Maybe Text))
-> (Text -> Maybe Text) -> Text -> IO (Maybe Text)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> IO (Maybe Text)) -> Text -> IO (Maybe Text)
forall a b. (a -> b) -> a -> b
$ Text
ip Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Request -> Text
getRequestPath Request
req
byIPAndUserAgent :: Request -> IO (Maybe Text)
byIPAndUserAgent :: Request -> IO (Maybe Text)
byIPAndUserAgent Request
req = do
Text
ip <- Request -> IO Text
getClientIP Request
req
Maybe Text -> IO (Maybe Text)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Text -> IO (Maybe Text)) -> Maybe Text -> IO (Maybe Text)
forall a b. (a -> b) -> a -> b
$ case Request -> Maybe Text
getRequestUserAgent Request
req of
Maybe Text
Nothing -> Maybe Text
forall a. Maybe a
Nothing
Just Text
ua -> Text -> Maybe Text
forall a. a -> Maybe a
Just (Text
ip Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
ua)
byHeaderAndIP :: HeaderName -> Request -> IO (Maybe Text)
byHeaderAndIP :: HeaderName -> Request -> IO (Maybe Text)
byHeaderAndIP HeaderName
headerName Request
req = do
Text
ip <- Request -> IO Text
getClientIP Request
req
let mVal :: Maybe ByteString
mVal = HeaderName -> [(HeaderName, ByteString)] -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
headerName (Request -> [(HeaderName, ByteString)]
WAI.requestHeaders Request
req)
Maybe Text -> IO (Maybe Text)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Text -> IO (Maybe Text)) -> Maybe Text -> IO (Maybe Text)
forall a b. (a -> b) -> a -> b
$ (ByteString -> Text) -> Maybe ByteString -> Maybe Text
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\ByteString
hv -> Text
ip Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
":" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> OnDecodeError -> ByteString -> Text
TE.decodeUtf8With OnDecodeError
TEE.lenientDecode ByteString
hv) Maybe ByteString
mVal