From 2bcad9370938060957a6ebfcdacbf392bc510034 Mon Sep 17 00:00:00 2001 From: pucsdian <57708584+pucsdian@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:38:33 +0530 Subject: [PATCH 1/5] expose createSIOHandle and createTLSConnection --- ftp-client/src/Network/FTP/Client.hs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ftp-client/src/Network/FTP/Client.hs b/ftp-client/src/Network/FTP/Client.hs index 2b01e29..652ce11 100644 --- a/ftp-client/src/Network/FTP/Client.hs +++ b/ftp-client/src/Network/FTP/Client.hs @@ -40,6 +40,9 @@ module Network.FTP.Client ( Handle(..), -- * Exceptions FTPException(..), + -- * System Handle Creation + createSIOHandle, + createTLSConnection, -- * Handle Implementations sIOHandleImpl, tlsHandleImpl, From 8e1f2050cc4a5f93c938a3fc6c13287f2cceaec2 Mon Sep 17 00:00:00 2001 From: pucsdian <57708584+pucsdian@users.noreply.github.com> Date: Tue, 1 Apr 2025 18:12:13 +0530 Subject: [PATCH 2/5] fix issue in reading multi line response --- ftp-client/src/Network/FTP/Client.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ftp-client/src/Network/FTP/Client.hs b/ftp-client/src/Network/FTP/Client.hs index 652ce11..59f9f35 100644 --- a/ftp-client/src/Network/FTP/Client.hs +++ b/ftp-client/src/Network/FTP/Client.hs @@ -302,8 +302,8 @@ loopMultiLine h code lines = do $ C.intercalate "\n" lines Just nextLine -> do let newLines = lines <> [C.dropWhile (== ' ') nextLine] - nextCode = C.take 3 nextLine - if nextCode == code + isLastLine = C.isPrefixOf (code <> " ") nextLine -- Ref for reading multiline response : https://datatracker.ietf.org/doc/html/rfc959#page-36 + if isLastLine then return newLines else loopMultiLine h code newLines From 82bd151a9132127dc0bcdb838f9d221f5f497832 Mon Sep 17 00:00:00 2001 From: pucsdian <57708584+pucsdian@users.noreply.github.com> Date: Tue, 1 Apr 2025 18:13:38 +0530 Subject: [PATCH 3/5] expose connectTLS --- ftp-client/src/Network/FTP/Client.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/ftp-client/src/Network/FTP/Client.hs b/ftp-client/src/Network/FTP/Client.hs index 59f9f35..803c387 100644 --- a/ftp-client/src/Network/FTP/Client.hs +++ b/ftp-client/src/Network/FTP/Client.hs @@ -43,6 +43,7 @@ module Network.FTP.Client ( -- * System Handle Creation createSIOHandle, createTLSConnection, + connectTLS, -- * Handle Implementations sIOHandleImpl, tlsHandleImpl, From 9e0b58f56ca1b3fcf313aeff722fa091df10c9a3 Mon Sep 17 00:00:00 2001 From: Paul Burns Date: Tue, 18 Aug 2026 18:25:49 -0400 Subject: [PATCH 4/5] Test multiline response parsing, tolerate a bare closing code Adds the cases that distinguish the new terminator check from the old one. The multiline test that has been here since 277d621 passes either way, so it never covered this: its continuation line does not repeat the code. Requiring the code followed by a space is what RFC 959 specifies, but it regressed servers whose final line is the bare code with no trailing space. Those terminated under the old prefix comparison and would now loop until the socket closed. Accept both forms. --- ftp-client/src/Network/FTP/Client.hs | 8 +++++- ftp-client/test/test.hs | 37 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/ftp-client/src/Network/FTP/Client.hs b/ftp-client/src/Network/FTP/Client.hs index 803c387..a9d894a 100644 --- a/ftp-client/src/Network/FTP/Client.hs +++ b/ftp-client/src/Network/FTP/Client.hs @@ -302,8 +302,14 @@ loopMultiLine h code lines = do Nothing -> liftIO $ throwIO $ BadProtocolResponseException $ C.intercalate "\n" lines 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] - isLastLine = C.isPrefixOf (code <> " ") nextLine -- Ref for reading multiline response : https://datatracker.ietf.org/doc/html/rfc959#page-36 + isLastLine = + nextLine == code + || C.isPrefixOf (code <> " ") nextLine if isLastLine then return newLines else loopMultiLine h code newLines diff --git a/ftp-client/test/test.hs b/ftp-client/test/test.hs index f5b3e28..c4738b9 100644 --- a/ftp-client/test/test.hs +++ b/ftp-client/test/test.hs @@ -108,6 +108,43 @@ main = hspec $ do , C.pack "220 Third Line\r\n" ] Clear getResponse h `shouldReturn` expected + it "keeps reading continuation lines that repeat the code" $ do + let expected = FTPResponse + F.Success 220 + (MultiLine + [ C.pack "First Line" + , C.pack "220-Second Line" + , C.pack "220 Third Line" + ]) + (TestHandle _ h) <- testHandle [] + [ C.pack "220-First Line\r\n" + , C.pack "220-Second Line\r\n" + , C.pack "220 Third Line\r\n" + ] Clear + getResponse h `shouldReturn` expected + it "ends a multiline response on a bare code" $ do + let expected = FTPResponse + F.Success 220 + (MultiLine [C.pack "First Line", C.pack "220"]) + (TestHandle _ h) <- testHandle [] + [ C.pack "220-First Line\r\n" + , C.pack "220\r\n" + ] Clear + getResponse h `shouldReturn` expected + it "does not end a multiline response on a different code" $ do + let expected = FTPResponse + F.Success 220 + (MultiLine + [ C.pack "First Line" + , C.pack "331 Not the terminator" + , C.pack "220 Done" + ]) + (TestHandle _ h) <- testHandle [] + [ C.pack "220-First Line\r\n" + , C.pack "331 Not the terminator\r\n" + , C.pack "220 Done\r\n" + ] Clear + getResponse h `shouldReturn` expected it "rejects a multiline response the server never finished" $ do -- Terminating rather than hanging is only half of it. Handing back -- the fragment would report a successful 220 for a greeting that From 1fbc52659cb6009254a52d7eabd52c0511ae01ec Mon Sep 17 00:00:00 2001 From: Paul Burns Date: Tue, 18 Aug 2026 18:31:40 -0400 Subject: [PATCH 5/5] Version 0.5.2.0, add a changelog The three newly exported functions are an additive API change, so PVP asks for a minor bump. This stays inside the ftp-client == 0.5.* bound that ftp-client-conduit depends on, which therefore needs no bump of its own. The package has never carried a changelog. Seed one covering this release and point at git history for anything earlier. --- ftp-client/CHANGELOG.md | 14 ++++++++++++++ ftp-client/ftp-client.cabal | 2 +- ftp-client/package.yaml | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ftp-client/CHANGELOG.md b/ftp-client/CHANGELOG.md index 48cf595..8ddf20f 100644 --- a/ftp-client/CHANGELOG.md +++ b/ftp-client/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog for ftp-client +## 0.5.2.0 + +* Expose `createSIOHandle`, `createTLSConnection` and `connectTLS` so callers can + manage the handle lifecycle themselves rather than going through `withFTP` and + `withFTPS`. Thanks to @pucsdian. + +* Fix multiline response parsing. A response was terminated at the first + continuation line whose first three bytes matched the response code, so a reply + such as `220-First` / `220-Second` / `220 Third` was truncated to two lines. Per + [RFC 959](https://datatracker.ietf.org/doc/html/rfc959#page-36) only the code + followed by a space ends a multiline reply; the code followed by a hyphen + continues it. A final line consisting of the bare code is also accepted, for + servers that omit the trailing space. Thanks to @pucsdian. + ## 0.5.1.8 * Fix a crash on short response lines. `getResponse` called `head` on the bytes diff --git a/ftp-client/ftp-client.cabal b/ftp-client/ftp-client.cabal index a7e5ad8..b658910 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.1.8 +version: 0.5.2.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 diff --git a/ftp-client/package.yaml b/ftp-client/package.yaml index 323c3f0..72493b2 100644 --- a/ftp-client/package.yaml +++ b/ftp-client/package.yaml @@ -1,5 +1,5 @@ name: ftp-client -version: 0.5.1.8 +version: 0.5.2.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