packages feed

ftp-conduit (empty) → 0.0.1

raw patch · 4 files changed

+237/−0 lines, 4 filesdep +MissingHdep +basedep +byteordersetup-changed

Dependencies added: MissingH, base, byteorder, bytestring, conduit, mtl, network

Files

+ LICENSE view
@@ -0,0 +1,25 @@+The following license covers this documentation, and the source code, except+where otherwise indicated.++Copyright 2010, Michael Snoyman. All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Network/FTP/Conduit.hs view
@@ -0,0 +1,178 @@+-- | 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+--+-- 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+  ) 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 Network.Utils+import Control.Monad.Error+import System.IO+import System.ByteOrder++-- | 'UnexpectedCode' has the form (Expected code, full response string)+data FTPError = UnexpectedCode Int String+                | GeneralError String+                | IncorrectScheme String+  deriving (Show)+instance Error FTPError where+  noMsg  = GeneralError ""+  strMsg = GeneralError++hton_16 :: Word16 -> Word16+hton_16 x = case byteOrder of+  BigEndian -> x+  LittleEndian -> x `shiftL` 8 + x `shiftR` 8+  _ -> undefined++extractCode :: String -> Int+extractCode = read . (takeWhile (/= ' '))++readExpected :: Handle -> Int -> ResourceT (ErrorT FTPError IO) String+readExpected h i = do+  line <- liftIO $ hGetLine h+  --liftIO $ putStrLn $ "Read: " ++ 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.++-- | 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++-- | 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++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 ()++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+  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+          Nothing -> undefined+          Just (URIAuth userInfo regName port') ->+            ( regName+            , if null port' then 21 else read (tail port')+            , 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)+                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)
+ Setup.lhs view
@@ -0,0 +1,4 @@+#! /usr/bin/env runhaskell++> import Distribution.Simple+> main = defaultMain
+ ftp-conduit.cabal view
@@ -0,0 +1,30 @@+name:            ftp-conduit+version:         0.0.1+license:         BSD3+license-file:    LICENSE+author:          Myles C. Maxfield <myles.maxfield@gmail.com>+maintainer:      Myles C. Maxfield <myles.maxfield@gmail.com>+synopsis:        FTP client package with conduit interface based off http-conduit+description:+    This package allows files on remote FTP servers to be available through the Conduit interface.+category:        Web, Conduit+stability:       Experimental+cabal-version:   >= 1.8+build-type:      Simple+homepage:        https://github.com/litherum/ftp-conduit++library+    build-depends: base                  >= 4       && < 5+                 , conduit               >= 0.1.1+                 , network               >= 2.0+                 , bytestring            >= 0.9+                 , MissingH              >= 0.18.6+                 , mtl                   >= 1.0+                 , byteorder             >= 1.0.0+    exposed-modules: Network.FTP.Conduit+    ghc-options:     -Wall+++source-repository head+  type:     git+  location: git://github.com/litherum/ftp-conduit.git