Skip to content
Merged
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
6 changes: 6 additions & 0 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@ type Settings struct {
EnableCOMB bool // Enable COMB support
DeflateCompressionLevel int // Deflate compression level (0-9). 0 means disabled
DefaultTransferType TransferType // Transfer type to use if the client don't send the TYPE command
// DisableASCIIConversion disables CRLF/LF line-ending conversion for TYPE A (ASCII) transfers.
// When true, files are transferred byte-for-byte even in ASCII mode, and SIZE is allowed in
// ASCII mode. This is not RFC 959-compliant but matches the behavior of vsftpd's
// ascii_download_enable/ascii_upload_enable=NO and is useful when clients do not want the
// server to modify their files.
DisableASCIIConversion bool
// ActiveConnectionsCheck defines the security requirements for active connections
ActiveConnectionsCheck DataConnectionRequirement
// PasvConnectionsCheck defines the security requirements for passive connections
Expand Down
9 changes: 6 additions & 3 deletions handle_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (c *clientHandler) doFileTransfer(transferConn io.ReadWriter, file io.ReadW
writer = transferConn
}

if c.currentTransferType == TransferTypeASCII {
if c.currentTransferType == TransferTypeASCII && !c.server.settings.DisableASCIIConversion {
reader = newASCIIConverter(reader, conversionMode)
}

Expand Down Expand Up @@ -396,9 +396,12 @@ func (c *clientHandler) handleRNTO(param string) error {
// the current TYPE is ASCII.
// However, clients in general should not be resuming downloads
// in ASCII mode. Resuming downloads in binary mode is the
// recommended way as specified in RFC-3659
// recommended way as specified in RFC-3659.
// When Settings.DisableASCIIConversion is true, no conversion is
// performed and the on-disk size matches the transfer size, so
// SIZE is allowed in ASCII mode.
func (c *clientHandler) handleSIZE(param string) error {
if c.currentTransferType == TransferTypeASCII {
if c.currentTransferType == TransferTypeASCII && !c.server.settings.DisableASCIIConversion {
c.writeMessage(StatusActionNotTaken, "SIZE not allowed in ASCII mode")

return nil
Expand Down
56 changes: 56 additions & 0 deletions transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,62 @@ func TestASCIITransfersInvalidFiles(t *testing.T) {
require.Equal(t, localHash, remoteHash)
}

func TestASCIITransfersWithConversionDisabled(t *testing.T) {
driver := &TestServerDriver{
Settings: &Settings{DisableASCIIConversion: true},
}
s := NewTestServerWithTestDriver(t, driver)
conf := goftp.Config{
User: authUser,
Password: authPass,
}
client, err := goftp.DialConfig(conf, s.Addr())
require.NoError(t, err, "Couldn't connect")

defer func() { require.NoError(t, client.Close()) }()

raw, err := client.OpenRawConn()
require.NoError(t, err)

defer func() { require.NoError(t, raw.Close()) }()

file, err := os.CreateTemp("", "ftpserver")
require.NoError(t, err)

defer func() { require.NoError(t, file.Close()) }()

// A mix of CRLF and lone LF that the converter would normally rewrite.
contents := []byte("line1\r\n\r\nline3\r\n,line4\nline5")
_, err = file.Write(contents)
require.NoError(t, err)

returnCode, response, err := raw.SendCommand("TYPE A")
require.NoError(t, err)
require.Equal(t, StatusOK, returnCode, response)

_, err = file.Seek(0, io.SeekStart)
require.NoError(t, err)

ftpUploadWithRawConnection(t, raw, file, "file.txt", nil)

// On disk size must match the input exactly: no \r stripping on upload.
files, err := client.ReadDir("/")
require.NoError(t, err)
require.Len(t, files, 1)
require.Equal(t, int64(len(contents)), files[0].Size())

// SIZE must be allowed in ASCII mode when conversion is disabled.
returnCode, response, err = raw.SendCommand("SIZE file.txt")
require.NoError(t, err)
require.Equal(t, StatusFileStatus, returnCode, response)
require.Equal(t, strconv.Itoa(len(contents)), response)

// Round-trip hash must match: no line-ending mangling on download either.
remoteHash := ftpDownloadAndHashWithRawConnection(t, raw, "file.txt", nil)
localHash := hashFile(t, file)
require.Equal(t, localHash, remoteHash)
}

func TestPASVWrappedListenerError(t *testing.T) {
server := NewTestServerWithTestDriver(t, &TestServerDriver{
Debug: true,
Expand Down
Loading