packages feed

ftp-conduit 0.0.1 → 0.0.2

raw patch · 2 files changed

+113/−134 lines, 2 filesdep +utf8-stringPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: utf8-string

API changes (from Hackage documentation)

- Network.FTP.Conduit: connectDownloadToSink :: URI -> Sink ByteString (ErrorT FTPError IO) b -> ResourceT (ErrorT FTPError IO) b
- Network.FTP.Conduit: connectSourceToUpload :: URI -> Source (ErrorT FTPError IO) ByteString -> ResourceT (ErrorT FTPError IO) ()
- Network.FTP.Conduit: getFTPFile :: URI -> ResourceT (ErrorT FTPError IO) (Handle, Source (ErrorT FTPError IO) ByteString, ReleaseKey, ReleaseKey)
- Network.FTP.Conduit: putFTPFile :: URI -> ResourceT (ErrorT FTPError IO) (Handle, Sink ByteString (ErrorT FTPError IO) (), ReleaseKey, ReleaseKey)
+ Network.FTP.Conduit: SocketClosed :: FTPError
+ Network.FTP.Conduit: createSink :: URI -> Sink ByteString (ErrorT FTPError IO) ()
+ Network.FTP.Conduit: createSource :: URI -> Source (ErrorT FTPError IO) ByteString
- Network.FTP.Conduit: UnexpectedCode :: Int -> String -> FTPError
+ Network.FTP.Conduit: UnexpectedCode :: Int -> ByteString -> FTPError

Files

