diff --git a/ftp-client/CHANGELOG.md b/ftp-client/CHANGELOG.md index 7c3b1ad..f47b347 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 diff --git a/ftp-client/src/Network/FTP/Client.hs b/ftp-client/src/Network/FTP/Client.hs index d780aae..dd94d89 100644 --- a/ftp-client/src/Network/FTP/Client.hs +++ b/ftp-client/src/Network/FTP/Client.hs @@ -40,6 +40,10 @@ module Network.FTP.Client ( Handle(..), -- * Exceptions FTPException(..), + -- * System Handle Creation + createSIOHandle, + createTLSConnection, + connectTLS, -- * Handle Implementations sIOHandleImpl, tlsHandleImpl, @@ -292,9 +296,15 @@ loopMultiLine h code lines = do -- to be kept and the loop has to continue past it. Nothing -> return 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] - nextCode = C.take 3 nextLine - if nextCode == code + 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 fa8b996..bf9d059 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 "stops when the server hangs up during a multiline response" $ do let expected = FTPResponse F.Success 220