packages feed

connection 0.1.2 → 0.1.3

raw patch · 3 files changed

+111/−38 lines, 3 filesdep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: bytestring

API changes (from Hackage documentation)

+ Network.Connection: LineTooLong :: LineTooLong
+ Network.Connection: connectionGetLine :: Int -> Connection -> IO ByteString
+ Network.Connection: data LineTooLong
+ Network.Connection: instance Exception LineTooLong
+ Network.Connection: instance Show LineTooLong
+ Network.Connection: instance Typeable LineTooLong

Files

Network/Connection.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveDataTypeable #-} -- | -- Module      : Network.Connection -- License     : BSD-style@@ -16,6 +18,9 @@     , TLSSettings(..)     , SockSettings(..) +    -- * Exceptions+    , LineTooLong(..)+     -- * Library initialization     , initConnectionContext     , ConnectionContext@@ -29,6 +34,7 @@     , connectionGet     , connectionGetChunk     , connectionGetChunk'+    , connectionGetLine     , connectionPut      -- * TLS related operation@@ -37,8 +43,8 @@     ) where  import Control.Applicative-import Control.Monad ((>=>), when) import Control.Concurrent.MVar+import Control.Monad (join) import qualified Control.Exception as E import qualified System.IO.Error as E @@ -50,6 +56,7 @@ import Network.Socks5 import qualified Network as N +import Data.Data import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L@@ -64,6 +71,12 @@ type Manager = MVar (M.Map TLS.SessionID TLS.SessionData) data ConnectionSessionManager = ConnectionSessionManager Manager +-- | This is the exception raised if we reached the user specified limit for+-- the line in ConnectionGetLine.+data LineTooLong = LineTooLong deriving (Show,Typeable)++instance E.Exception LineTooLong+ instance TLS.SessionManager ConnectionSessionManager where     sessionResume (ConnectionSessionManager mvar) sessionID =         withMVar mvar (return . M.lookup sessionID)@@ -92,17 +105,11 @@ withBackend :: (ConnectionBackend -> IO a) -> Connection -> IO a withBackend f conn = modifyMVar (connectionBackend conn) (\b -> f b >>= \a -> return (b,a)) -withBuffer :: (ByteString -> IO (ByteString, b)) -> Connection -> IO b-withBuffer f conn = modifyMVar (connectionBuffer conn) f--setEOF :: Connection -> IO ()-setEOF conn = modifyMVar_ (connectionEOF conn) (const (return True))--checkEOF :: Connection -> IO ()-checkEOF conn = readMVar (connectionEOF conn) >>= flip when (E.ioError $ E.mkIOError E.eofErrorType "" Nothing Nothing)- connectionNew :: ConnectionParams -> ConnectionBackend -> IO Connection-connectionNew p backend = Connection <$> newMVar backend <*> newMVar B.empty <*> pure (connectionHostname p, connectionPort p) <*> newMVar False+connectionNew p backend =+    Connection <$> newMVar backend+               <*> newMVar (Just B.empty)+               <*> pure (connectionHostname p, connectionPort p)  -- | Use an already established handle to create a connection object. --@@ -121,7 +128,7 @@           -> ConnectionParams  -- ^ The parameters for this connection (where to connect, and such).           -> IO Connection     -- ^ The new established connection on success. connectTo cg cParams = do-        h <- conFct (connectionHostname cParams) (N.PortNumber $ connectionPort cParams)        +        h <- conFct (connectionHostname cParams) (N.PortNumber $ connectionPort cParams)         connectFromHandle cg h cParams     where         conFct = case connectionUseSocks cParams of@@ -130,42 +137,109 @@  -- | Put a block of data in the connection. connectionPut :: Connection -> ByteString -> IO ()-connectionPut connection content = checkEOF connection >> withBackend doWrite connection+connectionPut connection content = withBackend doWrite connection     where doWrite (ConnectionStream h) = B.hPut h content >> hFlush h           doWrite (ConnectionTLS ctx)  = TLS.sendData ctx $ L.fromChunks [content]  -- | Get some bytes from a connection. ----- The size argument is just the maximum that could be returned to the user,--- however the call will returns as soon as there's data, even if there's less--- data than expected.+-- The size argument is just the maximum that could be returned to the user.+-- The call will return as soon as there's data, even if there's less+-- than requested.  Hence, it behaves like 'B.hGetSome'.+--+-- On end of input, 'connectionGet' returns 0, but subsequent calls will throw+-- an 'E.isEOFError' exception. connectionGet :: Connection -> Int -> IO ByteString-connectionGet conn size = connectionGetChunk' conn $ B.splitAt size+connectionGet conn size+  | size < 0  = fail "Network.Connection.connectionGet: size < 0"+  | size == 0 = return B.empty+  | otherwise = connectionGetChunkBase "connectionGet" conn $ B.splitAt size  -- | Get the next block of data from the connection. connectionGetChunk :: Connection -> IO ByteString-connectionGetChunk conn = connectionGetChunk' conn $ \s -> (s, B.empty)+connectionGetChunk conn =+    connectionGetChunkBase "connectionGetChunk" conn $ \s -> (s, B.empty)  -- | Like 'connectionGetChunk', but return the unused portion to the buffer, -- where it will be the next chunk read. connectionGetChunk' :: Connection -> (ByteString -> (a, ByteString)) -> IO a-connectionGetChunk' conn f = checkEOF conn >> withBuffer getData conn-  where getData buf-          | B.null buf = do-              chunk <- withBackend (getMoreData >=> markEOF) conn-              return $ swap $ f chunk-          | otherwise =-              return $ swap $ f buf+connectionGetChunk' = connectionGetChunkBase "connectionGetChunk'" -        markEOF bs-            | B.null bs = setEOF conn >> return bs-            | otherwise = return bs+connectionGetChunkBase :: String -> Connection -> (ByteString -> (a, ByteString)) -> IO a+connectionGetChunkBase loc conn f =+    modifyMVar (connectionBuffer conn) $ \m ->+        case m of+            Nothing -> throwEOF conn loc+            Just buf+              | B.null buf -> do+                  chunk <- withBackend getMoreData conn+                  if B.null chunk+                     then closeBuf chunk+                     else updateBuf chunk+              | otherwise ->+                  updateBuf buf+  where+    getMoreData (ConnectionTLS tlsctx) = TLS.recvData tlsctx+    getMoreData (ConnectionStream h)   = B.hGetSome h (16 * 1024) -        getMoreData (ConnectionTLS tlsctx) = TLS.recvData tlsctx-        getMoreData (ConnectionStream h)   = B.hGetSome h (16 * 1024)+    updateBuf buf = case f buf of (a, !buf') -> return (Just buf', a)+    closeBuf  buf = case f buf of (a, _buf') -> return (Nothing, a) -        swap (a, b) = (b, a)+-- | Get the next line, using ASCII LF as the line terminator.+--+-- This throws an 'isEOFError' exception on end of input, and LineTooLong when+-- the number of bytes gathered is over the limit without a line terminator.+--+-- The actual line returned can be bigger than the limit specified, provided+-- that the last chunk returned by the underlaying backend contains a LF.+-- In another world only when we need more input and limit is reached that the+-- LineTooLong exception will be raised.+--+-- An end of file will be considered as a line terminator too, if line is+-- not empty.+connectionGetLine :: Int           -- ^ Maximum number of bytes before raising a LineTooLong exception+                  -> Connection    -- ^ Connection+                  -> IO ByteString -- ^ The received line with the LF trimmed+connectionGetLine limit conn = more (throwEOF conn loc) 0 id+  where+    loc = "connectionGetLine"+    lineTooLong = E.throwIO LineTooLong +    -- Accumulate chunks using a difference list, and concatenate them+    -- when an end-of-line indicator is reached.+    more eofK !currentSz !dl =+        getChunk (\s -> let len = B.length s+                         in if currentSz + len > limit+                               then lineTooLong+                               else more eofK (currentSz + len) (dl . (s:)))+                 (\s -> done (dl . (s:)))+                 (done dl)++    done :: ([ByteString] -> [ByteString]) -> IO ByteString+    done dl = return $! B.concat $ dl []++    -- Get another chunk, and call one of the continuations+    getChunk :: (ByteString -> IO r) -- moreK: need more input+             -> (ByteString -> IO r) -- doneK: end of line (line terminator found)+             -> IO r                 -- eofK:  end of file+             -> IO r+    getChunk moreK doneK eofK =+      join $ connectionGetChunkBase loc conn $ \s ->+        if B.null s+          then (eofK, B.empty)+          else case B.breakByte 10 s of+                 (a, b)+                   | B.null b  -> (moreK a, B.empty)+                   | otherwise -> (doneK a, B.tail b)++throwEOF :: Connection -> String -> IO a+throwEOF conn loc =+    E.throwIO $ E.mkIOError E.eofErrorType loc' Nothing (Just path)+  where+    loc' = "Network.Connection." ++ loc+    path = let (host, port) = connectionID conn+            in host ++ ":" ++ show port+ -- | Close a connection. connectionClose :: Connection -> IO () connectionClose = withBackend backendClose@@ -173,12 +247,12 @@           backendClose (ConnectionStream h) = hClose h  -- | Activate secure layer using the parameters specified.--- +-- -- This is typically used to negociate a TLS channel on an already -- establish channel, e.g. supporting a STARTTLS command. it also -- flush the received buffer to prevent application confusing -- received data before and after the setSecure call.--- +-- -- If the connection is already using TLS, nothing else happens. connectionSetSecure :: ConnectionContext                     -> Connection@@ -189,7 +263,7 @@     modifyMVar (connectionBackend connection) $ \backend ->         case backend of             (ConnectionStream h) -> do ctx <- tlsEstablish h (makeTLSParams cg params)-                                       return (ConnectionTLS ctx, B.empty)+                                       return (ConnectionTLS ctx, Just B.empty)             (ConnectionTLS _)    -> return (backend, b)  -- | Returns if the connection is establish securely or not.
Network/Connection/Types.hs view
@@ -47,7 +47,7 @@ -- The simple settings is just the hostname and portnumber of the proxy server. -- -- That's for now the only settings in the SOCKS package,--- socks password, or authentication is not yet implemented.+-- socks password, or any sort of other authentications is not yet implemented. data SockSettings = SockSettingsSimple HostName PortNumber  -- | TLS Settings that can be either expressed as simple settings,@@ -74,9 +74,8 @@ -- | This opaque type represent a connection to a destination. data Connection = Connection     { connectionBackend :: MVar ConnectionBackend-    , connectionBuffer  :: MVar ByteString+    , connectionBuffer  :: MVar (Maybe ByteString) -- ^ this is set to 'Nothing' on EOF     , connectionID      :: (HostName, PortNumber)  -- ^ return a simple tuple of the port and hostname that we're connected to.-    , connectionEOF     :: MVar Bool     }  -- | Shared values (certificate store, sessions, ..) between connections
connection.cabal view
@@ -1,5 +1,5 @@ Name:                connection-Version:             0.1.2+Version:             0.1.3 Description:     Simple network library for all your connection need.     .