Network/FTP/Conduit.hs view
@@ -1,69 +1,36 @@ -- | This module contains code to use files on a remote FTP server as -- Sources and Sinks. ----- This module makes use of the FTP 'STOR' and 'RETR' commands. According--- to the FTP spec, these--- commands are set up so that a server response is sent at the beginning--- and end of the data transfers. The current Conduit library doesn't--- allow the code that creates the Source / Sink of the data transfer to--- run checks _after_ the data has been transferred. Because of this fact, this module exposes--- two interfaces.------ One interface is an interface that simply creates a Source / Sink and--- does not check the server return code after the data transfer (because--- it can't, as stated above). These two functions (putFTPFile and--- getFTPFile) return a Sink and a Source, respectively.------ Using these functions looks like:------ > runErrorT $--- >   runResourceT $--- >     (getFTPFile $ fromJust $ parseURI--- >       "ftp://ftp.kernel.org/pub/README_ABOUT_BZ2_FILES") >>=--- >         (\ (_, s, _, _) -> s $$ consume)------ The other interface is one that does check the error code after the--- data transfer. In order to do this, however, these functions must--- actually be the ones that apply the '$$' (connect) function, so they--- can perform the checks afterword. These are the connectDownloadToSink--- and connectSourceToUpload functions. You pass them a Sink and a Source,--- respectively, and they will call the '$$' (connect) function, check that--- the data transfer was successful, and then safely tear down the FTP--- connection. ------ Using these functions looks like:------ > runErrorT $ runResourceT $ connectDownloadToSink--- >   (fromJust $ parseURI--- >     "ftp://ftp.kernel.org/pub/README_ABOUT_BZ2_FILES") consume+-- Using these functions looks like this:+-- > let uri = fromJust $ parseURI "ftp://ftp.kernel.org/pub/README_ABOUT_BZ2_FILES"+-- > runErrorT $ runResourceT $ createSource uri $$ consume -- -- The functions here operate on the ErrorT monad transformer, because -- the server can send unexpected replies, which are thrown as errors. module Network.FTP.Conduit-  ( FTPError(..)-  , putFTPFile-  , getFTPFile-  , connectDownloadToSink-  , connectSourceToUpload+  ( createSink+  , createSource+  , FTPError(..)   ) where -import Control.Monad.Trans.Resource-import Data.Word import Data.Conduit-import Data.Conduit.Binary (sourceHandle, sinkHandle)-import Data.Bits-import Network.URI (URI(..), URIAuth(..))-import Data.ByteString (ByteString)-import Network.Socket hiding (connect)+import qualified Data.ByteString as BS+import Data.ByteString.UTF8 hiding (foldl)+import Network.Socket hiding (send, sendTo, recv, recvFrom, Closed)+import Network.Socket.ByteString+import Network.URI import Network.Utils import Control.Monad.Error-import System.IO+import Data.Word import System.ByteOrder+import Data.Bits+import Prelude hiding (getLine)+import Control.Monad.Trans.Resource --- | 'UnexpectedCode' has the form (Expected code, full response string)-data FTPError = UnexpectedCode Int String+data FTPError = UnexpectedCode Int BS.ByteString                 | GeneralError String                 | IncorrectScheme String+                | SocketClosed   deriving (Show) instance Error FTPError where   noMsg  = GeneralError ""@@ -75,74 +42,107 @@   LittleEndian -> x `shiftL` 8 + x `shiftR` 8   _ -> undefined -extractCode :: String -> Int-extractCode = read . (takeWhile (/= ' '))+getByte :: Socket -> ResourceT (ErrorT FTPError IO) Word8+getByte s = do+  b <- lift $ lift $ recv s 1+  if BS.null b then lift (throwError SocketClosed) else return $ BS.head b -readExpected :: Handle -> Int -> ResourceT (ErrorT FTPError IO) String-readExpected h i = do-  line <- liftIO $ hGetLine h-  --liftIO $ putStrLn $ "Read: " ++ line+getLine :: Socket -> ResourceT (ErrorT FTPError IO) BS.ByteString+getLine s = do+  b <- getByte s+  helper b+  where helper b = do+          b' <- getByte s+          if BS.pack [b, b'] == fromString "\r\n"+            then return BS.empty+            else helper b' >>= return . (BS.cons b)++extractCode :: BS.ByteString -> Int+extractCode = read . toString . (BS.takeWhile (/= 32))++readExpected :: Socket -> Int -> ResourceT (ErrorT FTPError IO) BS.ByteString+readExpected s i = do+  line <- getLine s+  --lift $ lift $ putStrLn $ "Read: " ++ (toString line)   if extractCode line /= i     then lift $ throwError $ UnexpectedCode i line     else return line -writeLine :: Handle -> String -> ResourceT (ErrorT FTPError IO) ()-writeLine h s = liftIO $ do-  --liftIO $ putStrLn $ "Writing: " ++ s-  hPutStr h $ s ++ "\r\n" -- hardcode the newline for platform independence-  hFlush h -- Buffering doesn't work right for some reason. Explicitly flush here.+writeLine :: Socket -> BS.ByteString -> ResourceT (ErrorT FTPError IO) ()+writeLine s bs = lift $ lift $ do+  --lift $ lift $ putStrLn $ "Writing: " ++ (toString bs)+  sendAll s $ bs `BS.append` (fromString "\r\n") -- hardcode the newline for platform independence --- | Give this a source and it uploads it to the given URI. This function calls '$$'.-connectSourceToUpload :: URI -> Source (ErrorT FTPError IO) ByteString -> ResourceT (ErrorT FTPError IO) ()-connectSourceToUpload uri source = do-  (handle, sink, release_control, release_data) <- putFTPFile uri-  out <- source $$ sink-  cleanUp release_control release_data handle-  return out+createSource :: URI -> Source (ErrorT FTPError IO) BS.ByteString+createSource uri = Source { sourcePull = pull+                           , sourceClose = close+                           } --- | Give this a Sink and it downloads the given URI to it. This function calls '$$'.-connectDownloadToSink :: URI -> Sink ByteString (ErrorT FTPError IO) b -> ResourceT (ErrorT FTPError IO) b-connectDownloadToSink uri sink = do-  (handle, source, release_control, release_data) <- getFTPFile uri-  out <- source $$ sink-  cleanUp release_control release_data handle-  return out+  where pull = do+          (c, rc, d, rd, path') <- common uri+          writeLine c $ fromString $ "RETR " ++ path'+          _ <- readExpected c 150+          pull' c rc d rd+        pull' c rc d rd= do+          bytes <- lift $ lift $ recv d 1024+          if BS.null bytes+            then do+              close' c rc d rd+              return Closed+            else do+              return $ Open (Source { sourcePull = pull' c rc d rd+                                    , sourceClose = close' c rc d rd+                                    }) bytes+        close = return ()+        close' c rc _ rd = do+          release rd+          _ <- readExpected c 226+          writeLine c $ fromString "QUIT"+          _ <- readExpected c 221+          release rc -cleanUp :: ReleaseKey -> ReleaseKey -> Handle -> ResourceT (ErrorT FTPError IO) ()-cleanUp release_control release_data handle= do-  release release_data-  _ <- readExpected handle 226-  writeLine handle "QUIT"-  _ <- readExpected handle 221-  release release_control-  return ()+createSink :: URI -> Sink BS.ByteString (ErrorT FTPError IO) ()+createSink uri = SinkData { sinkPush = push+                           , sinkClose = close+                           }+  where push input = do+          (c, rc, d, rd, path') <- common uri+          writeLine c $ fromString $ "STOR " ++ path'+          _ <- readExpected c 150+          push' c rc d rd input+        push' c rc d rd input = do+          lift $ lift $ sendAll d input+          return $ Processing (push' c rc d rd) (close' c rc d rd)+        close = return ()+        close' c rc _ rd = do+          release rd+          _ <- readExpected c 226+          writeLine c $ fromString "QUIT"+          _ <- readExpected c 221+          release rc -setupHandleForFTP :: URI -> IOMode -> ResourceT (ErrorT FTPError IO) (Handle, Handle, String, ReleaseKey, ReleaseKey)-setupHandleForFTP URI { uriScheme = scheme-                      , uriAuthority = authority-                      , uriPath = path-                      } iomode = do -  if scheme /= "ftp:" then lift (throwError (IncorrectScheme scheme)) else return ()-  s <- liftIO $ connectTCP host (PortNum (hton_16 port))-  h <- liftIO $ socketToHandle s ReadWriteMode-  liftIO $ hSetBuffering h LineBuffering-  release_control <- register $ liftIO $ hClose h-  _ <- readExpected h 220-  writeLine h $ "USER " ++ user-  _ <- readExpected h 331-  writeLine h $ "PASS " ++ pass-  _ <- readExpected h 230-  writeLine h "TYPE I"-  _ <- readExpected h 200-  writeLine h "PASV"-  pasv_response <- readExpected h 227+common :: URI -> ResourceT (ErrorT FTPError IO) (Socket, ReleaseKey, Socket, ReleaseKey, String)+common (URI { uriScheme = scheme'+       , uriAuthority = authority'+       , uriPath = path'+       }) = do+  if scheme' /= "ftp:" then lift (throwError (IncorrectScheme scheme')) else return ()+  c <- lift $ lift $ connectTCP host (PortNum (hton_16 port))+  rc <- register $ sClose c+  _ <- readExpected c 220+  writeLine c $ fromString $ "USER " ++ user+  _ <- readExpected c 331+  writeLine c $ fromString $ "PASS " ++ pass+  _ <- readExpected c 230+  writeLine c $ fromString "TYPE I"+  _ <- readExpected c 200+  writeLine c $ fromString "PASV"+  pasv_response <- readExpected c 227   let (pasvhost, pasvport) = parsePasvString pasv_response-  ds <- liftIO $ connectTCP pasvhost (PortNum (hton_16 pasvport))-  dh <- liftIO $ socketToHandle ds iomode-  liftIO $ hSetBuffering h $ BlockBuffering Nothing-  release_data <- register $ liftIO $ hClose dh-  return (h, dh, path, release_control, release_data)-  where (host, port, user, pass) = case authority of+  d <- lift $ lift $ connectTCP (toString pasvhost) (PortNum (hton_16 pasvport))+  rd <- register $ sClose d+  return (c, rc, d, rd, path')+  where (host, port, user, pass) = case authority' of           Nothing -> undefined           Just (URIAuth userInfo regName port') ->             ( regName@@ -150,29 +150,7 @@             , if null userInfo then "anonymous" else takeWhile (\ l -> l /= ':' && l /= '@') userInfo             , if null userInfo || not (':' `elem` userInfo) then "" else init $ tail $ (dropWhile (/= ':')) userInfo             )-        parsePasvString s = (pasvhost, pasvport)-          where pasvhost = (show ip1) ++ "." ++ (show ip2) ++ "." ++ (show ip3) ++ "." ++ (show ip4)+        parsePasvString ps = (pasvhost, pasvport)+          where pasvhost = BS.init $ foldl (\ a ip -> a `BS.append` (fromString $ show ip) `BS.append` (fromString ".")) BS.empty [ip1, ip2, ip3, ip4]                 pasvport = (fromIntegral port1) `shiftL` 8 + (fromIntegral port2)-                (ip1, ip2, ip3, ip4, port1, port2) = read $ (++ ")") $ (takeWhile (/= ')')) $ (dropWhile (/= '(')) s :: (Int, Int, Int, Int, Int, Int)---- | Returns (Handle of the control connection, the Sink itself--- a destructor for the control connection, and a destructor for the data connection).------ The caller should use the handle to check for return codes, and release the ReleaseKeys when appropriate.-putFTPFile :: URI -> ResourceT (ErrorT FTPError IO) (Handle, (Sink ByteString (ErrorT FTPError IO) ()), ReleaseKey, ReleaseKey)-putFTPFile uri = do-  (h, dh, path, release_control, release_data) <- setupHandleForFTP uri WriteMode-  writeLine h $ "STOR " ++ path-  _ <- readExpected h 150-  return $ (h, sinkHandle dh, release_control, release_data)---- | Returns (Handle of the control connection, the Source itself,--- a destructor for the control connection, and a destructor for the data connection).------ The caller should use the handle to check for return codes, and release the ReleaseKeys when appropriate.-getFTPFile :: URI -> ResourceT (ErrorT FTPError IO) (Handle, (Source (ErrorT FTPError IO) ByteString), ReleaseKey, ReleaseKey)-getFTPFile uri = do-  (h, dh, path, release_control, release_data) <- setupHandleForFTP uri ReadMode-  writeLine h $ "RETR " ++ path-  _ <- readExpected h 150-  return $ (h, sourceHandle dh, release_control, release_data)+                (ip1, ip2, ip3, ip4, port1, port2) = read $ toString $ (`BS.append` (fromString ")")) $ (BS.takeWhile (/= 41)) $ (BS.dropWhile (/= 40)) ps :: (Int, Int, Int, Int, Int, Int)
ftp-conduit.cabal view
@@ -1,5 +1,5 @@ name:            ftp-conduit-version:         0.0.1+version:         0.0.2 license:         BSD3 license-file:    LICENSE author:          Myles C. Maxfield <myles.maxfield@gmail.com>@@ -21,6 +21,7 @@                  , MissingH              >= 0.18.6                  , mtl                   >= 1.0                  , byteorder             >= 1.0.0+                 , utf8-string           >= 0.3     exposed-modules: Network.FTP.Conduit     ghc-options:     -Wall