Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions ftp-client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion ftp-client/ftp-client.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ftp-client/package.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
14 changes: 12 additions & 2 deletions ftp-client/src/Network/FTP/Client.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ module Network.FTP.Client (
Handle(..),
-- * Exceptions
FTPException(..),
-- * System Handle Creation
createSIOHandle,
createTLSConnection,
connectTLS,
Comment on lines +45 to +46
-- * Handle Implementations
sIOHandleImpl,
tlsHandleImpl,
Expand Down Expand Up @@ -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

Expand Down
37 changes: 37 additions & 0 deletions ftp-client/test/test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading