diff --git a/src/Network/Gemini/Capsule/Internal.hs b/src/Network/Gemini/Capsule/Internal.hs index 735be99..76e02ea 100644 --- a/src/Network/Gemini/Capsule/Internal.hs +++ b/src/Network/Gemini/Capsule/Internal.hs @@ -34,10 +34,16 @@ time. module Network.Gemini.Capsule.Internal ( readURL, - sendResponse + sendResponse, + strFromConn, + readMax, + stripCRLF ) where +import qualified Data.ByteString as BS import Data.Connection (Connection) +import qualified Data.Text as T +import Data.Text.Encoding (decodeUtf8') import Network.Gemini.Capsule.Encoding import Network.Gemini.Capsule.Types @@ -76,6 +82,28 @@ strFromConn -> Connection a -- ^ The connection to read from -> IO (Maybe String) -strFromConn = undefined +strFromConn maxLen conn = do + mbs <- readMax maxLen conn + return $ do + bs <- mbs + txt <- case decodeUtf8' bs of + Left _ -> Nothing + Right s -> Just s + stripCRLF $ T.unpack txt + +-- | Reads from a connection up to a maximum number of bytes, +-- returning 'Nothing' if the limit is exceeded +readMax + :: Int + -- ^ the maximum number of bytes + -> Connection a + -- ^ the 'Connection' to read from + -> IO (Maybe BS.ByteString) +readMax = undefined + +-- | Strips the CR/LF characters from the end of a string, retuning +-- Nothing if they are not present +stripCRLF :: String -> Maybe String +stripCRLF = undefined --jl diff --git a/test/Network/Gemini/Capsule/InternalSpec.hs b/test/Network/Gemini/Capsule/InternalSpec.hs index 390381f..08742f9 100644 --- a/test/Network/Gemini/Capsule/InternalSpec.hs +++ b/test/Network/Gemini/Capsule/InternalSpec.hs @@ -38,6 +38,8 @@ spec = describe "Internal" $ do readURLSpec sendResponseSpec strFromConnSpec + readMaxSpec + stripCRLFSpec readURLSpec :: Spec readURLSpec = describe "readURL" $ mapM_ @@ -56,10 +58,10 @@ readURLSpec = describe "readURL" $ mapM_ ] where - validConn = mkConn "gemini://example.com/\r\n" - longConn = mkConn longBS - tooLongConn = mkConn tooLongBS - gibConn = mkConn "aosidjfwoeinboijwefr" + 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') @@ -67,14 +69,32 @@ readURLSpec = describe "readURL" $ mapM_ longExp = validExp { gemPath = [longDir] } longDir = replicate (1024 - BS.length prefix) 'A' prefix = "gemini://example.com/" - mkConn bs = do - s <- nullInput - unRead bs s - return sampleConnection { source = s } sendResponseSpec :: Spec sendResponseSpec = describe "sendResponse" $ return () +strFromConnSpec :: Spec +strFromConnSpec = describe "strFromConn" $ mapM_ + ( \(desc, maxLen, ioConn, expect) -> context desc $ + xit ("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" ) + ] + +mkInConn :: [BS.ByteString] -> IO (Connection a) +mkInConn bss = do + s <- nullInput + mapM_ (`unRead` s) (reverse bss) + return sampleConnection { source = s } + sampleConnection :: Connection a sampleConnection = Connection { source = undefined @@ -83,7 +103,10 @@ sampleConnection = Connection , connExtraInfo = undefined } -strFromConnSpec :: Spec -strFromConnSpec = describe "strFromConn" $ return () +readMaxSpec :: Spec +readMaxSpec = describe "readMax" $ return () + +stripCRLFSpec :: Spec +stripCRLFSpec = describe "stripCRLF" $ return () --jl