203 lines
6.5 KiB
Haskell
203 lines
6.5 KiB
Haskell
{-
|
|
|
|
gemcap
|
|
|
|
Cooyright (C) Jonathan Lamothe <jonathan@jlamothe.net>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public
|
|
License along with this program. If not, see
|
|
<https://www.gnu.org/licenses/>.
|
|
|
|
-}
|
|
|
|
{-# LANGUAGE OverloadedStrings, RecordWildCards #-}
|
|
|
|
module Network.Gemini.Capsule.InternalSpec (spec) where
|
|
|
|
import qualified Data.ByteString as BS
|
|
import qualified Data.ByteString.Lazy as BSL
|
|
import Data.Char (ord)
|
|
import Data.Connection (Connection (..))
|
|
import Data.IORef (IORef, modifyIORef', newIORef, readIORef)
|
|
import Data.X509 (Certificate (..))
|
|
import System.IO.Streams (nullInput, unRead)
|
|
import Test.Hspec (
|
|
Spec,
|
|
context,
|
|
describe,
|
|
it,
|
|
shouldBe,
|
|
shouldReturn)
|
|
|
|
import Network.Gemini.Capsule.Types
|
|
import Network.Gemini.Capsule.Internal
|
|
|
|
spec :: Spec
|
|
spec = describe "Internal" $ do
|
|
runConnectionSpec
|
|
readURLSpec
|
|
strFromConnSpec
|
|
readMaxSpec
|
|
stripCRLFSpec
|
|
|
|
runConnectionSpec :: Spec
|
|
runConnectionSpec = describe "runConnection" $ mapM_
|
|
( \(desc, ioConnRef, handler, mCert, expect) -> context desc $
|
|
it ("should return " ++ show expect) $ do
|
|
(conn, outRef) <- ioConnRef
|
|
runConnection conn handler mCert
|
|
readIORef outRef `shouldReturn` expect
|
|
)
|
|
|
|
-- description, connection, handler, certificate, expectation
|
|
[ ( "basic connection", basicConn, basicH, Nothing, basicExp )
|
|
, ( "no certificate", basicConn, certH, Nothing, noCertExp )
|
|
, ( "with certificate", basicConn, certH, Just sampleCert, basicExp )
|
|
, ( "gibberish with CR/LF", gibConnCRLF, basicH, Nothing, gibExp )
|
|
, ( "gibberish w/o CR/LF", gibConn, basicH, Nothing, gibExp )
|
|
]
|
|
|
|
where
|
|
basicConn = mkIOConn ["gemini://example.com/\r\n"]
|
|
gibConnCRLF = mkIOConn ["aosidjgfoeribjeworifj\r\n"]
|
|
gibConn = mkIOConn ["sodifjboije"]
|
|
basicH _ = return newGemResponse { respBody = Just success }
|
|
|
|
certH req = return $ case reqCert req of
|
|
Nothing -> newGemResponse
|
|
{ respStatus = 60
|
|
, respMeta = "certificate required"
|
|
}
|
|
Just _ -> newGemResponse { respBody = Just success }
|
|
|
|
basicExp = ["20 text/gemini\r\nSuccess!\r\n"]
|
|
noCertExp = ["60 certificate required\r\n"]
|
|
gibExp = ["59 bad request\r\n"]
|
|
success = "Success!\r\n"
|
|
|
|
readURLSpec :: Spec
|
|
readURLSpec = describe "readURL" $ mapM_
|
|
( \(desc, ioConn, expect) -> context desc $
|
|
it ("should return " ++ show expect) $
|
|
do
|
|
conn <- ioConn
|
|
readURL conn `shouldReturn` expect
|
|
)
|
|
|
|
-- description, connection, expected result
|
|
[ ( "valid URL", validConn, Just validExp )
|
|
, ( "long URL", longConn, Just longExp )
|
|
, ( "too long URL", tooLongConn, Nothing )
|
|
, ( "gibberish input", gibConn, Nothing )
|
|
]
|
|
|
|
where
|
|
validConn = mkInConn ["gemini://example.com/\r\n"]
|
|
longConn = mkInConn [longBS]
|
|
tooLongConn = mkInConn [tooLongBS]
|
|
gibConn = mkInConn ["aosidjfwoeinboijwefr"]
|
|
longBS = BS.pack (take 1024 bytes) <> "\r\n"
|
|
tooLongBS = BS.pack (take 1025 bytes) <> "\r\n"
|
|
bytes = BS.unpack prefix ++ repeat (fromIntegral $ ord 'A')
|
|
validExp = newGemURL "example.com"
|
|
longExp = validExp { gemPath = [longDir] }
|
|
longDir = replicate (1024 - BS.length prefix) 'A'
|
|
prefix = "gemini://example.com/"
|
|
|
|
strFromConnSpec :: Spec
|
|
strFromConnSpec = describe "strFromConn" $ mapM_
|
|
( \(desc, maxLen, ioConn, expect) -> context desc $
|
|
it ("should return " ++ show expect) $ do
|
|
conn <- ioConn
|
|
strFromConn maxLen conn `shouldReturn` expect
|
|
)
|
|
|
|
-- description, max size, connection, expected
|
|
[ ( "valid string", 100, mkInConn ["foo\r\n"], Just "foo" )
|
|
, ( "long string", 5, mkInConn ["too long\r\n"], Nothing )
|
|
, ( "no CR/LF", 100, mkInConn ["foo"], Nothing )
|
|
, ( "bad UTF-8", 100, mkInConn ["foo\xff\r\n"], Nothing )
|
|
, ( "non-ASCII", 100, mkInConn ["\xc3\xa9\r\n"], Just "\xe9" )
|
|
]
|
|
|
|
readMaxSpec :: Spec
|
|
readMaxSpec = describe "readMax" $ mapM_
|
|
( \(desc, maxLen, ioConn, expect) -> context desc $
|
|
it ("should return " ++ show expect) $ do
|
|
conn <- ioConn
|
|
readMax maxLen conn `shouldReturn` expect
|
|
)
|
|
|
|
-- description, max length, connection, expected
|
|
[ ( "single input", 1024, singleConn, Just singleBS )
|
|
, ( "multi input", 1024, multiConn, Just multiBS )
|
|
, ( "long input", longLen, longConn, Just longBS )
|
|
, ( "too long", pred longLen, longConn, Nothing )
|
|
, ( "empty input", 1024, mkInConn [], Just "" )
|
|
]
|
|
|
|
where
|
|
singleConn = mkInConn ["foo"]
|
|
multiConn = mkInConn ["foo", "bar", "baz"]
|
|
longConn = mkInConn [longBS]
|
|
longLen = BS.length longBS
|
|
singleBS = "foo"
|
|
multiBS = "foobarbaz"
|
|
longBS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
|
stripCRLFSpec :: Spec
|
|
stripCRLFSpec = describe "stripCRLF" $ mapM_
|
|
( \(input, expected) -> context (show input) $
|
|
it ("should be" ++ show expected) $
|
|
stripCRLF input `shouldBe` expected
|
|
)
|
|
|
|
-- input, expectation
|
|
[ ( "foo\r\n", Just "foo" )
|
|
, ( "foo\n", Nothing )
|
|
, ( "foo", Nothing )
|
|
, ( "\r\n", Just "" )
|
|
]
|
|
|
|
mkIOConn :: [BS.ByteString] -> IO (Connection (), IORef [BSL.ByteString])
|
|
mkIOConn input = do
|
|
ref <- newIORef []
|
|
conn <-
|
|
( \c -> c { send = \bs -> modifyIORef' ref (++[bs]) }
|
|
) <$> mkInConn input
|
|
return (conn, ref)
|
|
|
|
mkInConn :: [BS.ByteString] -> IO (Connection ())
|
|
mkInConn bss = do
|
|
source <- nullInput
|
|
mapM_ (`unRead` source) (reverse bss)
|
|
let
|
|
send = const $ return ()
|
|
close = return ()
|
|
connExtraInfo = ()
|
|
return Connection {..}
|
|
|
|
sampleCert :: Certificate
|
|
sampleCert = Certificate
|
|
{ certVersion = undefined
|
|
, certSerial = undefined
|
|
, certSignatureAlg = undefined
|
|
, certIssuerDN = undefined
|
|
, certValidity = undefined
|
|
, certSubjectDN = undefined
|
|
, certPubKey = undefined
|
|
, certExtensions = undefined
|
|
}
|
|
|
|
--jl
|