Skip to content

Fix a crash on short response lines and a hang on mid-reply hangup - #4

Open
onslaughtq wants to merge 3 commits into
mainfrom
fix-response-line-partiality
Open

Fix a crash on short response lines and a hang on mid-reply hangup#4
onslaughtq wants to merge 3 commits into
mainfrom
fix-response-line-partiality

Conversation

@onslaughtq

Copy link
Copy Markdown
Member

Two bugs in getResponse.

C.head was called on the bytes after the three digit code, so any reply line shorter than four bytes died with Prelude.head: empty list instead of an FTPException. A bare 200 does it, as does the empty line a closed socket yields. The code is now checked up front and the remainder matched with C.uncons, which also makes the later read total.

loopMultiLine never terminated when the peer hung up part way through a multiline reply, because no line ever matched the closing code.

The second fix is fussier than it looks, on purpose. recvLine signals end of input by throwing, while an empty ByteString is ordinary reply text — RFC 959 lets the intermediate lines of a multiline reply carry anything, blank lines included. Treating a blank line as the end would cut the reply short and leave its real terminator unread, so the next command would read the previous reply and everything after that would be off by one. The two cases are kept apart.

8 hspec examples, up from 3. The blank line test fails against the old code.

Stacked: add-ci-multi-ghcfix-response-line-partialitypr-2-multiline-and-handle-exportsadd-ci-flagadd-henforcer-fourmolufix-audit-findings. Merge bottom-up.

getResponse called C.head on the remainder after the three digit code, so
any response line shorter than four bytes died with "Prelude.head: empty
list" instead of an FTPException. A bare "200", or the empty line a closed
socket yields, both hit this. Validate the three digit code up front and
match on the remainder with C.uncons, which also makes the later
read of the code total.

loopMultiLine never terminated when the peer hung up mid response, since
no line ever matched the closing code. It now stops at end of input.

End of input and a blank line are deliberately kept distinct. recvLine
signals end of input by throwing, while an empty ByteString is legitimate
reply text -- RFC 959 lets the intermediate lines of a multiline reply
carry arbitrary text. Collapsing the two would truncate any reply
containing a blank line and leave its real terminator unread, so every
later command would pick up the previous reply.
Both changes on this branch are bug fixes with no API change, so PVP asks
for a patch bump.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes FTP response parsing failures and incomplete multiline replies.

Changes:

  • Validates three-digit response codes without partial operations.
  • Distinguishes blank lines from EOF in multiline responses.
  • Adds regression tests, changelog notes, and bumps version to 0.5.1.8.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
ftp-client/src/Network/FTP/Client.hs Updates response parsing and EOF handling.
ftp-client/test/test.hs Adds response parsing regression tests.
ftp-client/package.yaml Bumps the package version.
ftp-client/ftp-client.cabal Synchronizes the generated package version.
ftp-client/CHANGELOG.md Documents the fixes.
Suppressed comments (1)

ftp-client/src/Network/FTP/Client.hs:297

  • This treats any intermediate line whose first three bytes equal the reply code as the terminator, although a multiline reply closes only when that code is followed by a space. For example, an intermediate 220-not a terminator would end the response here and leave the real terminator buffered for the next command. Check the required separator as well.
            if nextCode == code

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ftp-client/src/Network/FTP/Client.hs Outdated
Base automatically changed from add-ci-multi-ghc to main August 24, 2026 15:43
Terminating instead of looping forever was only half of the fix. Returning
the lines collected so far handed back a fragment as though it were a whole
reply, so a greeting cut off after "220-" produced a well formed FTPResponse
with code 220 and status Success, and withFTP carried on against a control
connection that was already gone. End of input mid reply now raises
BadProtocolResponseException carrying what did arrive.

The distinction that motivated the original fix is unchanged: end of input is
not a blank line. RFC 959 lets the intermediate lines of a multiline reply
hold arbitrary text, blank lines included, so a blank line still has to be
kept and the loop still has to continue past it.

The existing test asserted the old behaviour -- it expected exactly the
Success 220 that this commit rejects -- so it becomes a shouldThrow rather
than a new case beside it.

Reported by Copilot on #4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@onslaughtq
onslaughtq marked this pull request as ready for review August 25, 2026 01:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants