packages feed

wai-extra-3.1.14: test/Network/Wai/Middleware/RealIpSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Network.Wai.Middleware.RealIpSpec (
    spec,
) where

import qualified Data.ByteString.Lazy.Char8 as B8
import qualified Data.IP as IP
import Network.HTTP.Types (RequestHeaders, status200)
import Network.Wai
import Test.Hspec

import Network.Wai.Middleware.RealIp
import Network.Wai.Test

spec :: Spec
spec = do
    describe "realIp" $ do
        it "does nothing when header is missing" $ do
            resp <- runApp "127.0.0.1" [] realIp
            simpleBody resp `shouldBe` "127.0.0.1"

        it "uses X-Forwarded-For when present" $ do
            let headers = [("X-Forwarded-For", "1.1.1.1")]
            resp <- runApp "127.0.0.1" headers realIp
            simpleBody resp `shouldBe` "1.1.1.1"

        it "ignores X-Forwarded-For from non-trusted ip" $ do
            let headers = [("X-Forwarded-For", "1.1.1.1")]
            resp <- runApp "1.2.3.4" headers realIp
            simpleBody resp `shouldBe` "1.2.3.4"

        it "ignores trusted ip addresses in X-Forwarded-For" $ do
            let headers = [("X-Forwarded-For", "1.1.1.1, 10.0.0.1")]
            resp <- runApp "127.0.0.1" headers realIp
            simpleBody resp `shouldBe` "1.1.1.1"

        it "ignores invalid ip addresses" $ do
            let headers1 = [("X-Forwarded-For", "1.1.1")]
            resp1 <- runApp "127.0.0.1" headers1 realIp
            let headers2 = [("X-Forwarded-For", "1.1.1.1,foo")]
            resp2 <- runApp "127.0.0.1" headers2 realIp

            simpleBody resp1 `shouldBe` "127.0.0.1"
            simpleBody resp2 `shouldBe` "1.1.1.1"

        it "takes the last non-trusted address" $ do
            let headers = [("X-Forwarded-For", "1.1.1.1, 2.2.2.2, 10.0.0.1")]
            resp <- runApp "127.0.0.1" headers realIp
            simpleBody resp `shouldBe` "2.2.2.2"

        it "takes the first address when all are trusted" $ do
            let headers = [("X-Forwarded-For", "192.168.0.1, 10.0.0.2, 10.0.0.1")]
            resp <- runApp "127.0.0.1" headers realIp
            simpleBody resp `shouldBe` "192.168.0.1"

        it "handles repeated headers" $ do
            let headers = [("X-Forwarded-For", "1.1.1.1"), ("X-Forwarded-For", "2.2.2.2")]
            resp <- runApp "127.0.0.1" headers realIp
            simpleBody resp `shouldBe` "2.2.2.2"

        it "handles ipv6 addresses" $ do
            let headers = [("X-Forwarded-For", "2001:db8::ff00:42:8329,10.0.0.1")]
            resp <- runApp "::1" headers realIp
            simpleBody resp `shouldBe` "2001:db8::ff00:42:8329"

    describe "realIpHeader" $ do
        it "uses specified header" $ do
            let headers = [("X-Forwarded-For", "1.1.1.1"), ("X-Real-Ip", "2.2.2.2")]
            resp <- runApp "127.0.0.1" headers $ realIpHeader "X-Real-Ip"
            simpleBody resp `shouldBe` "2.2.2.2"

    describe "realIpTrusted" $ do
        it "uses provided trusted predicate" $ do
            let headers = [("X-Forwarded-For", "10.0.0.1, 1.1.1.1")]
                isTrusted1 ip = any (ipInRange ip) ["127.0.0.1/32", "1.1.1.1/32"]
                isTrusted2 = flip ipInRange "1.1.1.1/32"

            resp1 <- runApp "127.0.0.1" headers $ realIpTrusted "X-Forwarded-For" isTrusted1
            resp2 <- runApp "10.0.0.2" headers $ realIpTrusted "X-Forwarded-For" isTrusted2

            simpleBody resp1 `shouldBe` "10.0.0.1"
            simpleBody resp2 `shouldBe` "10.0.0.2"

runApp :: IP.IP -> RequestHeaders -> Middleware -> IO SResponse
runApp ip hs mw =
    runSession
        ( request
            defaultRequest{remoteHost = IP.toSockAddr (ip, 80), requestHeaders = hs}
        )
        $ mw app
  where
    app req respond = respond $ responseLBS status200 [] $ renderIp req
    renderIp = B8.pack . maybe "" (show . fst) . IP.fromSockAddr . remoteHost