diff --git a/example/app/Main.hs b/example/app/Main.hs index 5d90df4..ae962e0 100644 --- a/example/app/Main.hs +++ b/example/app/Main.hs @@ -1,15 +1,13 @@ -module Main where +module Main (main) where import Network.FTP.Client import qualified Network.FTP.Client.Conduit as FC import Conduit -import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as C -import Data.Monoid ((<>)) main :: IO () -main = withFTPS "hostname.com" 21 $ \h welcome -> do - login h "username" "password" +main = withFTPS "hostname.com" 21 $ \h _welcome -> do + _ <- login h "username" "password" runConduitRes $ FC.mlsd h "." .| mapC (C.pack . (<> "\n") . show) diff --git a/example/example.cabal b/example/example.cabal index 809ad46..195d6db 100644 --- a/example/example.cabal +++ b/example/example.cabal @@ -19,21 +19,36 @@ source-repository head type: git location: https://github.com/flipstone/ftp-client +flag ci + description: More strict ghc options used for development and ci, not intended for end-use. + manual: True + default: False + executable example-exe main-is: Main.hs other-modules: Paths_example hs-source-dirs: app - ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base , bytestring , conduit - , containers , ftp-client , ftp-client-conduit default-language: Haskell2010 + if flag(ci) + ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N -Wall -Werror -Wcompat -Widentities -Winvalid-haddock -Wmissing-local-signatures -Wmissing-export-lists -Wpartial-fields -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe -Wunused-packages + if impl (ghc < 9.4) + ghc-options: -Wno-unticked-promoted-constructors + if impl (ghc >= 9.4) + ghc-options: -Wimplicit-lift -Woperator-whitespace -Wredundant-bang-patterns -Wredundant-strictness-flags + if impl (ghc >= 9.8) + ghc-options: -Wincomplete-export-warnings -Wmissing-poly-kind-signatures -Wterm-variable-capture + if impl (ghc >= 9.10) + ghc-options: -Wdefaulted-exception-context + else + ghc-options: -threaded -rtsopts -with-rtsopts=-N test-suite example-test type: exitcode-stdio-1.0 @@ -42,7 +57,18 @@ test-suite example-test Paths_example hs-source-dirs: test - ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base default-language: Haskell2010 + if flag(ci) + ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Werror -Wcompat -Widentities -Winvalid-haddock -Wmissing-local-signatures -Wmissing-export-lists -Wpartial-fields -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe + if impl (ghc < 9.4) + ghc-options: -Wno-unticked-promoted-constructors + if impl (ghc >= 9.4) + ghc-options: -Wimplicit-lift -Woperator-whitespace -Wredundant-bang-patterns -Wredundant-strictness-flags + if impl (ghc >= 9.8) + ghc-options: -Wincomplete-export-warnings -Wmissing-poly-kind-signatures -Wterm-variable-capture + if impl (ghc >= 9.10) + ghc-options: -Wdefaulted-exception-context + else + ghc-options: -threaded -rtsopts -with-rtsopts=-N diff --git a/example/package.yaml b/example/package.yaml index 4bdc1b1..e613db0 100644 --- a/example/package.yaml +++ b/example/package.yaml @@ -10,31 +10,114 @@ category: Web git: https://github.com/flipstone/ftp-client +flags: + ci: + description: More strict ghc options used for development and ci, not intended for end-use. + manual: true + default: false + executables: example-exe: main: Main.hs source-dirs: app language: Haskell2010 - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N dependencies: - base - ftp-client - ftp-client-conduit - conduit - bytestring - - containers + when: + - condition: flag(ci) + then: + ghc-options: + - -O2 + - -threaded + - -rtsopts + - -with-rtsopts=-N + - -Wall + - -Werror + - -Wcompat + - -Widentities + - -Winvalid-haddock + - -Wmissing-local-signatures + - -Wmissing-export-lists + - -Wpartial-fields + - -Wmissed-specialisations + - -Wno-implicit-prelude + - -Wno-safe + - -Wno-unsafe + - -Wunused-packages + when: + - condition: impl (ghc < 9.4) + ghc-options: + - -Wno-unticked-promoted-constructors + - condition: impl (ghc >= 9.4) + ghc-options: + - -Wimplicit-lift + - -Woperator-whitespace + - -Wredundant-bang-patterns + - -Wredundant-strictness-flags + - condition: impl (ghc >= 9.8) + ghc-options: + - -Wincomplete-export-warnings + - -Wmissing-poly-kind-signatures + - -Wterm-variable-capture + - condition: impl (ghc >= 9.10) + ghc-options: + - -Wdefaulted-exception-context + else: + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N tests: example-test: main: Spec.hs source-dirs: test language: Haskell2010 - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N dependencies: - base + when: + - condition: flag(ci) + then: + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + - -Wall + - -Werror + - -Wcompat + - -Widentities + - -Winvalid-haddock + - -Wmissing-local-signatures + - -Wmissing-export-lists + - -Wpartial-fields + - -Wmissed-specialisations + - -Wno-implicit-prelude + - -Wno-safe + - -Wno-unsafe + when: + - condition: impl (ghc < 9.4) + ghc-options: + - -Wno-unticked-promoted-constructors + - condition: impl (ghc >= 9.4) + ghc-options: + - -Wimplicit-lift + - -Woperator-whitespace + - -Wredundant-bang-patterns + - -Wredundant-strictness-flags + - condition: impl (ghc >= 9.8) + ghc-options: + - -Wincomplete-export-warnings + - -Wmissing-poly-kind-signatures + - -Wterm-variable-capture + - condition: impl (ghc >= 9.10) + ghc-options: + - -Wdefaulted-exception-context + else: + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N diff --git a/example/test/Spec.hs b/example/test/Spec.hs index cd4753f..356ebe8 100644 --- a/example/test/Spec.hs +++ b/example/test/Spec.hs @@ -1,2 +1,4 @@ +module Main (main) where + main :: IO () main = putStrLn "Test suite not yet implemented" diff --git a/ftp-client-conduit/CHANGELOG.md b/ftp-client-conduit/CHANGELOG.md index c63620f..7b2df8c 100644 --- a/ftp-client-conduit/CHANGELOG.md +++ b/ftp-client-conduit/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog for ftp-client-conduit +## 0.5.0.7 + +* Add the missing upper bounds on `bytestring`, `conduit` and `exceptions`. + +* Replace conduit's deprecated `Producer` and `Consumer` synonyms with + `ConduitT` in the exported signatures of `nlst`, `retr`, `list`, `stor` and + `mlsd`. The type variables are left free, so the exported types are unchanged + -- `Producer m o` is `forall i. ConduitT i o m ()`, not `ConduitT () o m ()`. + +* Raise the `conduit` lower bound to `>= 1.3`. `ConduitT` arrived in conduit + 1.3, so the previous `>= 1.1` allowed dependency selections that cannot + compile the signatures above. The bounds audit only added the missing upper + halves; this is the lower half that replacing `Producer` and `Consumer` + invalidated. + ## 0.5.0.6 * Correct the `resourcet` bound. `>= 1.2 && < 1.3` excluded `resourcet-1.3.0`, diff --git a/ftp-client-conduit/ftp-client-conduit.cabal b/ftp-client-conduit/ftp-client-conduit.cabal index dba243a..37197e0 100644 --- a/ftp-client-conduit/ftp-client-conduit.cabal +++ b/ftp-client-conduit/ftp-client-conduit.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: ftp-client-conduit -version: 0.5.0.6 +version: 0.5.0.7 synopsis: Transfer file with FTP and FTPS with Conduit description: ftp-client is a library for communicating with an FTP server. It works over both a clear channel or TLS. ftp-client-conduit uses conduit to stream files and data in constant space. category: Web @@ -30,6 +30,11 @@ source-repository head type: git location: https://github.com/flipstone/ftp-client +flag ci + description: More strict ghc options used for development and ci, not intended for end-use. + manual: True + default: False + library exposed-modules: Network.FTP.Client.Conduit @@ -38,17 +43,26 @@ library hs-source-dirs: src default-extensions: - RankNTypes OverloadedStrings build-depends: base >=4.16 && <5 - , bytestring - , conduit >=1.1 + , bytestring >=0.10.8.2 && <0.13 + , conduit ==1.3.* , crypton-connection >=0.3 && <0.5 - , exceptions + , exceptions >=0.10.3 && <0.11 , ftp-client ==0.5.* , resourcet >=1.2 && <1.4 default-language: Haskell2010 + if flag(ci) + ghc-options: -O2 -Wall -Werror -Wcompat -Widentities -Winvalid-haddock -Wmissing-local-signatures -Wmissing-export-lists -Wpartial-fields -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe -Wunused-packages + if impl (ghc < 9.4) + ghc-options: -Wno-unticked-promoted-constructors + if impl (ghc >= 9.4) + ghc-options: -Wimplicit-lift -Woperator-whitespace -Wredundant-bang-patterns -Wredundant-strictness-flags + if impl (ghc >= 9.8) + ghc-options: -Wincomplete-export-warnings -Wmissing-poly-kind-signatures -Wterm-variable-capture + if impl (ghc >= 9.10) + ghc-options: -Wdefaulted-exception-context test-suite ftp-conduit-test type: exitcode-stdio-1.0 @@ -57,8 +71,19 @@ test-suite ftp-conduit-test Paths_ftp_client_conduit hs-source-dirs: test - ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.16 && <5 , ftp-client-conduit default-language: Haskell2010 + if flag(ci) + ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Werror -Wcompat -Widentities -Winvalid-haddock -Wmissing-local-signatures -Wmissing-export-lists -Wpartial-fields -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe + if impl (ghc < 9.4) + ghc-options: -Wno-unticked-promoted-constructors + if impl (ghc >= 9.4) + ghc-options: -Wimplicit-lift -Woperator-whitespace -Wredundant-bang-patterns -Wredundant-strictness-flags + if impl (ghc >= 9.8) + ghc-options: -Wincomplete-export-warnings -Wmissing-poly-kind-signatures -Wterm-variable-capture + if impl (ghc >= 9.10) + ghc-options: -Wdefaulted-exception-context + else + ghc-options: -threaded -rtsopts -with-rtsopts=-N diff --git a/ftp-client-conduit/package.yaml b/ftp-client-conduit/package.yaml index df43bbb..ec1f36c 100644 --- a/ftp-client-conduit/package.yaml +++ b/ftp-client-conduit/package.yaml @@ -1,5 +1,5 @@ name: ftp-client-conduit -version: 0.5.0.6 +version: 0.5.0.7 synopsis: Transfer file with FTP and FTPS with Conduit description: ftp-client is a library for communicating with an FTP server. It works over both a clear channel or TLS. ftp-client-conduit uses conduit to stream files and data in constant space. homepage: https://github.com/flipstone/ftp-client @@ -23,32 +23,113 @@ extra-source-files: git: https://github.com/flipstone/ftp-client +flags: + ci: + description: More strict ghc options used for development and ci, not intended for end-use. + manual: true + default: false + library: source-dirs: src exposed-modules: - Network.FTP.Client.Conduit language: Haskell2010 default-extensions: - - RankNTypes - OverloadedStrings dependencies: - base >= 4.16 && < 5 - ftp-client == 0.5.* - - conduit >= 1.1 - - bytestring + - conduit >= 1.3 && < 1.4 + - bytestring >= 0.10.8.2 && < 0.13 - resourcet >= 1.2 && < 1.4 - crypton-connection >= 0.3 && < 0.5 - - exceptions + - exceptions >= 0.10.3 && < 0.11 + when: + - condition: flag(ci) + ghc-options: + - -O2 + - -Wall + - -Werror + - -Wcompat + - -Widentities + - -Winvalid-haddock + - -Wmissing-local-signatures + - -Wmissing-export-lists + - -Wpartial-fields + - -Wmissed-specialisations + - -Wno-implicit-prelude + - -Wno-safe + - -Wno-unsafe + - -Wunused-packages + when: + # Previously the guidance was to always use ticked constructors for type + # constructors, but that guidance has been reversed, so we turn the + # warning off in older compilers. + - condition: impl (ghc < 9.4) + ghc-options: + - -Wno-unticked-promoted-constructors + - condition: impl (ghc >= 9.4) + ghc-options: + - -Wimplicit-lift + - -Woperator-whitespace + - -Wredundant-bang-patterns + - -Wredundant-strictness-flags + - condition: impl (ghc >= 9.8) + ghc-options: + - -Wincomplete-export-warnings + - -Wmissing-poly-kind-signatures + - -Wterm-variable-capture + - condition: impl (ghc >= 9.10) + ghc-options: + - -Wdefaulted-exception-context tests: ftp-conduit-test: main: Spec.hs source-dirs: test language: Haskell2010 - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N dependencies: - base >= 4.16 && < 5 - ftp-client-conduit + when: + - condition: flag(ci) + then: + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + - -Wall + - -Werror + - -Wcompat + - -Widentities + - -Winvalid-haddock + - -Wmissing-local-signatures + - -Wmissing-export-lists + - -Wpartial-fields + - -Wmissed-specialisations + - -Wno-implicit-prelude + - -Wno-safe + - -Wno-unsafe + when: + - condition: impl (ghc < 9.4) + ghc-options: + - -Wno-unticked-promoted-constructors + - condition: impl (ghc >= 9.4) + ghc-options: + - -Wimplicit-lift + - -Woperator-whitespace + - -Wredundant-bang-patterns + - -Wredundant-strictness-flags + - condition: impl (ghc >= 9.8) + ghc-options: + - -Wincomplete-export-warnings + - -Wmissing-poly-kind-signatures + - -Wterm-variable-capture + - condition: impl (ghc >= 9.10) + ghc-options: + - -Wdefaulted-exception-context + else: + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N diff --git a/ftp-client-conduit/src/Network/FTP/Client/Conduit.hs b/ftp-client-conduit/src/Network/FTP/Client/Conduit.hs index 39955fd..8b9dc17 100644 --- a/ftp-client-conduit/src/Network/FTP/Client/Conduit.hs +++ b/ftp-client-conduit/src/Network/FTP/Client/Conduit.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE ScopedTypeVariables #-} + {-| Module : Network.FTP.Client Description : Transfer files over FTP and FTPS with Conduit @@ -14,23 +16,21 @@ module Network.FTP.Client.Conduit ( mlsd ) where -import Conduit -import Control.Monad.IO.Class +-- MonadResource appears in this module's exported signatures, so it is taken +-- from resourcet directly rather than through Conduit's re-export. That keeps +-- the resourcet dependency honest instead of implicit. +import Conduit hiding (MonadResource) +import Control.Monad.Trans.Resource (MonadResource) import Data.ByteString.Lazy.Internal (defaultChunkSize) import System.IO import Network.FTP.Client - ( sendCommand - , sendCommandS - , sendAll - , sendAllS + ( sendCommandS , FTPCommand(..) , RTypeCode(..) - , getLineResp , createSendDataCommand , createTLSSendDataCommand , PortActivity(..) , getResponse - , getResponseS , sIOHandleImpl , tlsHandleImpl , Security(..) @@ -40,9 +40,6 @@ import Network.FTP.Client import qualified Network.FTP.Client as FTP import qualified Data.ByteString as B import Data.ByteString (ByteString) -import Control.Monad.Trans.Resource -import Data.Monoid ((<>)) -import System.IO.Error import Network.Connection import qualified Control.Monad.Catch as M @@ -50,17 +47,21 @@ debugging :: Bool debugging = False debugPrint :: (Show a, MonadIO m) => a -> m () -debugPrint s = debugPrint' s debugging - where - debugPrint' _ False = return () - debugPrint' s True = liftIO $ print s +debugPrint s = + if debugging + then liftIO $ print s + else return () debugResponse :: (Show a, MonadIO m) => a -> m () debugResponse s = debugPrint $ "Recieved: " <> (show s) -getAllLineRespC :: MonadIO m => FTP.Handle -> Producer m ByteString -getAllLineRespC h = loop - where +getAllLineRespC + :: forall i m + . MonadIO m + => FTP.Handle + -> ConduitT i ByteString m () +getAllLineRespC h = + let loop :: ConduitT i ByteString m () loop = do line <- liftIO $ FTP.getLineResp h `M.catchIOError` const (return "") @@ -69,10 +70,15 @@ getAllLineRespC h = loop else do yield line loop + in loop -sendAllLineC :: MonadIO m => FTP.Handle -> Consumer ByteString m () -sendAllLineC h = loop - where +sendAllLineC + :: forall o m + . MonadIO m + => FTP.Handle + -> ConduitT ByteString o m () +sendAllLineC h = + let loop :: ConduitT ByteString o m () loop = do mx <- await case mx of @@ -80,6 +86,7 @@ sendAllLineC h = loop Just x -> do liftIO $ FTP.sendLine h x loop + in loop sourceDataCommandSecurity :: MonadResource m @@ -103,7 +110,7 @@ sourceDataCommand -> (FTP.Handle -> ConduitM i o m r) -> ConduitM i o m r sourceDataCommand ch pa code cmd f = do - sendCommandS ch $ RType code + _ <- sendCommandS ch $ RType code x <- bracketP (createSendDataCommand ch pa cmd) (liftIO . hClose) @@ -121,7 +128,7 @@ sourceTLSDataCommand -> (FTP.Handle -> ConduitM i o m r) -> ConduitM i o m r sourceTLSDataCommand ch pa code cmd f = do - sendCommandS ch $ RType code + _ <- sendCommandS ch $ RType code x <- bracketP (createTLSSendDataCommand ch pa cmd) (liftIO . connectionClose) @@ -130,9 +137,13 @@ sourceTLSDataCommand ch pa code cmd f = do debugResponse resp return x -sourceFTPHandle :: MonadIO m => FTP.Handle -> Producer m ByteString -sourceFTPHandle h = loop - where +sourceFTPHandle + :: forall i m + . MonadIO m + => FTP.Handle + -> ConduitT i ByteString m () +sourceFTPHandle h = + let loop :: ConduitT i ByteString m () loop = do bs <- liftIO $ FTP.recv h defaultChunkSize `M.catchIOError` const (return "") @@ -141,10 +152,15 @@ sourceFTPHandle h = loop else do yield bs loop + in loop -sinkFTPHandle :: MonadIO m => FTP.Handle -> Consumer ByteString m () -sinkFTPHandle h = loop - where +sinkFTPHandle + :: forall o m + . MonadIO m + => FTP.Handle + -> ConduitT ByteString o m () +sinkFTPHandle h = + let loop :: ConduitT ByteString o m () loop = do mbs <- await case mbs of @@ -152,24 +168,25 @@ sinkFTPHandle h = loop Just bs -> do liftIO $ FTP.send h bs loop + in loop sendType :: MonadResource m => RTypeCode -> FTP.Handle - -> Consumer ByteString m () + -> ConduitT ByteString o m () sendType TA h = sendAllLineC h sendType TI h = sinkFTPHandle h -nlst :: MonadResource m => FTP.Handle -> [String] -> Producer m ByteString +nlst :: MonadResource m => FTP.Handle -> [String] -> ConduitT i ByteString m () nlst ch args = sourceDataCommandSecurity ch Passive TA (Nlst args) getAllLineRespC -retr :: MonadResource m => FTP.Handle -> String -> Producer m ByteString +retr :: MonadResource m => FTP.Handle -> String -> ConduitT i ByteString m () retr ch path = sourceDataCommandSecurity ch Passive TI (Retr path) sourceFTPHandle -list :: MonadResource m => FTP.Handle -> [String] -> Producer m ByteString +list :: MonadResource m => FTP.Handle -> [String] -> ConduitT i ByteString m () list ch args = sourceDataCommandSecurity ch Passive TA (List args) getAllLineRespC @@ -178,7 +195,7 @@ stor => FTP.Handle -> String -> RTypeCode - -> Consumer ByteString m () + -> ConduitT ByteString o m () stor ch loc rtype = sourceDataCommandSecurity ch Passive rtype (Stor loc) $ sendType rtype @@ -186,7 +203,7 @@ mlsd :: MonadResource m => FTP.Handle -> String - -> Producer m FTP.MlsxResponse + -> ConduitT i FTP.MlsxResponse m () mlsd ch dir = sourceDataCommandSecurity ch Passive TA (Mlsd dir) getAllLineRespC .| mapC parseMlsxLine diff --git a/ftp-client-conduit/test/Spec.hs b/ftp-client-conduit/test/Spec.hs index cd4753f..9531f64 100644 --- a/ftp-client-conduit/test/Spec.hs +++ b/ftp-client-conduit/test/Spec.hs @@ -1,2 +1,6 @@ +module Main (main) where + +import Network.FTP.Client.Conduit () + main :: IO () main = putStrLn "Test suite not yet implemented" diff --git a/ftp-client/CHANGELOG.md b/ftp-client/CHANGELOG.md index 8ddf20f..53f7ef4 100644 --- a/ftp-client/CHANGELOG.md +++ b/ftp-client/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog for ftp-client +## 0.5.3.0 + +* Export `acct`, `pbsz`, `prot`, `ccc` and `auth`. These command wrappers were + defined but never exported, unlike every other command wrapper in the module. + +* Drop the `transformers` dependency. The only module it supplied, + `Control.Monad.IO.Class`, has been in `base` since 4.9. + +* Stop deriving `Typeable` for `FTPException`. It has been a no-op since GHC + 7.10 and GHC 9.12 warns about it. + ## 0.5.2.0 * Expose `createSIOHandle`, `createTLSConnection` and `connectTLS` so callers can diff --git a/ftp-client/ftp-client.cabal b/ftp-client/ftp-client.cabal index b658910..902d7d8 100644 --- a/ftp-client/ftp-client.cabal +++ b/ftp-client/ftp-client.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: ftp-client -version: 0.5.2.0 +version: 0.5.3.0 synopsis: Transfer files with FTP and FTPS description: ftp-client is a library for communicating with an FTP server. It works over both a clear channel or TLS. category: Web @@ -30,6 +30,11 @@ source-repository head type: git location: https://github.com/flipstone/ftp-client +flag ci + description: More strict ghc options used for development and ci, not intended for end-use. + manual: True + default: False + library exposed-modules: Network.FTP.Client @@ -48,8 +53,17 @@ library , data-default-class >=0.1.2.0 && <0.3 , exceptions >=0.10.3 && <0.11 , network >=2.6.3.6 && <3.3 - , transformers >=0.5.6.2 && <0.7 default-language: Haskell2010 + if flag(ci) + ghc-options: -O2 -Wall -Werror -Wcompat -Widentities -Winvalid-haddock -Wmissing-local-signatures -Wmissing-export-lists -Wpartial-fields -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe -Wunused-packages + if impl (ghc < 9.4) + ghc-options: -Wno-unticked-promoted-constructors + if impl (ghc >= 9.4) + ghc-options: -Wimplicit-lift -Woperator-whitespace -Wredundant-bang-patterns -Wredundant-strictness-flags + if impl (ghc >= 9.8) + ghc-options: -Wincomplete-export-warnings -Wmissing-poly-kind-signatures -Wterm-variable-capture + if impl (ghc >= 9.10) + ghc-options: -Wdefaulted-exception-context test-suite ftp-client-test type: exitcode-stdio-1.0 @@ -58,10 +72,21 @@ test-suite ftp-client-test Paths_ftp_client hs-source-dirs: test - ghc-options: -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.16 && <5 , bytestring >=0.10.8.2 && <0.13 , ftp-client , hspec >=2.7 default-language: Haskell2010 + if flag(ci) + ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Werror -Wcompat -Widentities -Winvalid-haddock -Wmissing-local-signatures -Wmissing-export-lists -Wpartial-fields -Wmissed-specialisations -Wno-implicit-prelude -Wno-safe -Wno-unsafe + if impl (ghc < 9.4) + ghc-options: -Wno-unticked-promoted-constructors + if impl (ghc >= 9.4) + ghc-options: -Wimplicit-lift -Woperator-whitespace -Wredundant-bang-patterns -Wredundant-strictness-flags + if impl (ghc >= 9.8) + ghc-options: -Wincomplete-export-warnings -Wmissing-poly-kind-signatures -Wterm-variable-capture + if impl (ghc >= 9.10) + ghc-options: -Wdefaulted-exception-context + else + ghc-options: -threaded -rtsopts -with-rtsopts=-N diff --git a/ftp-client/package.yaml b/ftp-client/package.yaml index 72493b2..4d71c6b 100644 --- a/ftp-client/package.yaml +++ b/ftp-client/package.yaml @@ -1,5 +1,5 @@ name: ftp-client -version: 0.5.2.0 +version: 0.5.3.0 synopsis: Transfer files with FTP and FTPS description: ftp-client is a library for communicating with an FTP server. It works over both a clear channel or TLS. homepage: https://github.com/flipstone/ftp-client @@ -23,6 +23,12 @@ extra-source-files: git: https://github.com/flipstone/ftp-client +flags: + ci: + description: More strict ghc options used for development and ci, not intended for end-use. + manual: true + default: false + library: source-dirs: src exposed-modules: @@ -36,22 +42,100 @@ library: - network >= 2.6.3.6 && < 3.3 - attoparsec >= 0.10 && < 0.15 - crypton-connection >= 0.3 && < 0.5 - - transformers >= 0.5.6.2 && < 0.7 - exceptions >= 0.10.3 && < 0.11 - containers >= 0.5.11.0 && < 0.8 - data-default-class >= 0.1.2.0 && < 0.3 + when: + - condition: flag(ci) + ghc-options: + - -O2 + - -Wall + - -Werror + - -Wcompat + - -Widentities + - -Winvalid-haddock + - -Wmissing-local-signatures + - -Wmissing-export-lists + - -Wpartial-fields + - -Wmissed-specialisations + - -Wno-implicit-prelude + - -Wno-safe + - -Wno-unsafe + - -Wunused-packages + when: + # Previously the guidance was to always use ticked constructors for type + # constructors, but that guidance has been reversed, so we turn the + # warning off in older compilers. + - condition: impl (ghc < 9.4) + ghc-options: + - -Wno-unticked-promoted-constructors + - condition: impl (ghc >= 9.4) + ghc-options: + - -Wimplicit-lift + - -Woperator-whitespace + - -Wredundant-bang-patterns + - -Wredundant-strictness-flags + - condition: impl (ghc >= 9.8) + ghc-options: + - -Wincomplete-export-warnings + - -Wmissing-poly-kind-signatures + - -Wterm-variable-capture + - condition: impl (ghc >= 9.10) + ghc-options: + - -Wdefaulted-exception-context tests: ftp-client-test: main: test.hs source-dirs: test language: Haskell2010 - ghc-options: - - -threaded - - -rtsopts - - -with-rtsopts=-N dependencies: - base >= 4.16 && < 5 - bytestring >= 0.10.8.2 && < 0.13 - hspec >= 2.7 - ftp-client + when: + - condition: flag(ci) + then: + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + - -Wall + - -Werror + - -Wcompat + - -Widentities + - -Winvalid-haddock + - -Wmissing-local-signatures + - -Wmissing-export-lists + - -Wpartial-fields + - -Wmissed-specialisations + - -Wno-implicit-prelude + - -Wno-safe + - -Wno-unsafe + when: + # Previously the guidance was to always use ticked constructors for type + # constructors, but that guidance has been reversed, so we turn the + # warning off in older compilers. + - condition: impl (ghc < 9.4) + ghc-options: + - -Wno-unticked-promoted-constructors + - condition: impl (ghc >= 9.4) + ghc-options: + - -Wimplicit-lift + - -Woperator-whitespace + - -Wredundant-bang-patterns + - -Wredundant-strictness-flags + - condition: impl (ghc >= 9.8) + ghc-options: + - -Wincomplete-export-warnings + - -Wmissing-poly-kind-signatures + - -Wterm-variable-capture + - condition: impl (ghc >= 9.10) + ghc-options: + - -Wdefaulted-exception-context + else: + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N diff --git a/ftp-client/src/Network/FTP/Client.hs b/ftp-client/src/Network/FTP/Client.hs index a9d894a..e1e33a9 100644 --- a/ftp-client/src/Network/FTP/Client.hs +++ b/ftp-client/src/Network/FTP/Client.hs @@ -16,6 +16,7 @@ module Network.FTP.Client ( dele, cwd, size, + acct, mkd, rmd, pwd, @@ -38,6 +39,11 @@ module Network.FTP.Client ( ProtType(..), Security(..), Handle(..), + -- * TLS Commands + pbsz, + prot, + ccc, + auth, -- * Exceptions FTPException(..), -- * System Handle Creation @@ -66,11 +72,10 @@ import Data.Default.Class (def) import qualified Data.ByteString.Char8 as C import qualified Data.ByteString as B import Data.ByteString (ByteString) -import Data.List +import Data.List (intercalate) import Data.Attoparsec.ByteString.Char8 import qualified Network.Socket as S import qualified System.IO as SIO -import Data.Monoid ((<>)) import Control.Exception import Control.Monad.Catch (MonadCatch, MonadMask) import qualified Control.Monad.Catch as M @@ -79,12 +84,9 @@ import Control.Monad.IO.Class import Data.Bits import Network.Connection import Data.ByteString.Lazy.Internal (defaultChunkSize) -import Data.Functor ((<$>)) -import Control.Applicative ((<*>)) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Control.Arrow -import Data.Typeable import System.IO.Error (isEOFError) debugging :: Bool @@ -139,7 +141,7 @@ data FTPException | UnsuccessfulException FTPResponse | BogusResponseFormatException FTPResponse | BadProtocolResponseException ByteString - deriving (Show, Typeable) + deriving (Show) instance Exception FTPException @@ -206,7 +208,7 @@ formatPort ha pn = serializeCommand :: FTPCommand -> String serializeCommand (User user) = "USER " <> user serializeCommand (Pass pass) = "PASS " <> pass -serializeCommand (Acct acct) = "ACCT " <> acct +serializeCommand (Acct account) = "ACCT " <> account serializeCommand (RType rt) = "TYPE " <> serialzeRTypeCode rt serializeCommand (Retr file) = "RETR " <> file serializeCommand (Nlst []) = "NLST" @@ -267,10 +269,10 @@ getResponse h = do Just ('-', _) -> MultiLine <$> loopMultiLine h code [line] _ -> return $ SingleLine line let codeDroppedMessage = case message of - SingleLine message -> SingleLine $ C.drop 4 message + SingleLine singleMessage -> SingleLine $ C.drop 4 singleMessage MultiLine [] -> MultiLine [] - MultiLine (message:messages) -> - MultiLine $ C.drop 4 message : messages + MultiLine (firstMessage:messages) -> + MultiLine $ C.drop 4 firstMessage : messages let response = FTPResponse (responseStatus code) (read $ C.unpack code) @@ -286,7 +288,7 @@ loopMultiLine -> ByteString -> [ByteString] -> m [ByteString] -loopMultiLine h code lines = do +loopMultiLine h code priorLines = do mNextLine <- liftIO $ getLineRespMaybe h case mNextLine of -- The server hung up before sending the terminating line. Stop rather @@ -300,13 +302,13 @@ loopMultiLine h code lines = do -- lines of a multiline reply hold arbitrary text, blank lines included, -- so a blank line has to be kept and the loop has to continue past it. Nothing -> liftIO $ throwIO $ BadProtocolResponseException - $ C.intercalate "\n" lines + $ C.intercalate "\n" priorLines Just nextLine -> do -- RFC 959 (https://datatracker.ietf.org/doc/html/rfc959#page-36) ends a -- multiline reply with the code followed by a space, and continues it -- with the code followed by a hyphen. The bare code is accepted too, -- for servers that omit the trailing space on an empty final line. - let newLines = lines <> [C.dropWhile (== ' ') nextLine] + let newLines = priorLines <> [C.dropWhile (== ' ') nextLine] isLastLine = nextLine == code || C.isPrefixOf (code <> " ") nextLine @@ -385,9 +387,9 @@ withSocketPassive host portNum f = do (createSocket (Just host) portNum hints) (liftIO . S.close . fst) (\(sock, addr) -> do - debugPrint "Connecting" + debugPrint ("Connecting" :: String) liftIO $ S.connect sock (S.addrAddress addr) - debugPrint "Connected" + debugPrint ("Connected" :: String) f sock ) @@ -401,10 +403,10 @@ withSocketActive f = do (createSocket Nothing 0 hints) (liftIO . S.close . fst) (\(sock, addr) -> do - debugPrint "Binding" + debugPrint ("Binding" :: String) liftIO $ S.bind sock (S.addrAddress addr) liftIO $ S.listen sock 1 - debugPrint "Listening" + debugPrint ("Listening" :: String) f sock ) @@ -471,9 +473,9 @@ withDataSocketActive -> m a withDataSocketActive h f = withSocketActive $ \socket -> do (sPort, sHost) <- liftIO $ do - (S.SockAddrInet p h) <- S.getSocketName socket - return (p,h) - port h sHost sPort + (S.SockAddrInet p hostAddr) <- S.getSocketName socket + return (p,hostAddr) + _ <- port h sHost sPort f socket -- | Open a socket that can be used for data transfers @@ -527,7 +529,7 @@ withDataCommand -> (Handle -> m a) -> m a withDataCommand ch pa code cmd f = do - sendCommandS ch $ RType code + _ <- sendCommandS ch $ RType code x <- M.bracket (createSendDataCommand ch pa cmd) (liftIO . SIO.hClose) @@ -538,32 +540,35 @@ withDataCommand ch pa code cmd f = do -- | Recieve data and interpret it linewise getAllLineResp :: (MonadIO m, MonadCatch m) => Handle -> m ByteString -getAllLineResp h = getAllLineResp' h [] - where - getAllLineResp' h ret = ( do +getAllLineResp h = + let collect :: (MonadIO n, MonadCatch n) => [ByteString] -> n ByteString + collect ret = ( do line <- liftIO $ getLineResp h - getAllLineResp' h (ret <> [line])) + collect (ret <> [line])) `M.catchIOError` (\_ -> return $ C.intercalate "\n" ret) + in collect [] -- | Recieve all data and return it as a 'Data.ByteString.ByteString' recvAll :: (MonadIO m, MonadCatch m) => Handle -> m ByteString -recvAll h = recvAll' "" - where - recvAll' bs = ( do +recvAll h = + let collect :: (MonadIO n, MonadCatch n) => ByteString -> n ByteString + collect bs = ( do chunk <- liftIO $ recv h defaultChunkSize if C.null chunk then return bs - else recvAll' $ bs <> chunk + else collect $ bs <> chunk ) `M.catchIOError` (\_ -> return bs) + in collect "" -- TLS connection connectTLS :: MonadIO m => SIO.Handle -> String -> Int -> m Connection connectTLS h host portNum = do context <- liftIO initConnectionContext - let tlsSettings = def - { settingDisableCertificateValidation = True - } + let tlsSettings = case def of + simpleSettings@TLSSettingsSimple{} -> + simpleSettings { settingDisableCertificateValidation = True } + otherSettings -> otherSettings connectionParams = ConnectionParams { connectionHostname = host , connectionPort = toEnum . fromEnum $ portNum @@ -581,7 +586,7 @@ createTLSConnection host portNum = do h <- createSIOHandle host portNum let insecureH = sIOHandleImpl h resp <- getResponse insecureH - sendCommand insecureH Auth + _ <- sendCommand insecureH Auth conn <- connectTLS h host portNum return (resp, conn) @@ -633,7 +638,7 @@ createTLSSendDataCommand -> FTPCommand -> m Connection createTLSSendDataCommand ch pa cmd = do - sendAllS ch [Pbsz 0, Prot P] + _ <- sendAllS ch [Pbsz 0, Prot P] withDataSocket pa ch $ \socket -> do resp <- sendCommand ch cmd ensureSucessfulData ch resp @@ -655,7 +660,7 @@ withTLSDataCommand -> (Handle -> m a) -> m a withTLSDataCommand ch pa code cmd f = do - sendCommandS ch $ RType code + _ <- sendCommandS ch $ RType code x <- M.bracket (createTLSSendDataCommand ch pa cmd) (liftIO . connectionClose) @@ -681,7 +686,7 @@ ensureCode resp code = parse227 :: Parser (String, Int) parse227 = do - skipWhile (/= '(') *> char '(' + _ <- skipWhile (/= '(') *> char '(' [h1,h2,h3,h4,p1,p2] <- many1 digit `sepBy` char ',' let host = intercalate "." [h1,h2,h3,h4] highBits = read p1 @@ -691,7 +696,7 @@ parse227 = do parse257 :: Parser String parse257 = do - char '"' + _ <- char '"' C.unpack <$> takeTill (== '"') -- Control commands @@ -837,15 +842,16 @@ parseMlsxLine line = in MlsxResponse (C.unpack filename) facts getMlsxResponse :: (MonadIO m, MonadCatch m) => Handle -> m [MlsxResponse] -getMlsxResponse h = getMlsxResponse' h [] - where - getMlsxResponse' h ret = ( do +getMlsxResponse h = + let collect :: (MonadIO n, MonadCatch n) => [MlsxResponse] -> n [MlsxResponse] + collect ret = ( do line <- liftIO $ getLineResp h - getMlsxResponse' h $ + collect $ if C.null line then ret else parseMlsxLine line : ret ) `M.catchIOError` (\_ -> return ret) + in collect [] mlsd :: (MonadIO m, MonadMask m) => Handle -> String -> m [MlsxResponse] mlsd h path = withDataCommandSecurity h Passive TA (Mlsd path) getMlsxResponse diff --git a/ftp-client/test/test.hs b/ftp-client/test/test.hs index c4738b9..aac5058 100644 --- a/ftp-client/test/test.hs +++ b/ftp-client/test/test.hs @@ -1,16 +1,17 @@ +module Main (main) where + import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as C import Test.Hspec import Network.FTP.Client hiding (Success) import qualified Network.FTP.Client as F -import Control.Monad.IO.Class import Control.Concurrent.MVar import System.IO.Error (eofErrorType, mkIOError) data TestHandleMVars = TestHandleMVars { thmSend :: MVar [ByteString] - , thmSendLine :: MVar [ByteString] - , thmRecv :: MVar [Int] + , _thmSendLine :: MVar [ByteString] + , _thmRecv :: MVar [Int] } data TestHandle = TestHandle TestHandleMVars Handle @@ -156,7 +157,7 @@ main = hspec $ do describe "Network.FTP.Client.recvAll" $ it "doesn't hang on empty response" $ do let expected = C.pack "" - (TestHandle mvars h) <- testHandle [C.pack ""] [] Clear + (TestHandle _ h) <- testHandle [C.pack ""] [] Clear recvAll h `shouldReturn` expected isBadProtocolResponse :: FTPException -> Bool diff --git a/stack-base.yaml b/stack-base.yaml index b7fa0aa..f622551 100644 --- a/stack-base.yaml +++ b/stack-base.yaml @@ -3,4 +3,12 @@ packages: - 'ftp-client-conduit' - 'example' +flags: + ftp-client: + ci: true + ftp-client-conduit: + ci: true + example: + ci: true + pvp-bounds: both