{- gemcap Cooyright (C) Jonathan Lamothe 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 . -} {-# 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